fix(workspaces): validate remote launch option overrides

This commit is contained in:
Jinwoo-H
2026-08-09 23:25:07 -04:00
parent acd8425fe1
commit 1da44f4c59
7 changed files with 103 additions and 18 deletions
+11 -4
View File
@@ -10279,7 +10279,7 @@
"oracle": "Send a linked-task draft and session options through the real worktree.create schema and handler. Require Git, nested SSH, and folder hosts to emit the selected model and effort in the exact startup command, return the actual applied subset with the startup-terminal receipt, and publish no client-authored command. Feed that receipt through Tasks completion and activation; require the host tab to own draft/session state, zero fallback or wake creation, and no applied cache entry when a legacy receipt omits acknowledgement. On a physical paired Windows host, compare authoritative PTY inventory before and after the fix and require Codex plus Setup only, a running Codex process, the unsent issue draft, completed Setup, and no ConPTY DA1 text.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/shared/tui-agent-startup-session-options.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/schemas.test.ts src/main/runtime/rpc/methods/worktree.test.ts src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/lib/worktree-creation-flow.test.ts src/renderer/src/lib/worktree-creation-flow-startup.test.ts src/renderer/src/lib/worktree-creation-agent-seeds.test.ts src/renderer/src/lib/worktree-activation.test.ts",
"Tasks -> GitHub issue -> Codex -> windows 2 paired host"
"Tasks -> GitHub issue #13320 -> create on Windows 2 paired host with Codex"
],
"testFiles": [
"src/shared/tui-agent-startup-session-options.test.ts",
@@ -10306,7 +10306,14 @@
{
"file": "src/shared/tui-agent-startup-session-options.test.ts",
"assertions": [
"option values with the wrong catalog kind are neither encoded nor acknowledged"
"option values with the wrong catalog kind are neither encoded nor acknowledged",
"invalid requested replacements preserve valid host-configured agent arguments"
]
},
{
"file": "src/main/runtime/rpc/schemas.test.ts",
"assertions": [
"draft session options cannot be combined with a competing startup agent or command"
]
},
{
@@ -10351,14 +10358,14 @@
"result": "passed",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/shared/tui-agent-startup-session-options.test.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/schemas.test.ts src/main/runtime/rpc/methods/worktree.test.ts src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/lib/worktree-creation-flow.test.ts src/renderer/src/lib/worktree-creation-flow-startup.test.ts src/renderer/src/lib/worktree-creation-agent-seeds.test.ts src/renderer/src/lib/worktree-activation.test.ts",
"durationSeconds": 12,
"summary": "1,516 deterministic renderer, RPC, Git, SSH, folder, launch-command, compatibility, and activation tests passed; one unrelated test was skipped."
"summary": "1,518 deterministic renderer, RPC, Git, SSH, folder, launch-command, compatibility, and activation tests passed; one unrelated test was skipped."
},
{
"date": "2026-08-09",
"runner": "manual",
"platform": "windows",
"result": "passed",
"command": "Tasks -> GitHub issue -> Codex -> windows 2 paired host",
"command": "Tasks -> GitHub issue #13320 -> create on Windows 2 paired host with Codex",
"durationSeconds": 60,
"summary": "Baseline had two blank shells plus Setup and no Codex. Candidate had Codex plus Setup only, Codex running with the issue URL drafted, Setup complete, and no DA1 text."
}
+29
View File
@@ -42672,6 +42672,35 @@ describe('OrcaRuntimeService', () => {
expect(addWorktree).not.toHaveBeenCalled()
})
it('rejects startup draft options combined with another startup command', async () => {
const runtime = new OrcaRuntimeService(store as never)
const startupSessionOptions = {
agent: 'codex' as const,
values: { model: 'gpt-5.6-sol', effort: 'high' }
}
await expect(
runtime.createManagedWorktree({
repoSelector: TEST_REPO_ID,
name: 'ambiguous-startup',
startupAgent: 'codex',
startupDraft: 'https://github.com/stablyai/orca/issues/123',
startupSessionOptions
})
).rejects.toThrow('Startup session options require a draft without another startup command.')
await expect(
runtime.createManagedWorktree({
repoSelector: TEST_REPO_ID,
name: 'ambiguous-startup',
startup: { command: 'codex' },
startupDraft: 'https://github.com/stablyai/orca/issues/123',
startupSessionOptions
})
).rejects.toThrow('Startup session options require a draft without another startup command.')
expect(addWorktree).not.toHaveBeenCalled()
})
it('launches explicit startup agents with prompts for CLI-created worktrees', async () => {
const metaById: Record<string, WorktreeMeta> = {}
const runtimeStore = {
+3
View File
@@ -21735,6 +21735,9 @@ export class OrcaRuntimeService {
if (!this.store) {
throw new Error('runtime_unavailable')
}
if (args.startupSessionOptions && (!args.startupDraft || args.startupAgent || args.startup)) {
throw new Error('Startup session options require a draft without another startup command.')
}
const repo = await this.resolveRepoSelector(args.repoSelector)
const createSettings = this.store.getSettings()
@@ -212,11 +212,16 @@ export const WorktreeCreate = z
message: 'startupPrompt requires startupAgent'
})
}
if (params.startupSessionOptions !== undefined && params.startupDraft === undefined) {
if (
params.startupSessionOptions !== undefined &&
(params.startupDraft === undefined ||
params.startupAgent !== undefined ||
params.startupCommand !== undefined)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['startupSessionOptions'],
message: 'startupSessionOptions requires startupDraft'
message: 'startupSessionOptions requires startupDraft without an explicit startup command'
})
}
})
+12
View File
@@ -205,6 +205,18 @@ describe('RPC optional pipe schemas', () => {
repo: 'id:repo-github',
startupSessionOptions: { agent: 'codex', values: { model: 'gpt-5.6-sol' } }
})
expectRejects(create, {
repo: 'id:repo-github',
startupAgent: 'codex',
startupDraft: 'https://github.com/acme/app/pull/123',
startupSessionOptions: { agent: 'codex', values: { model: 'gpt-5.6-sol' } }
})
expectRejects(create, {
repo: 'id:repo-github',
startupCommand: 'codex',
startupDraft: 'https://github.com/acme/app/pull/123',
startupSessionOptions: { agent: 'codex', values: { model: 'gpt-5.6-sol' } }
})
expectRejects(create, {
repo: 'id:repo-github',
startupDraft: 'https://github.com/acme/app/pull/123',
+22 -12
View File
@@ -1,5 +1,6 @@
import type { AgentType } from './agent-status-types'
import { findCatalogModel, getAgentSessionOptionCatalog } from './agent-session-option-catalog'
import type { CatalogOption } from './agent-session-option-catalog-types'
import type { SessionOptionValue } from './native-chat-session-options'
export type ResolvedSessionOptionLaunch = {
@@ -21,7 +22,12 @@ export function removeOverriddenAgentSessionArgs(
const model = findCatalogModel(catalog, modelId)
const modelOptions = model?.options ?? catalog.unknownModelOptions ?? []
for (const option of modelOptions) {
if (values[option.id] !== undefined && option.apply.removeAgentArgs) {
const explicitValue = values[option.id]
if (
explicitValue !== undefined &&
optionValueIsValid(option, explicitValue, Boolean(model)) &&
option.apply.removeAgentArgs
) {
result = option.apply.removeAgentArgs(result)
}
}
@@ -48,17 +54,7 @@ export function resolveAgentSessionOptionLaunch(
modelOptions.flatMap((option) => {
const explicitValue = values[option.id]
if (explicitValue !== undefined) {
if (
(option.kind.type === 'boolean' && typeof explicitValue !== 'boolean') ||
(option.kind.type === 'select' && typeof explicitValue !== 'string')
) {
return []
}
if (
!model &&
option.kind.type === 'select' &&
!option.kind.choices.some((choice) => choice.value === explicitValue)
) {
if (!optionValueIsValid(option, explicitValue, Boolean(model))) {
return []
}
return [[option.id, explicitValue]]
@@ -98,3 +94,17 @@ export function resolveAgentSessionOptionLaunch(
}
return { args, appliedValues }
}
function optionValueIsValid(
option: CatalogOption,
value: SessionOptionValue,
hasKnownModel: boolean
): boolean {
if (option.kind.type === 'boolean') {
return typeof value === 'boolean'
}
return (
typeof value === 'string' &&
(hasKnownModel || option.kind.choices.some((choice) => choice.value === value))
)
}
@@ -201,4 +201,23 @@ describe('tui agent startup session options', () => {
expect(resolved.appliedSessionOptions).toEqual({ model: 'gpt-5.3-codex' })
}
})
it('preserves configured arguments when their requested replacement has the wrong type', () => {
const plan = buildAgentStartupPlan({
agent: 'codex',
prompt: '',
cmdOverrides: {},
platform: 'linux',
allowEmptyPromptLaunch: true,
sessionOptions: { model: 'gpt-5.6-sol', effort: true },
sessionOptionsOverrideAgentArgs: true,
agentArgs:
'-m host-model -c model_reasoning_effort=low --dangerously-bypass-approvals-and-sandbox'
})
expect(plan?.launchCommand).toBe(
"codex '-c' 'model_reasoning_effort=low' '--dangerously-bypass-approvals-and-sandbox' '-m' 'gpt-5.6-sol'"
)
expect(plan?.sessionOptions).toEqual({ model: 'gpt-5.6-sol' })
})
})