mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 08:02:35 +00:00
fix(native-chat): refuse a Command that can't run instead of silently running the stock CLI (#25667)
* fix(native-chat): honor custom Claude and Codex launch commands * fix(native-chat): sync launch failure localization catalogs * feat(native-chat): run Settings → Agents → Command as the native chat program Native chat ignored the Command setting, so a user who pointed it at a custom Claude or Codex build still got the stock CLI. The host now reads the Command per acquisition and per model-catalog probe, resolves it on the execution host as a program (an absolute path, ~ path, or a name on PATH), and spawns it with the normal structured arguments. A set Command that is not a runnable program refuses the start with a new agentCommandNotRunnable failure that names the setting; it never falls back to the stock CLI. * fix(native-chat): find a configured Claude Command on the launch PATH; clearer copy A Claude Command given as a bare name was looked up on Orca's own PATH, not the PATH the Claude child launches with (login shell plus Settings → Agents environment), so a name only that environment provides was refused. Codex already resolved against its launch environment. Claude now reads its launch env first and resolves a configured name against its PATH and home; the stock lookup with no Command is unchanged. The failure copy now states the rule (a program path or name, no arguments or variables) and names the Settings control (Reset). The catalog fingerprint doc notes that the program is not keyed, so a chat on an older program keeps refreshing the shared entry until it ends. * fix(native-chat): one PATH key for the Claude lookup and the child on Windows On Windows, an inherited `Path` and a Settings → Agents env `PATH` both reached the Claude child. The configured-program lookup read the first inserted twin (inherited), while Node's child_process keeps the lexicographically first (`PATH`, the user's), so a bare-name Command found only on the Settings PATH was refused. The Claude launch env now drops inherited case-twins of the overlay's variables (the rule the structured shell env already applies), so the lookup and the child read one PATH. pathEnvOf follows Node's win32 key rule, and Codex's lookup uses it too, so both agents read PATH one way. es/fr/ja copy now says the Command must be, as the English does. * fix(native-chat): refuse a Command that can't run instead of running the stock CLI A saved Settings → Agents → Command that did not resolve to a program (a command line such as `codex --profile work`, `FOO=1 claude` or `npx …`, a missing or relative path, a non-executable file) silently ran the stock Claude/Codex in native chat and its model-catalog probe. The user's choice was ignored with no sign of it. A set Command that does not resolve now refuses the start with a new agentCommandNotRunnable failure naming the setting, beside the existing Retry; the catalog probe refuses the same way and never lists the stock CLI. A blank Command keeps the stock lookup. On Windows a resolved file without a spawnable extension (.exe/.com/.cmd/.bat) refuses too, instead of failing to spawn with no word about the setting. * chore(native-chat): correct comments that say native chat ignores the Command After #25721 native chat runs a runnable Command, and this PR refuses one it can't run; four comments still said the Command applies to terminal launches only. The pre-spawn test fixture also drops the saved value from its error message, matching the resolver.
This commit is contained in:
@@ -59,8 +59,8 @@ function listedResult(): DiscoverResult {
|
||||
}
|
||||
|
||||
describe('claude model catalog probe', () => {
|
||||
it.each([`"${process.execPath}"`, 'npx claude', '/missing/claude', './claude'])(
|
||||
'lists with the session executable for Command %s',
|
||||
it.each([`"${process.execPath}"`, ''])(
|
||||
'lists with the session executable for Command %j',
|
||||
async (command) => {
|
||||
const deps = probeDeps(command)
|
||||
const captured: DiscoverInput[] = []
|
||||
@@ -134,6 +134,16 @@ describe('claude model catalog probe', () => {
|
||||
await expect(probe('/homes/a')).rejects.toThrow(/listed no models/)
|
||||
})
|
||||
|
||||
it.each(['npx claude', 'claude --verbose', '/missing/claude', './claude'])(
|
||||
'lists nothing, and never probes the stock CLI, for Command %s',
|
||||
async (command) => {
|
||||
const discover = vi.fn()
|
||||
const probe = createClaudeModelCatalogProbe({ ...probeDeps(command), discover })
|
||||
await expect(probe('/homes/a')).rejects.toMatchObject({ reason: 'agentCommandNotRunnable' })
|
||||
expect(discover).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it.runIf(process.platform !== 'win32')(
|
||||
'lists through a supervised one-shot that answers after its input ends',
|
||||
async () => {
|
||||
|
||||
@@ -13,8 +13,8 @@ const MODEL_ROW = {
|
||||
}
|
||||
|
||||
describe('codex model catalog probe', () => {
|
||||
it.each([`"${process.execPath}"`, 'npx codex', '/missing/codex', './codex'])(
|
||||
'lists with the session executable for Command %s',
|
||||
it.each([`"${process.execPath}"`, ''])(
|
||||
'lists with the session executable for Command %j',
|
||||
async (command) => {
|
||||
// The env a user's shell/config resolves for sessions, PATH included.
|
||||
const resolveEnvironment = async (): Promise<NodeJS.ProcessEnv> => ({
|
||||
@@ -111,4 +111,23 @@ describe('codex model catalog probe', () => {
|
||||
})
|
||||
await expect(probe('/homes/a')).rejects.toThrow(/listed no models/)
|
||||
})
|
||||
|
||||
it.each(['npx codex', 'codex --profile work', '/missing/codex', './codex'])(
|
||||
'lists nothing, and never probes the stock CLI, for Command %s',
|
||||
async (command) => {
|
||||
const runSession = vi.fn()
|
||||
const probe = createCodexModelCatalogProbe({
|
||||
resolveEnvironment: async () => ({ PATH: '/bin' }),
|
||||
resolveCommand: (options) =>
|
||||
resolveStructuredAgentCommand(
|
||||
'codex',
|
||||
{ agentCmdOverrides: { codex: command } },
|
||||
options
|
||||
),
|
||||
runSession
|
||||
})
|
||||
await expect(probe('/homes/a')).rejects.toMatchObject({ reason: 'agentCommandNotRunnable' })
|
||||
expect(runSession).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -133,6 +133,7 @@ export type AgentSessionPreSpawnReason = Extract<
|
||||
| 'accountSwitchInProgress'
|
||||
| 'managedAccountUnsupported'
|
||||
| 'launchFolderMissing'
|
||||
| 'agentCommandNotRunnable'
|
||||
>
|
||||
|
||||
/** Acquisition failed with first-hand proof that no provider process existed. */
|
||||
|
||||
@@ -99,6 +99,10 @@ describe('structuredAgentSessionStartFailure', () => {
|
||||
[
|
||||
'managedAccountUnsupported',
|
||||
'While a Claude account is added in WSL, Claude chats need a Windows Claude account. Choose or add one in Claude Accounts settings, then send your message again.'
|
||||
],
|
||||
[
|
||||
'agentCommandNotRunnable',
|
||||
"Claude's Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it."
|
||||
]
|
||||
] as const)('words a start refused for %s by that situation', (reason, sentence) => {
|
||||
const code = 'agent_session_operation_invalid'
|
||||
|
||||
@@ -29,7 +29,8 @@ const TYPED_START_REFUSALS = [
|
||||
'managedAccountEnvOverride',
|
||||
'accountSwitchInProgress',
|
||||
'managedAccountUnsupported',
|
||||
'launchFolderMissing'
|
||||
'launchFolderMissing',
|
||||
'agentCommandNotRunnable'
|
||||
] as const satisfies readonly (AgentSessionFailureKind &
|
||||
AgentSessionRefusalReason<'agent_session_operation_invalid'>)[]
|
||||
|
||||
|
||||
+6
@@ -163,6 +163,12 @@ describe('a create that fails before any process spawns', () => {
|
||||
'launchFolderMissing',
|
||||
'The folder this chat ran in no longer exists. Restore it to continue this chat.'
|
||||
],
|
||||
[
|
||||
'a Command setting that names no runnable program',
|
||||
'the claude Command setting is not a runnable program',
|
||||
'agentCommandNotRunnable',
|
||||
"Claude's Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it."
|
||||
],
|
||||
[
|
||||
"Orca's own reason",
|
||||
'claude sessions pin CLAUDE_CONFIG_DIR, not CODEX_HOME',
|
||||
|
||||
@@ -5,6 +5,11 @@ import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { resolveStructuredAgentCommand } from './structured-agent-command-resolution'
|
||||
import { resolveCliCommand } from '../../shared/node-cli-command-resolution'
|
||||
|
||||
const NOT_RUNNABLE = expect.objectContaining({
|
||||
name: 'AgentSessionPreSpawnError',
|
||||
reason: 'agentCommandNotRunnable'
|
||||
})
|
||||
|
||||
const scratch: string[] = []
|
||||
afterEach(() => {
|
||||
for (const dir of scratch.splice(0)) {
|
||||
@@ -62,7 +67,12 @@ describe('structured agent executable resolution', () => {
|
||||
expect(resolveStructuredAgentCommand('claude', settings)).toBe(second.command)
|
||||
})
|
||||
|
||||
it('uses the stock executable for missing files, directories, relative paths and shell lines', () => {
|
||||
it.each(['', ' ', undefined])('uses the stock executable when the Command is %j', (command) => {
|
||||
const settings = command === undefined ? {} : { agentCmdOverrides: { claude: command } }
|
||||
expect(resolveStructuredAgentCommand('claude', settings)).toBe(resolveCliCommand('claude'))
|
||||
})
|
||||
|
||||
it('refuses missing files, directories, relative paths and command lines instead of the stock executable', () => {
|
||||
const { directory } = executable()
|
||||
const folder = join(directory, 'folder')
|
||||
mkdirSync(folder)
|
||||
@@ -71,18 +81,89 @@ describe('structured agent executable resolution', () => {
|
||||
folder,
|
||||
'./claude',
|
||||
'npx claude',
|
||||
'wrapper --flag'
|
||||
'wrapper --flag',
|
||||
'FOO=1 claude',
|
||||
'$HOME/bin/claude'
|
||||
]) {
|
||||
const settings = { agentCmdOverrides: { claude: command } }
|
||||
expect(resolveStructuredAgentCommand('claude', settings)).toBe(resolveCliCommand('claude'))
|
||||
expect(() => resolveStructuredAgentCommand('claude', settings)).toThrow(NOT_RUNNABLE)
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')('requires executable permission on Unix', () => {
|
||||
const { command } = executable()
|
||||
chmodSync(command, 0o644)
|
||||
expect(
|
||||
expect(() =>
|
||||
resolveStructuredAgentCommand('claude', { agentCmdOverrides: { claude: `"${command}"` } })
|
||||
).toBe(resolveCliCommand('claude'))
|
||||
).toThrow(NOT_RUNNABLE)
|
||||
})
|
||||
|
||||
it('keeps the saved value out of the refusal, since a command line can carry a secret', () => {
|
||||
expect(() =>
|
||||
resolveStructuredAgentCommand('codex', {
|
||||
agentCmdOverrides: { codex: 'TOKEN=sk-secret codex' }
|
||||
})
|
||||
).toThrow(expect.not.objectContaining({ message: expect.stringContaining('sk-secret') }))
|
||||
})
|
||||
|
||||
describe('on Windows', () => {
|
||||
function fileIn(directory: string, name: string): string {
|
||||
const file = join(directory, name)
|
||||
writeFileSync(file, '')
|
||||
return file
|
||||
}
|
||||
|
||||
it('refuses an extensionless path, which Windows cannot spawn', () => {
|
||||
const { directory } = executable()
|
||||
const command = fileIn(directory, 'claude')
|
||||
fileIn(directory, 'claude.cmd')
|
||||
expect(() =>
|
||||
resolveStructuredAgentCommand(
|
||||
'claude',
|
||||
{ agentCmdOverrides: { claude: command } },
|
||||
{ platform: 'win32' }
|
||||
)
|
||||
).toThrow(NOT_RUNNABLE)
|
||||
})
|
||||
|
||||
it('refuses a name whose only match on PATH is extensionless', () => {
|
||||
const { directory } = executable()
|
||||
fileIn(directory, 'my-claude')
|
||||
expect(() =>
|
||||
resolveStructuredAgentCommand(
|
||||
'claude',
|
||||
{ agentCmdOverrides: { claude: 'my-claude' } },
|
||||
{ platform: 'win32', pathEnv: directory, homePath: directory }
|
||||
)
|
||||
).toThrow(NOT_RUNNABLE)
|
||||
})
|
||||
|
||||
it.each(['claude.exe', 'claude.com', 'claude.cmd', 'claude.bat'])(
|
||||
'runs an explicit %s path',
|
||||
(name) => {
|
||||
const { directory } = executable()
|
||||
const command = fileIn(directory, name)
|
||||
expect(
|
||||
resolveStructuredAgentCommand(
|
||||
'claude',
|
||||
{ agentCmdOverrides: { claude: `"${command}"` } },
|
||||
{ platform: 'win32' }
|
||||
)
|
||||
).toBe(command)
|
||||
}
|
||||
)
|
||||
|
||||
it('finds the .cmd shim for a bare name, as the stock lookup does', () => {
|
||||
const { directory } = executable()
|
||||
fileIn(directory, 'my-claude')
|
||||
const shim = fileIn(directory, 'my-claude.cmd')
|
||||
expect(
|
||||
resolveStructuredAgentCommand(
|
||||
'claude',
|
||||
{ agentCmdOverrides: { claude: 'my-claude' } },
|
||||
{ platform: 'win32', pathEnv: directory, homePath: directory }
|
||||
)
|
||||
).toBe(shim)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
resolveCliCommand,
|
||||
resolveExecutableCommand
|
||||
} from '../../shared/node-cli-command-resolution'
|
||||
import { structuredAgentCommandToken } from '../../shared/tui-agent-launch-command-override'
|
||||
import {
|
||||
hasExplicitTuiLaunchCommand,
|
||||
structuredAgentCommandToken
|
||||
} from '../../shared/tui-agent-launch-command-override'
|
||||
import { AgentSessionPreSpawnError } from './agent-session-wire/structured-agent-session-adapter'
|
||||
|
||||
type CommandSettings = Partial<Pick<GlobalSettings, 'agentCmdOverrides' | 'agentDefaultEnv'>>
|
||||
type CommandOptions = NonNullable<Parameters<typeof resolveExecutableCommand>[1]>
|
||||
@@ -24,11 +28,28 @@ function resolveOverride(
|
||||
: null
|
||||
}
|
||||
|
||||
/** Re-read the existing setting for every session acquisition and catalog probe. */
|
||||
// Windows starts .exe/.com directly and .cmd/.bat through Orca's shim handling; an extensionless
|
||||
// file would fail to spawn with no word about the setting.
|
||||
function spawnableOn(platform: NodeJS.Platform, command: string): boolean {
|
||||
return platform !== 'win32' || /\.(exe|com|cmd|bat)$/i.test(command)
|
||||
}
|
||||
|
||||
/** Re-read the existing setting for every session acquisition and catalog probe. A set Command
|
||||
* that names no runnable program refuses the start rather than quietly running the stock CLI. */
|
||||
export function resolveStructuredAgentCommand(
|
||||
agent: 'claude' | 'codex',
|
||||
settings: CommandSettings,
|
||||
options: CommandOptions = {}
|
||||
): string {
|
||||
return resolveOverride(agent, settings, options) ?? resolveCliCommand(agent, options)
|
||||
if (!hasExplicitTuiLaunchCommand(settings, agent)) {
|
||||
return resolveCliCommand(agent, options)
|
||||
}
|
||||
const command = resolveOverride(agent, settings, options)
|
||||
if (!command || !spawnableOn(options.platform ?? process.platform, command)) {
|
||||
// The value stays out of the message: a saved command line can carry a secret.
|
||||
throw new AgentSessionPreSpawnError(`the ${agent} Command setting is not a runnable program`, {
|
||||
reason: 'agentCommandNotRunnable'
|
||||
})
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -37,9 +37,13 @@ describe('execution host structured launch settings wiring', () => {
|
||||
settings.agentDefaultArgs = { claude: '--model second', codex: '' }
|
||||
expect(await deps?.resolveLaunchArgs?.('claude')).toEqual(['--model', 'second'])
|
||||
expect(await deps?.resolveLaunchArgs?.('codex')).toEqual([])
|
||||
const notRunnable = expect.objectContaining({ reason: 'agentCommandNotRunnable' })
|
||||
settings.agentCmdOverrides = { claude: 'wrapper --arg' }
|
||||
expect(deps?.resolveClaudeCommand?.()).toBe(resolveCliCommand('claude'))
|
||||
expect(() => deps?.resolveClaudeCommand?.()).toThrow(notRunnable)
|
||||
settings.agentCmdOverrides = { codex: '/missing/codex' }
|
||||
expect(() => deps?.resolveCodexCommand?.()).toThrow(notRunnable)
|
||||
settings.agentCmdOverrides = {}
|
||||
expect(deps?.resolveClaudeCommand?.()).toBe(resolveCliCommand('claude'))
|
||||
expect(deps?.resolveCodexCommand?.()).toBe(resolveCliCommand('codex'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('a structured default this dispatch cannot honour', () => {
|
||||
expect(decide({ params: { agent: 'codex', worktree: 'current' } }).mode).toBe('structured')
|
||||
})
|
||||
|
||||
// A custom launch command applies to terminal launches only; native chat ignores it.
|
||||
// A custom launch command never decides the surface; native chat runs it as the program.
|
||||
it.each([
|
||||
['claude', 'claude-wrapper'],
|
||||
['codex', 'codex-nightly']
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { sayAgentSessionFailureEnglish } from '../../../../shared/agent-session-failure-copy'
|
||||
import { agentSessionFailureSentence } from '../../../../shared/agent-session-failure-words'
|
||||
import { agentSessionRefusalFailure } from '../../../../shared/agent-session-write-failure'
|
||||
import { NativeChatLaunchRetry } from './NativeChatLaunchRetry'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
it('names the Command setting when it is not a program Orca can run', () => {
|
||||
const failure = agentSessionRefusalFailure({
|
||||
code: 'agent_session_operation_invalid',
|
||||
details: { reason: 'agentCommandNotRunnable' }
|
||||
})
|
||||
render(
|
||||
<NativeChatLaunchRetry
|
||||
lifecycle="failed"
|
||||
failure={failure}
|
||||
agentLabel="Claude"
|
||||
onRetry={() => {}}
|
||||
/>
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Chat could not be started. Claude's Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it."
|
||||
)
|
||||
).toBeTruthy()
|
||||
expect(screen.getByRole('button', { name: 'Retry' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('reads naturally where no agent name is known', () => {
|
||||
expect(
|
||||
agentSessionFailureSentence(
|
||||
{ kind: 'agentCommandNotRunnable' },
|
||||
'row',
|
||||
{},
|
||||
sayAgentSessionFailureEnglish
|
||||
)
|
||||
).toBe(
|
||||
"The agent's Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it."
|
||||
)
|
||||
})
|
||||
@@ -108,6 +108,12 @@ const PIECES: Record<AgentSessionFailureCopyId, (values: AgentSessionFailureCopy
|
||||
'components.native-chat.failureWords.launchFolderMissing',
|
||||
COPY.launchFolderMissing
|
||||
),
|
||||
agentCommandNotRunnable: (values) =>
|
||||
translate(
|
||||
'components.native-chat.failureWords.agentCommandNotRunnable',
|
||||
COPY.agentCommandNotRunnable,
|
||||
values
|
||||
),
|
||||
chooseClaudeAccount: () =>
|
||||
translate(
|
||||
'components.native-chat.failureWords.chooseClaudeAccount',
|
||||
|
||||
@@ -127,7 +127,7 @@ export async function discoverNativeChatCatalogModels(
|
||||
const hostCatalogAgent =
|
||||
agent === 'claude' ? ('claude' as const) : agent === 'codex' ? ('codex' as const) : null
|
||||
// Only `local` proves a native pane: a paired runtime's key also covers its SSH/WSL worktrees.
|
||||
// A custom launch command runs a binary the structured catalog never lists; the CLI listing honors it.
|
||||
// Terminal-backed chat runs the full custom command line, which only the CLI listing models.
|
||||
if (
|
||||
hostCatalogAgent &&
|
||||
hostKey === LOCAL_COMMIT_MESSAGE_HOST_KEY &&
|
||||
|
||||
+1
@@ -2796,6 +2796,7 @@
|
||||
"managedAccountEnvOverride": "This Claude launch sets its own Anthropic sign-in variables. Remove them to use a managed Claude account.",
|
||||
"managedAccountUnsupported": "While a Claude account is added in WSL, Claude chats need a Windows Claude account.",
|
||||
"launchFolderMissing": "The folder this chat ran in no longer exists. Restore it to continue this chat.",
|
||||
"agentCommandNotRunnable": "{{agent}}'s Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it.",
|
||||
"noTurnToStop": "{{agent}} had no turn running to stop.",
|
||||
"notDelivered": "This message was not delivered.",
|
||||
"notDeliveredSendAgain": "This message was not delivered. Send it again to continue.",
|
||||
|
||||
@@ -17798,6 +17798,7 @@
|
||||
"accountSwitchInProgress": "A Claude account switch is in progress. Try again after it finishes.",
|
||||
"managedAccountUnsupported": "While a Claude account is added in WSL, Claude chats need a Windows Claude account.",
|
||||
"launchFolderMissing": "The folder this chat ran in no longer exists. Restore it to continue this chat.",
|
||||
"agentCommandNotRunnable": "{{agent}}'s Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it.",
|
||||
"chooseClaudeAccount": "Choose or add one in Claude Accounts settings.",
|
||||
"chooseClaudeAccountThenRunCommand": "Choose or add one in Claude Accounts settings, then run /{{command}} again.",
|
||||
"chooseClaudeAccountThenSend": "Choose or add one in Claude Accounts settings, then send your message again.",
|
||||
|
||||
@@ -17573,6 +17573,7 @@
|
||||
"accountSwitchInProgress": "Hay un cambio de cuenta de Claude en curso. Vuelva a intentarlo cuando termine.",
|
||||
"managedAccountUnsupported": "Mientras haya una cuenta de Claude agregada en WSL, los chats de Claude necesitan una cuenta de Claude de Windows.",
|
||||
"launchFolderMissing": "La carpeta en la que se ejecutaba este chat ya no existe. Restáurela para continuar este chat.",
|
||||
"agentCommandNotRunnable": "{{agent}}: el Comando en Ajustes → Agentes debe ser la ruta o el nombre de un programa que Orca pueda encontrar, sin argumentos ni variables. Cámbielo o restablézcalo con Reiniciar.",
|
||||
"chooseClaudeAccount": "Elija o agregue una en la configuración de Cuentas de Claude.",
|
||||
"chooseClaudeAccountThenRunCommand": "Elija o agregue una en la configuración de Cuentas de Claude y vuelva a ejecutar /{{command}}.",
|
||||
"chooseClaudeAccountThenSend": "Elija o agregue una en la configuración de Cuentas de Claude y vuelva a enviar su mensaje.",
|
||||
|
||||
@@ -17674,6 +17674,7 @@
|
||||
"accountSwitchInProgress": "Un changement de compte Claude est en cours. Réessayez une fois qu'il sera terminé.",
|
||||
"managedAccountUnsupported": "Tant qu'un compte Claude est ajouté dans WSL, les chats Claude nécessitent un compte Claude Windows.",
|
||||
"launchFolderMissing": "Le dossier dans lequel ce chat s'exécutait n'existe plus. Restaurez-le pour poursuivre ce chat.",
|
||||
"agentCommandNotRunnable": "{{agent}} : la Commande dans Paramètres → Agents doit être le chemin ou le nom d'un programme qu'Orca peut trouver, sans arguments ni variables. Modifiez-la ou réinitialisez-la.",
|
||||
"chooseClaudeAccount": "Choisissez-en un ou ajoutez-en un dans les paramètres Comptes Claude.",
|
||||
"chooseClaudeAccountThenRunCommand": "Choisissez-en un ou ajoutez-en un dans les paramètres Comptes Claude, puis relancez /{{command}}.",
|
||||
"chooseClaudeAccountThenSend": "Choisissez-en un ou ajoutez-en un dans les paramètres Comptes Claude, puis renvoyez votre message.",
|
||||
|
||||
@@ -17608,6 +17608,7 @@
|
||||
"accountSwitchInProgress": "Claude アカウントの切り替え中です。完了してからもう一度お試しください。",
|
||||
"managedAccountUnsupported": "WSL に Claude アカウントが追加されている間、Claude チャットには Windows の Claude アカウントが必要です。",
|
||||
"launchFolderMissing": "このチャットが実行されていたフォルダーは存在しなくなりました。このチャットを続けるには、フォルダーを復元してください。",
|
||||
"agentCommandNotRunnable": "[設定] → [Agent] にある {{agent}} の [コマンド] には、Orca が見つけられるプログラムのパスまたは名前を、引数や変数なしで指定する必要があります。変更するか、[リセット] してください。",
|
||||
"chooseClaudeAccount": "Claude アカウントの設定で選択または追加してください。",
|
||||
"chooseClaudeAccountThenRunCommand": "Claude アカウントの設定で選択または追加してから、/{{command}} をもう一度実行してください。",
|
||||
"chooseClaudeAccountThenSend": "Claude アカウントの設定で選択または追加してから、メッセージをもう一度送信してください。",
|
||||
|
||||
@@ -17608,6 +17608,7 @@
|
||||
"accountSwitchInProgress": "Claude 계정 전환이 진행 중입니다. 완료된 후 다시 시도하세요.",
|
||||
"managedAccountUnsupported": "WSL에 Claude 계정이 추가되어 있는 동안에는 Claude 채팅에 Windows Claude 계정이 필요합니다.",
|
||||
"launchFolderMissing": "이 채팅이 실행되던 폴더가 더 이상 존재하지 않습니다. 이 채팅을 계속하려면 폴더를 복원하세요.",
|
||||
"agentCommandNotRunnable": "{{agent}}의 설정 → 에이전트 → 명령은 Orca가 찾을 수 있는 프로그램 경로나 이름이어야 하며, 인수나 변수를 포함할 수 없습니다. 변경하거나 재설정하세요.",
|
||||
"chooseClaudeAccount": "Claude 계정 설정에서 선택하거나 추가하세요.",
|
||||
"chooseClaudeAccountThenRunCommand": "Claude 계정 설정에서 선택하거나 추가한 다음 /{{command}}을(를) 다시 실행하세요.",
|
||||
"chooseClaudeAccountThenSend": "Claude 계정 설정에서 선택하거나 추가한 다음 메시지를 다시 보내세요.",
|
||||
|
||||
@@ -17573,6 +17573,7 @@
|
||||
"accountSwitchInProgress": "正在切换 Claude 账户。请在切换完成后重试。",
|
||||
"managedAccountUnsupported": "当 WSL 中添加了 Claude 账户时,Claude 聊天需要 Windows Claude 账户。",
|
||||
"launchFolderMissing": "此聊天运行所在的文件夹已不存在。请恢复该文件夹以继续此聊天。",
|
||||
"agentCommandNotRunnable": "{{agent}} 在设置 → 智能体中的命令必须是 Orca 能找到的程序路径或名称,且不能包含参数或变量。请修改或重置。",
|
||||
"chooseClaudeAccount": "请在 Claude 账户设置中选择或添加一个。",
|
||||
"chooseClaudeAccountThenRunCommand": "请在 Claude 账户设置中选择或添加一个,然后重新运行 /{{command}}。",
|
||||
"chooseClaudeAccountThenSend": "请在 Claude 账户设置中选择或添加一个,然后重新发送您的消息。",
|
||||
|
||||
@@ -415,7 +415,7 @@ describe('buildAgentLaunchRouteInput', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// A launch command override applies to terminal launches only, here or on the server.
|
||||
// A launch command override never decides the surface, here or on the server.
|
||||
it("does not route on this machine's launch command override for the server", () => {
|
||||
const settings = { ...STRUCTURED_SETTINGS, agentCmdOverrides: { claude: 'claude-wrapper' } }
|
||||
expect(
|
||||
|
||||
@@ -37,6 +37,8 @@ export const AGENT_SESSION_FAILURE_COPY = {
|
||||
'While a Claude account is added in WSL, Claude chats need a Windows Claude account.',
|
||||
launchFolderMissing:
|
||||
'The folder this chat ran in no longer exists. Restore it to continue this chat.',
|
||||
agentCommandNotRunnable:
|
||||
"{{agent}}'s Command in Settings → Agents must be a program path or name Orca can find, with no arguments or variables. Change it or reset it.",
|
||||
chooseClaudeAccount: 'Choose or add one in Claude Accounts settings.',
|
||||
chooseClaudeAccountThenRunCommand:
|
||||
'Choose or add one in Claude Accounts settings, then run /{{command}} again.',
|
||||
|
||||
@@ -220,6 +220,8 @@ const FAILURE_SENTENCES = {
|
||||
managedAccountEnvOverride: (_context, _fact, _surface, say) => say('managedAccountEnvOverride'),
|
||||
accountSwitchInProgress: (_context, _fact, _surface, say) => say('accountSwitchInProgress'),
|
||||
launchFolderMissing: (_context, _fact, _surface, say) => say('launchFolderMissing'),
|
||||
agentCommandNotRunnable: (context, _fact, _surface, say) =>
|
||||
say('agentCommandNotRunnable', agent(say, context)),
|
||||
managedAccountUnsupported: (context, _fact, _surface, say) =>
|
||||
joinSentences([
|
||||
say('managedAccountUnsupported'),
|
||||
|
||||
@@ -30,6 +30,7 @@ export const AGENT_SESSION_FAILURE_KINDS = [
|
||||
'accountSwitchInProgress',
|
||||
'managedAccountUnsupported',
|
||||
'launchFolderMissing',
|
||||
'agentCommandNotRunnable',
|
||||
'providerExited',
|
||||
'restartFailed',
|
||||
'providerRejected',
|
||||
|
||||
@@ -57,6 +57,8 @@ export const AGENT_SESSION_REFUSAL_REASONS = {
|
||||
'managedAccountUnsupported',
|
||||
/** A floating chat resumes only in the folder it ran in, and that folder is gone. */
|
||||
'launchFolderMissing',
|
||||
/** Settings → Agents → Command names no program this host can run. */
|
||||
'agentCommandNotRunnable',
|
||||
/** The agent started, then Orca could not open the chat's conversation for it. */
|
||||
'attachFailed'
|
||||
],
|
||||
|
||||
@@ -97,6 +97,7 @@ const REASON_WORDS = {
|
||||
accountSwitchInProgress: { fact: 'accountSwitchInProgress', action: 'wait' },
|
||||
managedAccountUnsupported: { fact: 'managedAccountUnsupported', action: 'actFirst' },
|
||||
launchFolderMissing: { fact: 'launchFolderMissing', action: 'actFirst' },
|
||||
agentCommandNotRunnable: { fact: 'agentCommandNotRunnable', action: 'actFirst' },
|
||||
attachFailed: codeWords('retry')
|
||||
},
|
||||
agent_session_ownership_unknown: {
|
||||
|
||||
@@ -91,6 +91,7 @@ const KIND_CATEGORY = {
|
||||
accountSwitchInProgress: 'startFailed',
|
||||
managedAccountUnsupported: 'startFailed',
|
||||
launchFolderMissing: 'startFailed',
|
||||
agentCommandNotRunnable: 'startFailed',
|
||||
restartFailed: 'startFailed',
|
||||
providerRejected: 'content',
|
||||
attachmentInvalid: 'content',
|
||||
@@ -117,6 +118,7 @@ const KIND_VERDICT = {
|
||||
accountSwitchInProgress: 'failure',
|
||||
managedAccountUnsupported: 'failure',
|
||||
launchFolderMissing: 'failure',
|
||||
agentCommandNotRunnable: 'failure',
|
||||
restartFailed: 'failure',
|
||||
providerRejected: 'failure',
|
||||
attachmentInvalid: 'failure',
|
||||
|
||||
@@ -30,7 +30,7 @@ export type StructuredNativeChatBlocker =
|
||||
| 'reused-terminal'
|
||||
| 'agent-without-structured-session'
|
||||
/** The launch names a start directory outside its workspace, which only a PTY can apply. The
|
||||
* configured launch command and arguments are not read: they apply to terminal launches only. */
|
||||
* configured launch command and arguments never decide the surface. */
|
||||
| 'custom-start-directory'
|
||||
| 'remote-execution-host'
|
||||
| 'project-runtime'
|
||||
|
||||
Reference in New Issue
Block a user