mirror of
https://github.com/ollama/ollama.git
synced 2026-07-23 09:10:53 -05:00
launch: codex app restarts (#16155)
This commit is contained in:
@@ -39,11 +39,16 @@ var (
|
||||
codexAppOpenPath = defaultCodexAppOpenAppPath
|
||||
codexAppOpenStart = defaultCodexAppOpenStartAppID
|
||||
codexAppQuitApp = defaultCodexAppQuitApp
|
||||
codexAppForceQuit = defaultCodexAppForceQuitApp
|
||||
codexAppHasWindow = defaultCodexAppHasOpenWindow
|
||||
codexAppIsRunning = defaultCodexAppIsRunning
|
||||
codexAppRunPath = defaultCodexAppRunningAppPath
|
||||
codexAppStartID = defaultCodexAppStartAppID
|
||||
codexAppCanOpenID = defaultCodexAppCanOpenBundleID
|
||||
codexAppSleep = time.Sleep
|
||||
|
||||
codexAppExitTimeout = 5 * time.Second
|
||||
codexAppForceExitTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// CodexApp configures the desktop Codex app with one launch-selected default
|
||||
@@ -367,7 +372,7 @@ type codexAppModelMetadata struct {
|
||||
|
||||
func codexAppDefaultModelMetadata() codexAppModelMetadata {
|
||||
return codexAppModelMetadata{
|
||||
contextWindow: 272_000,
|
||||
contextWindow: 128_000,
|
||||
inputModalities: []string{"text"},
|
||||
}
|
||||
}
|
||||
@@ -567,8 +572,21 @@ func codexAppLaunchOrRestart(prompt string) error {
|
||||
if err := codexAppQuitApp(); err != nil {
|
||||
return fmt.Errorf("quit Codex: %w", err)
|
||||
}
|
||||
if err := waitForCodexAppExit(30 * time.Second); err != nil {
|
||||
return err
|
||||
gracefulErr := waitForCodexAppGracefulExit(codexAppExitTimeout)
|
||||
if gracefulErr != nil && !codexAppForceQuitSupported() {
|
||||
return gracefulErr
|
||||
}
|
||||
if codexAppForceQuitSupported() && codexAppIsRunning() {
|
||||
if forceErr := codexAppForceQuit(); forceErr != nil {
|
||||
return fmt.Errorf("force stop Codex: %w", forceErr)
|
||||
}
|
||||
if err := waitForCodexAppExit(codexAppForceExitTimeout); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if gracefulErr != nil {
|
||||
if codexAppIsRunning() {
|
||||
return gracefulErr
|
||||
}
|
||||
}
|
||||
if restartAppID != "" {
|
||||
return codexAppOpenStart(restartAppID)
|
||||
@@ -579,10 +597,29 @@ func codexAppLaunchOrRestart(prompt string) error {
|
||||
return codexAppOpenApp()
|
||||
}
|
||||
|
||||
func codexAppForceQuitSupported() bool {
|
||||
return codexAppGOOS == "darwin" || codexAppGOOS == "windows"
|
||||
}
|
||||
|
||||
func waitForCodexAppGracefulExit(timeout time.Duration) error {
|
||||
return waitForCodexAppCondition(timeout, func() bool {
|
||||
if codexAppGOOS == "windows" {
|
||||
return !codexAppHasWindow()
|
||||
}
|
||||
return !codexAppIsRunning()
|
||||
})
|
||||
}
|
||||
|
||||
func waitForCodexAppExit(timeout time.Duration) error {
|
||||
return waitForCodexAppCondition(timeout, func() bool {
|
||||
return !codexAppIsRunning()
|
||||
})
|
||||
}
|
||||
|
||||
func waitForCodexAppCondition(timeout time.Duration, done func() bool) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if !codexAppIsRunning() {
|
||||
if done() {
|
||||
return nil
|
||||
}
|
||||
codexAppSleep(200 * time.Millisecond)
|
||||
@@ -650,6 +687,46 @@ func defaultCodexAppQuitApp() error {
|
||||
return scriptErr
|
||||
}
|
||||
|
||||
func defaultCodexAppForceQuitApp() error {
|
||||
if !codexAppForceQuitSupported() {
|
||||
return nil
|
||||
}
|
||||
pids := codexAppMatchingProcessIDs()
|
||||
if len(pids) == 0 {
|
||||
return nil
|
||||
}
|
||||
pidArgs := make([]string, 0, len(pids))
|
||||
for _, pid := range pids {
|
||||
pidArgs = append(pidArgs, strconv.Itoa(pid))
|
||||
}
|
||||
switch codexAppGOOS {
|
||||
case "windows":
|
||||
script := "Stop-Process -Id " + strings.Join(pidArgs, ",") + " -Force -ErrorAction SilentlyContinue"
|
||||
return runCodexAppForceQuitCommand(exec.Command("powershell.exe", "-NoProfile", "-Command", script))
|
||||
case "darwin":
|
||||
return runCodexAppForceQuitCommand(exec.Command("kill", append([]string{"-TERM"}, pidArgs...)...))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func runCodexAppForceQuitCommand(cmd *exec.Cmd) error {
|
||||
err := cmd.Run()
|
||||
if err != nil && !codexAppIsRunning() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func defaultCodexAppHasOpenWindow() bool {
|
||||
if codexAppGOOS != "windows" {
|
||||
return codexAppIsRunning()
|
||||
}
|
||||
script := `(Get-Process Codex -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 } | Select-Object -First 1).Id`
|
||||
out, err := exec.Command("powershell.exe", "-NoProfile", "-Command", script).Output()
|
||||
return err == nil && strings.TrimSpace(string(out)) != ""
|
||||
}
|
||||
|
||||
func defaultCodexAppIsRunning() bool {
|
||||
switch codexAppGOOS {
|
||||
case "windows":
|
||||
|
||||
@@ -30,10 +30,15 @@ func withCodexAppProcessHooks(t *testing.T, isRunning func() bool, quit func() e
|
||||
oldOpen := codexAppOpenApp
|
||||
oldOpenPath := codexAppOpenPath
|
||||
oldOpenStart := codexAppOpenStart
|
||||
oldForceQuit := codexAppForceQuit
|
||||
oldHasWindow := codexAppHasWindow
|
||||
oldRunPath := codexAppRunPath
|
||||
oldStartID := codexAppStartID
|
||||
oldCanOpenID := codexAppCanOpenID
|
||||
oldExitTimeout := codexAppExitTimeout
|
||||
oldForceExitTimeout := codexAppForceExitTimeout
|
||||
codexAppIsRunning = isRunning
|
||||
codexAppHasWindow = isRunning
|
||||
codexAppQuitApp = quit
|
||||
codexAppOpenApp = open
|
||||
t.Cleanup(func() {
|
||||
@@ -42,9 +47,13 @@ func withCodexAppProcessHooks(t *testing.T, isRunning func() bool, quit func() e
|
||||
codexAppOpenApp = oldOpen
|
||||
codexAppOpenPath = oldOpenPath
|
||||
codexAppOpenStart = oldOpenStart
|
||||
codexAppForceQuit = oldForceQuit
|
||||
codexAppHasWindow = oldHasWindow
|
||||
codexAppRunPath = oldRunPath
|
||||
codexAppStartID = oldStartID
|
||||
codexAppCanOpenID = oldCanOpenID
|
||||
codexAppExitTimeout = oldExitTimeout
|
||||
codexAppForceExitTimeout = oldForceExitTimeout
|
||||
})
|
||||
}
|
||||
|
||||
@@ -585,7 +594,7 @@ func TestCodexAppConfigurePopulatesCatalogFromTagsAndShow(t *testing.T) {
|
||||
if !ok || len(levels) != 0 {
|
||||
t.Fatalf("supported_reasoning_levels for %q = %v, want empty list", slug, model["supported_reasoning_levels"])
|
||||
}
|
||||
wantContext := float64(272000)
|
||||
wantContext := float64(128000)
|
||||
wantModalities := []string{"text"}
|
||||
wantShowCalls := 0
|
||||
if slug == "gemma4" {
|
||||
@@ -1165,6 +1174,64 @@ func TestCodexAppRunWaitsForGracefulExitBeforeReopening(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunForceStopsMacAfterGracefulTimeout(t *testing.T) {
|
||||
withCodexAppPlatform(t, "darwin")
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
running := true
|
||||
calls := make([]string, 0)
|
||||
withCodexAppProcessHooks(t,
|
||||
func() bool { return running },
|
||||
func() error {
|
||||
calls = append(calls, "quit")
|
||||
return nil
|
||||
},
|
||||
func() error {
|
||||
calls = append(calls, "open")
|
||||
return nil
|
||||
},
|
||||
)
|
||||
codexAppExitTimeout = 0
|
||||
codexAppForceQuit = func() error {
|
||||
calls = append(calls, "force")
|
||||
running = false
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := (&CodexApp{}).Run("qwen3.5", nil); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
want := []string{"quit", "force", "open"}
|
||||
if strings.Join(calls, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("calls = %v, want %v", calls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunReturnsMacForceStopError(t *testing.T) {
|
||||
withCodexAppPlatform(t, "darwin")
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
withCodexAppProcessHooks(t,
|
||||
func() bool { return true },
|
||||
func() error { return nil },
|
||||
func() error {
|
||||
t.Fatal("app should not reopen when force stop fails")
|
||||
return nil
|
||||
},
|
||||
)
|
||||
codexAppExitTimeout = 0
|
||||
codexAppForceQuit = func() error {
|
||||
return fmt.Errorf("operation not permitted")
|
||||
}
|
||||
|
||||
err := (&CodexApp{}).Run("qwen3.5", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "force stop Codex") || !strings.Contains(err.Error(), "operation not permitted") {
|
||||
t.Fatalf("Run error = %v, want force stop failure", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunOpensOnWindowsWhenNotRunning(t *testing.T) {
|
||||
withCodexAppPlatform(t, "windows")
|
||||
|
||||
@@ -1234,6 +1301,79 @@ func TestCodexAppRunRestartsWindowsStartAppID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunForceStopsWindowsBackgroundProcessesBeforeReopening(t *testing.T) {
|
||||
withCodexAppPlatform(t, "windows")
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
windowOpen := true
|
||||
running := true
|
||||
calls := make([]string, 0)
|
||||
withCodexAppProcessHooks(t,
|
||||
func() bool { return running },
|
||||
func() error {
|
||||
calls = append(calls, "quit")
|
||||
windowOpen = false
|
||||
return nil
|
||||
},
|
||||
func() error {
|
||||
t.Fatal("open app fallback should not be used")
|
||||
return nil
|
||||
},
|
||||
)
|
||||
codexAppHasWindow = func() bool { return windowOpen }
|
||||
codexAppForceQuit = func() error {
|
||||
calls = append(calls, "force")
|
||||
running = false
|
||||
return nil
|
||||
}
|
||||
codexAppStartID = func() string { return "OpenAI.Codex_2p2nqsd0c76g0!App" }
|
||||
codexAppOpenStart = func(appID string) error {
|
||||
calls = append(calls, "open:"+appID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := (&CodexApp{}).Run("qwen3.5", nil); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
want := []string{"quit", "force", "open:OpenAI.Codex_2p2nqsd0c76g0!App"}
|
||||
if strings.Join(calls, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("calls = %v, want %v", calls, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunReturnsWindowsForceStopError(t *testing.T) {
|
||||
withCodexAppPlatform(t, "windows")
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
windowOpen := true
|
||||
withCodexAppProcessHooks(t,
|
||||
func() bool { return true },
|
||||
func() error {
|
||||
windowOpen = false
|
||||
return nil
|
||||
},
|
||||
func() error {
|
||||
t.Fatal("open app fallback should not be used")
|
||||
return nil
|
||||
},
|
||||
)
|
||||
codexAppHasWindow = func() bool { return windowOpen }
|
||||
codexAppForceQuit = func() error {
|
||||
return fmt.Errorf("access denied")
|
||||
}
|
||||
codexAppOpenStart = func(string) error {
|
||||
t.Fatal("app should not reopen when force stop fails")
|
||||
return nil
|
||||
}
|
||||
|
||||
err := (&CodexApp{}).Run("qwen3.5", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "force stop Codex") || !strings.Contains(err.Error(), "access denied") {
|
||||
t.Fatalf("Run error = %v, want force stop failure", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppRunRejectsExtraArgs(t *testing.T) {
|
||||
withCodexAppPlatform(t, "darwin")
|
||||
err := (&CodexApp{}).Run("qwen3.5", []string{"--foo"})
|
||||
|
||||
Reference in New Issue
Block a user