diff --git a/cmd/launch/launch.go b/cmd/launch/launch.go index 41cd84270..fcc3d7329 100644 --- a/cmd/launch/launch.go +++ b/cmd/launch/launch.go @@ -588,7 +588,7 @@ func (c *launcherClient) launchManagedSingleIntegration(ctx context.Context, nam return nil } - if (current == "" || needsConfigure || req.ModelOverride != "" || target != current) && !savedMatchesModels(saved, []string{target}) { + if needsConfigure || req.ModelOverride != "" || (current != "" && target != current) || !savedMatchesModels(saved, []string{target}) { if err := prepareManagedSingleIntegration(name, runner, managed, target); err != nil { return err } diff --git a/cmd/launch/launch_test.go b/cmd/launch/launch_test.go index a742b0ffb..b7779108a 100644 --- a/cmd/launch/launch_test.go +++ b/cmd/launch/launch_test.go @@ -512,6 +512,65 @@ func TestLaunchIntegration_ManagedSingleIntegrationRewritesWhenSavedDiffers(t *t } } +func TestLaunchIntegration_ManagedSingleIntegrationRewritesWhenLiveConfigDrifts(t *testing.T) { + tmpDir := t.TempDir() + setLaunchTestHome(t, tmpDir) + withInteractiveSession(t, true) + withLauncherHooks(t) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/tags": + fmt.Fprint(w, `{"models":[{"name":"gemma4"},{"name":"qwen3:8b"}]}`) + case "/api/show": + fmt.Fprint(w, `{"model_info":{"general.context_length":131072}}`) + default: + http.NotFound(w, r) + } + })) + defer srv.Close() + t.Setenv("OLLAMA_HOST", srv.URL) + + if err := config.SaveIntegration("stubmanaged", []string{"gemma4"}); err != nil { + t.Fatalf("failed to save managed integration config: %v", err) + } + + runner := &launcherManagedRunner{ + currentModel: "qwen3:8b", + } + withIntegrationOverride(t, "stubmanaged", runner) + + DefaultSingleSelector = func(title string, items []ModelItem, current string) (string, error) { + t.Fatal("selector should not be called when live config already provides the target") + return "", nil + } + DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) { + return true, nil + } + + if err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: "stubmanaged"}); err != nil { + t.Fatalf("LaunchIntegration returned error: %v", err) + } + + if diff := compareStrings(runner.configured, []string{"qwen3:8b"}); diff != "" { + t.Fatalf("expected Configure to reconcile stale saved config to live target: %s", diff) + } + if runner.refreshCalls != 1 { + t.Fatalf("expected runtime refresh once after drift reconciliation, got %d", runner.refreshCalls) + } + if runner.ranModel != "qwen3:8b" { + t.Fatalf("expected launch to run live configured model, got %q", runner.ranModel) + } + + saved, err := config.LoadIntegration("stubmanaged") + if err != nil { + t.Fatalf("failed to reload managed integration config: %v", err) + } + if diff := compareStrings(saved.Models, []string{"qwen3:8b"}); diff != "" { + t.Fatalf("saved models mismatch after drift reconciliation: %s", diff) + } +} + func TestLaunchIntegration_ManagedSingleIntegrationStopsWhenRuntimeRefreshFails(t *testing.T) { tmpDir := t.TempDir() setLaunchTestHome(t, tmpDir)