mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
feat(terminal): warn when a typed Codex joins Codex's shared server (STA-9051) (#24217)
* feat(terminal): warn when a typed Codex joins Codex's shared server Orca adds --no-daemon to the Codex it launches and to a codex typed in shells whose wrapper it controls, but a codex typed another way (fish, cmd.exe, a path-named binary) still joins Codex's shared server, which mixes up agent status across tabs. When a local pane's Codex is on that server, show a banner at the top of the pane with the command that turns auto-start off, a Copy button, "Don't show again" (a new setting next to the Codex server setting) and a per-pane dismiss. The banner takes layout space; the terminal refits below it. Main answers pty:isCodexOnSharedServer from the pane's outermost Codex command line (flags and subcommands that keep Codex embedded rule it out), the CODEX_HOME the pane launched with, and whether that home's server is live: a socket connect on macOS/Linux, the server's pid record plus creation time on Windows. The renderer asks only while the pane already shows Codex, on a short bounded ladder. Refs STA-9051 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: restore the Claude WSL trust-file fix (#23973) dropped by the banner commit Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(terminal): redesign the Codex shared-server banner and fix dialog Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(terminal): let the Codex shared-server fix run its commands The fix dialog now runs each step with the shared server's own Codex on the pane's CODEX_HOME, verifies the result (feature read back, server probed), and falls back to a copyable command on failure. Stopping asks first. Also: an apostrophe in a prompt no longer hides an opt-out flag, restored panes fall back to the saved pty id, and the IPC guards have a table test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(terminal): give each fix step its own card and label the command it runs Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(terminal): simplify the Codex shared-server banner after review - Read a subcommand only from Codex's first positional, so prompt words like "a", "update" or "review" no longer hide the banner. - Probe the server fresh on every ask; drop the probe cache. - Make the pty preload methods required and stub them on the web client, replacing the optional-method and paired-client checks. - Render the banner from the existing Codex pane portal loop. - Treat a non-zero or timed-out Codex command as failed; skip the read-back when the disable write failed. - Reserve the banner's space with a CSS :has() selector instead of a data attribute. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(terminal): make the Codex shared-server fix persist on Orca's mirror home - Step 1 now writes daemon_auto_start = false to the user's own Codex home when the pane runs on Orca's shared mirror home (as Windows panes do), then to the mirror home too; the mirror is rebuilt from the user's home on every launch, so a mirror-only write was lost. - The server probe is three-state (live / absent / unknown); stop reports success only once the server is proven gone. - The banner retires the one-time "runs Codex without its shared server" toast it contradicts. - A command line with no Codex program never counts as joining the server. - The fallback local PTY provider reports each pane's root pid. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(codex): keep Turn off in Orca's Codex home when ~/.codex has no config A pane on Orca's mirror home wrote the setting to ~/.codex first. With no ~/.codex the spawn failed on its cwd and Codex rejects a missing CODEX_HOME; and creating a config holding only this setting would make the next mirror replace every setting made in Orca's Codex. The mirror skips a missing or blank ~/.codex/config.toml, so write only the mirror home then. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(codex): promote [features].daemon_auto_start from Orca's Codex home Promotion now carries one [features] key alongside the [tui] keys, so a shared-server Turn off written in Orca's mirror home reaches ~/.codex/config.toml instead of being reverted by the next mirror. Table keys share one <table>.<key> scan for read, removal and upsert. Orca's own daemon socket override is never read as a user value, and a blank source config is seeded from the runtime like a missing one. * refactor(codex): run Turn off once, in the pane's own Codex home Settings promotion now carries the setting to ~/.codex, so the separate settings-home resolution and the two-home loop are gone. * fix(terminal): offer Stop server only after sharing is turned off Stopping while sharing is still on closes every sharing session, and the next Codex starts a new shared server. * fix(terminal): skip legacy mirror panes off Windows and quoted dotted keys A retained shared-home pane on macOS/Linux points at a mirror that is no longer promoted, so Turn off there would be reverted; name no home for it. A quoted top-level key such as "tui.theme" is one key, not [tui].theme. * fix(terminal): drop the Turn off note that promised the setting reaches Codex outside Orca Orca's tabs are what this fix is for; carrying the setting to ~/.codex is best-effort. * fix(terminal): keep the Turn off note that the setting also applies outside Orca It holds for nearly everyone; the rare Windows upgrade gaps don't justify hiding it. * fix(codex): promote Turn off to ~/.codex under an older Orca's baseline A pane on Orca's Windows mirror home writes daemon_auto_start = false into the mirror. A promotion baseline from an Orca that predates this key has no entry for it, so the next mirror pass kept the write as a conflict and then recorded it, and ~/.codex never got the setting. Turn off on a mirror-home pane now runs the same mirror pass a terminal launch runs before and after the write: the first records the key in the baseline, the second promotes the write to ~/.codex. The passes are synchronous, so they cannot interleave with a launch's pass. A failed pass is logged and does not fail Turn off, since the write still fixes Orca's tabs. Real-home panes are unchanged. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8b76683b40
commit
eefc49f7e3
@@ -0,0 +1,122 @@
|
||||
// Mirrors Codex's own opt-outs (tui/src/daemon_startup.rs `exclusion`). Every
|
||||
// `-c`/`--enable`/`--disable` counts, although Codex allows a few: skipping one
|
||||
// only costs a warning, never a false one.
|
||||
const EMBEDDED_FLAGS: ReadonlySet<string> = new Set([
|
||||
'--no-daemon',
|
||||
'--oss',
|
||||
'--remote',
|
||||
'--profile',
|
||||
'-p',
|
||||
'--strict-config',
|
||||
'--dangerously-bypass-hook-trust',
|
||||
'--search',
|
||||
'--approve-for-me',
|
||||
'--not-so-yolo',
|
||||
'--enable',
|
||||
'--disable',
|
||||
'--config',
|
||||
'-c'
|
||||
])
|
||||
|
||||
// Every subcommand in codex-rs/cli/src/main.rs except the ones that open the TUI
|
||||
// on the shared server: `resume`, `fork` and `agents`.
|
||||
const NON_TUI_SUBCOMMANDS: ReadonlySet<string> = new Set([
|
||||
'exec',
|
||||
'e',
|
||||
'review',
|
||||
'login',
|
||||
'logout',
|
||||
'mcp',
|
||||
'plugin',
|
||||
'app-server',
|
||||
'remote-control',
|
||||
'app',
|
||||
'completion',
|
||||
'update',
|
||||
'doctor',
|
||||
'sandbox',
|
||||
'debug',
|
||||
'execpolicy',
|
||||
'apply',
|
||||
'a',
|
||||
'queue',
|
||||
'archive',
|
||||
'delete',
|
||||
'migrate-rollouts',
|
||||
'unarchive',
|
||||
'cloud',
|
||||
'cloud-tasks',
|
||||
'responses-api-proxy',
|
||||
'stdio-to-uds',
|
||||
'exec-server',
|
||||
'features',
|
||||
'tcp-tunnel',
|
||||
'help'
|
||||
])
|
||||
|
||||
// Flags that never take a value, so the word after one is still the first positional.
|
||||
const VALUELESS_FLAGS: ReadonlySet<string> = new Set([
|
||||
'--dangerously-bypass-approvals-and-sandbox',
|
||||
'--yolo',
|
||||
'--no-alt-screen',
|
||||
'--worktree'
|
||||
])
|
||||
|
||||
// The program word: `codex`, `codex.exe`, the npm `codex.js` launcher, or a platform binary.
|
||||
const CODEX_PROGRAM_RE = /(?:^|[\\/])codex(?:\.(?:js|mjs|cjs|exe|cmd)|-[^\\/]+)?$/i
|
||||
|
||||
function isEmbeddedFlag(word: string): boolean {
|
||||
const name = word.split('=', 1)[0] ?? word
|
||||
// Why the prefix check: clap also accepts a short flag glued to its value (`-pwork`, `-cx=1`).
|
||||
return EMBEDDED_FLAGS.has(name) || /^-[pc][^-]/.test(word)
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a Codex process-table command line is an interactive Codex that
|
||||
* joins its shared server when one is running. Process tables may join argv
|
||||
* with spaces, so words are split on whitespace (double quotes group, as
|
||||
* Windows quotes paths); apostrophes never group because a prompt's `don't`
|
||||
* would swallow the rest. Opt-outs set through env (`CODEX_EXEC_SERVER_URL`,
|
||||
* workload identity) are invisible here, so those rare panes get a false banner.
|
||||
*/
|
||||
export function codexCommandLineJoinsSharedServer(commandLine: string): boolean {
|
||||
const words = (commandLine.match(/"[^"]*"|\S+/g) ?? []).map((word) =>
|
||||
word.replace(/^["']+|["']+$/g, '')
|
||||
)
|
||||
const programIndex = words.findIndex((word) => CODEX_PROGRAM_RE.test(word))
|
||||
if (programIndex === -1) {
|
||||
return false
|
||||
}
|
||||
const args = words.slice(programIndex + 1)
|
||||
if (args.some(isEmbeddedFlag)) {
|
||||
return false
|
||||
}
|
||||
// Why only the first positional: clap reads a subcommand there, and later words are prompt text.
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const word = args[index]
|
||||
if (word.startsWith('-')) {
|
||||
continue
|
||||
}
|
||||
if (NON_TUI_SUBCOMMANDS.has(word)) {
|
||||
return false
|
||||
}
|
||||
const previous = args[index - 1]
|
||||
// Why check one more: this word may be the previous flag's value (`-m gpt-5 exec`).
|
||||
if (!previous?.startsWith('-') || previous.includes('=') || VALUELESS_FLAGS.has(previous)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const CODEX_SHARED_SERVER_FEATURE_KEY = 'daemon_auto_start'
|
||||
/** The fix's commands, as argv after the `codex` program. */
|
||||
export const CODEX_DISABLE_SHARED_SERVER_ARGS = [
|
||||
'features',
|
||||
'disable',
|
||||
CODEX_SHARED_SERVER_FEATURE_KEY
|
||||
] as const
|
||||
export const CODEX_STOP_SHARED_SERVER_ARGS = ['app-server', 'daemon', 'stop'] as const
|
||||
/** What the fix dialog shows the user it runs. */
|
||||
export const CODEX_DISABLE_AUTO_START_COMMAND = `codex ${CODEX_DISABLE_SHARED_SERVER_ARGS.join(' ')}`
|
||||
export const CODEX_STOP_SHARED_SERVER_COMMAND = `codex ${CODEX_STOP_SHARED_SERVER_ARGS.join(' ')}`
|
||||
Reference in New Issue
Block a user