mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix: prevent setup command truncation during shell startup (#25820)
* fix: keep setup command short so it survives shell startup The POSIX setup wrapper inlined the runner path six times. On long worktree paths it exceeded macOS's 1024-byte canonical PTY input limit when typed while the shell was still sourcing rc files, so the tail and submit byte were dropped, setup never ran, and a gated agent waited forever. Carry the setup script in ORCA_SEQUENCED_SETUP_SCRIPT and submit only `bash -lc 'eval "$ORCA_SEQUENCED_SETUP_SCRIPT"'`, mirroring the existing startup gate. The env is merged into setup.envVars wherever the gate is built so provisioning, fallback retries and renderer tabs/splits all receive it, and WSL imports it untranslated. Closes #20935 * fix(wsl): forward the setup script WSLENV entry before the daemon boundary Same-protocol daemons from older builds rebuild WSLENV without ORCA_SEQUENCED_SETUP_SCRIPT, so WSL setup would eval an empty script and the gated agent would wait for its timeout. Also swap new spec casts for the spawnedPtyEnv fixture. * fix: fail loudly when the sequenced setup script env is missing A dropped ORCA_SEQUENCED_SETUP_SCRIPT made eval run an empty string and exit 0, silently skipping setup. ${VAR:?missing} reports it in the Setup pane. --------- Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -593,6 +593,13 @@ describe('registerPtyHandlers', () => {
|
||||
expect(env.GIT_CONFIG_KEY_0).toBe('http.proxy')
|
||||
expect(env.GIT_CONFIG_KEY_1).toBeUndefined()
|
||||
})
|
||||
it('forwards the sequenced setup script into WSLENV before the daemon boundary', async () => {
|
||||
const env = await withWin32Platform(() =>
|
||||
daemonSpawnAndGetEnv({ ORCA_SEQUENCED_SETUP_SCRIPT: 'true', WSLENV: 'EXISTING/p' })
|
||||
)
|
||||
|
||||
expect(env.WSLENV).toBe('EXISTING/p:ORCA_SEQUENCED_SETUP_SCRIPT')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,8 +3,10 @@ import type { AgentProviderSessionMetadata } from '../../../shared/agent-session
|
||||
import { getAppEnvironment } from '../../../shared/app-environment'
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
import { stampRuntimeSourceEnv } from '../../../shared/runtime-source-env'
|
||||
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
|
||||
import type { TuiAgent } from '../../../shared/tui-agent'
|
||||
import { isTuiAgent } from '../../../shared/tui-agent-config'
|
||||
import { addWslEnvKeys } from '../../../shared/wsl-env'
|
||||
import { isAgentStatusHooksEnabled } from '../../agent-hooks/managed-agent-hook-controls'
|
||||
import { awaitExplicitAgentPluginGuestReadiness } from '../../agent-hooks/wsl-agent-plugin-readiness'
|
||||
import { ensureCodexStateDbBackfillRecoveryStarted } from '../../codex/codex-state-db-backfill-recovery'
|
||||
@@ -245,6 +247,10 @@ export async function resolvePtySpawnHostEnv(args: PtySpawnHostEnvArgs): Promise
|
||||
getRuntimeSourceStamp(deps.runtime, getAppEnvironment().getPath('userData'))
|
||||
)
|
||||
promoteAgentTeamsShimPath(env, args.requestedAgentTeamsPath)
|
||||
if (process.platform === 'win32' && env[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]) {
|
||||
// Why: same-protocol daemons from older builds don't add this WSLENV entry themselves.
|
||||
addWslEnvKeys(env, [SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV])
|
||||
}
|
||||
} catch (err) {
|
||||
// Host prep creates hook/extension state; only this request's minted ID is safe to sweep.
|
||||
if (args.mintedSessionId) {
|
||||
|
||||
@@ -317,7 +317,8 @@ async function spawnLocalStartupAndSetupTerminals(args: {
|
||||
defaultTabs: CreateWorktreeResult['defaultTabs']
|
||||
createdWithAgent: CreateWorktreeArgs['createdWithAgent']
|
||||
}): Promise<StagedStartupResult> {
|
||||
const { runtime, worktree, startup, setup, defaultTabs, createdWithAgent } = args
|
||||
const { runtime, worktree, startup, defaultTabs, createdWithAgent } = args
|
||||
let { setup } = args
|
||||
if (!startup || defaultTabs?.tabs.length) {
|
||||
return { didSpawnSetup: false }
|
||||
}
|
||||
@@ -345,6 +346,7 @@ async function spawnLocalStartupAndSetupTerminals(args: {
|
||||
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
|
||||
}
|
||||
wrappedSetupCommandStr = sequenced.setupCommand
|
||||
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resolve } from 'node:path'
|
||||
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../shared/setup-agent-sequencing'
|
||||
import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
|
||||
import { resolveRegisteredWorktreePath } from './registered-worktree-roots-cache'
|
||||
import { computeWorkspaceRootAsync } from './worktree-logic'
|
||||
import type * as WorktreeLogic from './worktree-logic'
|
||||
@@ -779,15 +781,17 @@ describe('registerWorktreeHandlers', () => {
|
||||
request_kind: 'new'
|
||||
}
|
||||
}
|
||||
})) as { setup?: { command?: string; runnerScriptPath: string } }
|
||||
})) as { setup?: CreateWorktreeResult['setup'] }
|
||||
|
||||
expect(result.setup).toEqual(
|
||||
expect.objectContaining({
|
||||
runnerScriptPath: 'C:\\workspace\\repo\\.git\\orca\\setup-runner.sh',
|
||||
command: expect.stringContaining('bash /mnt/c/workspace/repo/.git/orca/setup-runner.sh')
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`
|
||||
})
|
||||
)
|
||||
expect(result.setup?.command).toContain('printf')
|
||||
expect(result.setup?.envVars[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]).toContain(
|
||||
'bash /mnt/c/workspace/repo/.git/orca/setup-runner.sh'
|
||||
)
|
||||
})
|
||||
|
||||
it('opens Setup beside the startup terminal without surfacing the workspace', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import { isAbsolute } from 'node:path'
|
||||
import { getShellReadyWrapperRoot } from '../providers/local-pty-shell-ready-wrapper-root'
|
||||
import {
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
|
||||
} from '../../shared/setup-agent-sequencing'
|
||||
@@ -46,10 +47,11 @@ describe('addOrcaWslInteropEnv', () => {
|
||||
expect(env.WSLENV?.split(':')).toContain('ORCA_SHELL_READY_ROOT/p')
|
||||
})
|
||||
|
||||
it('imports setup-gated startup env into WSL without path translation', () => {
|
||||
it('imports setup-gated startup and setup env into WSL without path translation', () => {
|
||||
const env: Record<string, string> = {
|
||||
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: 'codex',
|
||||
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: 'while :; do sleep 1; done'
|
||||
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: 'while :; do sleep 1; done',
|
||||
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: '( bash /repo/setup-runner.sh )'
|
||||
}
|
||||
|
||||
addOrcaWslInteropEnv(env)
|
||||
@@ -57,7 +59,8 @@ describe('addOrcaWslInteropEnv', () => {
|
||||
expect(env.WSLENV?.split(':')).toEqual([
|
||||
'ORCA_SHELL_READY_ROOT/p',
|
||||
`${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}/u`,
|
||||
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`
|
||||
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`,
|
||||
`${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}/u`
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ORCHESTRATION_COMPATIBILITY_HOST_KIND_ENV
|
||||
} from '../../shared/orchestration-compatibility-evidence'
|
||||
import {
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
|
||||
} from '../../shared/setup-agent-sequencing'
|
||||
@@ -98,6 +99,7 @@ export function addOrcaWslInteropEnv(env: Record<string, string>): void {
|
||||
'ORCA_OPENCODE_AGENT/u',
|
||||
`${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}/u`,
|
||||
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`,
|
||||
`${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}/u`,
|
||||
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_KIND/u',
|
||||
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_ID/u',
|
||||
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_INCARNATION/u',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getDefaultWorkspaceSession } from '../../../shared/constants'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
|
||||
import {
|
||||
OrcaRuntimeService,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV,
|
||||
@@ -115,15 +116,19 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
const startupCommand = startup.command
|
||||
const startupScript = startup.env[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]!
|
||||
const setupCommand = (spawn.mock.calls[1]![0] as { command: string }).command
|
||||
const setupScript = spawnedPtyEnv(spawn.mock.calls[1]![0])[
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV
|
||||
]
|
||||
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
|
||||
expect(nonceMatch?.[1]).toBeTruthy()
|
||||
expect(startupCommand.length).toBeLessThan(256)
|
||||
expect(startupScript).toContain('exec claude')
|
||||
expect(startupScript).toContain('/mnt/c/tmp/repo/.git/orca/setup-runner.sh')
|
||||
expect(setupCommand).toContain('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
|
||||
expect(setupCommand).toContain('printf')
|
||||
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
|
||||
expect(spawn.mock.calls[1]![0]).toMatchObject({
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`
|
||||
})
|
||||
expect(setupScript).toContain('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
|
||||
expect(setupScript).toContain(`${nonceMatch![1]} "$status"`)
|
||||
expect(result.setup).toBeUndefined()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { RuntimeClientEvent } from '../../../shared/runtime-client-events'
|
||||
import { getDefaultWorkspaceSession } from '../../../shared/constants'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
|
||||
import {
|
||||
OrcaRuntimeService,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
|
||||
@@ -334,10 +335,13 @@ describe('OrcaRuntimeService', () => {
|
||||
2,
|
||||
expect.objectContaining({
|
||||
cwd: '/tmp/workspaces/runtime-startup-setup-split',
|
||||
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh'),
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
|
||||
env: expect.objectContaining({
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
ORCA_WORKTREE_PATH: '/tmp/workspaces/runtime-startup-setup-split'
|
||||
ORCA_WORKTREE_PATH: '/tmp/workspaces/runtime-startup-setup-split',
|
||||
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
|
||||
'bash /tmp/repo/.git/orca/setup-runner.sh'
|
||||
)
|
||||
}),
|
||||
worktreeId: result.worktree.id
|
||||
})
|
||||
@@ -348,16 +352,17 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
const startupCommand = startup.command
|
||||
const startupScript = startup.env[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]!
|
||||
const setupCommand = (spawn.mock.calls[1]![0] as { command: string }).command
|
||||
const setupScript = spawnedPtyEnv(spawn.mock.calls[1]![0])[
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV
|
||||
]
|
||||
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
|
||||
expect(nonceMatch?.[1]).toBeTruthy()
|
||||
const markerPath = `/tmp/repo/.git/orca/setup-runner.sh.${nonceMatch![1]}.done`
|
||||
expect(startupCommand.length).toBeLessThan(256)
|
||||
expect(startupScript).toContain('--dangerously-bypass-approvals-and-sandbox')
|
||||
expect(setupCommand).toContain('printf')
|
||||
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
|
||||
expect(setupScript).toContain(`${nonceMatch![1]} "$status"`)
|
||||
expect(startupScript).toContain(markerPath)
|
||||
expect(setupCommand).toContain(markerPath)
|
||||
expect(setupScript).toContain(markerPath)
|
||||
const mainEnv = spawnedPtyEnv(spawn.mock.calls[0]![0])
|
||||
const setupEnv = spawnedPtyEnv(spawn.mock.calls[1]![0])
|
||||
expect(result.setup).toBeUndefined()
|
||||
@@ -473,7 +478,12 @@ describe('OrcaRuntimeService', () => {
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
|
||||
command: expect.stringContaining('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
|
||||
envVars: expect.objectContaining({
|
||||
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
|
||||
'bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh'
|
||||
)
|
||||
})
|
||||
}),
|
||||
undefined,
|
||||
undefined
|
||||
@@ -483,8 +493,6 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(
|
||||
events.filter((event) => event.type === 'activateWorktree' && event.setup !== undefined)
|
||||
).toEqual([])
|
||||
const activationSetup = activateWorktree.mock.calls[0]?.[2] as { command?: string } | undefined
|
||||
expect(activationSetup?.command).toContain('printf')
|
||||
})
|
||||
|
||||
it('lets explicit startup draft agents override the desktop default', async () => {
|
||||
|
||||
@@ -72,7 +72,8 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
|
||||
warning?: string
|
||||
ports: Ports
|
||||
}): Promise<RuntimeLocalWorktreeTerminalStartupResult> {
|
||||
const { request, repo, worktree, setup, defaultTabs, startup, ports } = args
|
||||
const { request, repo, worktree, defaultTabs, startup, ports } = args
|
||||
let { setup } = args
|
||||
const shouldActivate = request.activate === true || request.runHooks === true
|
||||
let warning = args.warning
|
||||
let didSpawnStartup = false
|
||||
@@ -98,6 +99,7 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
|
||||
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
|
||||
}
|
||||
wrappedSetupCommand = sequenced.setupCommand
|
||||
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
|
||||
}
|
||||
|
||||
// Main owns failed agent startup too, keeping its reason visible without a terminal service.
|
||||
@@ -162,7 +164,12 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
|
||||
Boolean(setup || defaultTabs))
|
||||
if (runtimeWillProvision) {
|
||||
const provisioned = await ports.provision({
|
||||
...provisionArgs(args, startupTerminalHandle, startupPaneHeld, wrappedSetupCommand),
|
||||
...provisionArgs(
|
||||
{ ...args, setup },
|
||||
startupTerminalHandle,
|
||||
startupPaneHeld,
|
||||
wrappedSetupCommand
|
||||
),
|
||||
...(provisionInBackground ? { surfaceOwner: false as const } : {})
|
||||
})
|
||||
didSpawnSetup = provisioned.setupSpawned
|
||||
@@ -186,7 +193,12 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
|
||||
}
|
||||
} else if (ports.canSpawn && (setup || defaultTabs || didSpawnStartup)) {
|
||||
const provisioning = ports.provision({
|
||||
...provisionArgs(args, startupTerminalHandle, startupPaneHeld, wrappedSetupCommand),
|
||||
...provisionArgs(
|
||||
{ ...args, setup },
|
||||
startupTerminalHandle,
|
||||
startupPaneHeld,
|
||||
wrappedSetupCommand
|
||||
),
|
||||
surfaceOwner: false
|
||||
})
|
||||
if (request.awaitTerminalProvisioning) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
|
||||
import {
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
|
||||
} from '../../../shared/setup-agent-sequencing'
|
||||
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
|
||||
import {
|
||||
createMockStore,
|
||||
@@ -10,6 +13,13 @@ import {
|
||||
|
||||
registerWorktreeActivationReset()
|
||||
|
||||
function sequencedSetup(env: Record<string, string>, runnerCommand: string): unknown {
|
||||
return {
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
|
||||
env: { ...env, [SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(runnerCommand) }
|
||||
}
|
||||
}
|
||||
|
||||
describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
it('creates a background Setup tab for newly created worktrees by default', () => {
|
||||
let createdIndex = 0
|
||||
@@ -112,14 +122,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
expect(result).toBe('tab-1')
|
||||
expect(createTab).toHaveBeenCalledTimes(1)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('printf')
|
||||
})
|
||||
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
)
|
||||
})
|
||||
|
||||
@@ -176,9 +179,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
})
|
||||
})
|
||||
)
|
||||
expect(startups['tab-3']).toEqual(
|
||||
expect.objectContaining({ command: expect.stringContaining('bash /tmp/setup.sh') })
|
||||
)
|
||||
expect(startups['tab-3']).toEqual(sequencedSetup({}, 'bash /tmp/setup.sh'))
|
||||
if (!activateCreatedTabs) {
|
||||
expect(store.setActiveTab).not.toHaveBeenCalled()
|
||||
}
|
||||
@@ -219,14 +220,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('printf')
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
})
|
||||
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
)
|
||||
})
|
||||
|
||||
@@ -277,14 +271,9 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
})
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual({
|
||||
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh'),
|
||||
env: { ORCA_ROOT_PATH: '/tmp/repo' }
|
||||
})
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual({
|
||||
command: expect.stringContaining('printf'),
|
||||
env: { ORCA_ROOT_PATH: '/tmp/repo' }
|
||||
})
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps WSL setup shell metadata when gating startup behind setup completion', () => {
|
||||
@@ -312,10 +301,9 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
})
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual({
|
||||
command: expect.stringContaining('bash /mnt/c/repo/.git/orca/setup-runner.sh'),
|
||||
env: { ORCA_ROOT_PATH: 'C:\\repo' }
|
||||
})
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
sequencedSetup({ ORCA_ROOT_PATH: 'C:\\repo' }, 'bash /mnt/c/repo/.git/orca/setup-runner.sh')
|
||||
)
|
||||
})
|
||||
|
||||
it.each(['split-vertical', 'split-horizontal', 'new-tab', null] as const)(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
|
||||
import { activateAndRevealFolderWorkspace, activateAndRevealWorktree } from './worktree-activation'
|
||||
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
|
||||
import type { AppStoreState } from './worktree-activation.test-fixture'
|
||||
@@ -300,16 +301,15 @@ describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
|
||||
recordInteraction: false
|
||||
})
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual(
|
||||
expect.objectContaining({
|
||||
command: expect.stringContaining('printf')
|
||||
})
|
||||
)
|
||||
expect(createdTabStartups(store)['tab-2']).toEqual({
|
||||
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
|
||||
env: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
|
||||
'bash /tmp/repo/.git/orca/setup-runner.sh'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('holds the issue command for the first mirrored web runtime tab when none exists yet', () => {
|
||||
|
||||
@@ -129,6 +129,7 @@ export function ensureWorktreeHasInitialTerminal(
|
||||
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
|
||||
}
|
||||
wrappedSetupCommandStr = sequenced.setupCommand
|
||||
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
|
||||
}
|
||||
|
||||
const backendStartupTerminalSpawned = opts?.backendStartupTerminalSpawned === true
|
||||
|
||||
@@ -8,7 +8,9 @@ import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createSequencedSetupAgentCommands,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV,
|
||||
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
|
||||
type SequencedSetupAgentCommands
|
||||
} from './setup-agent-sequencing'
|
||||
|
||||
const TEMP_DIRS: string[] = []
|
||||
@@ -29,6 +31,9 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
nonce: 'long-path'
|
||||
})
|
||||
|
||||
expect(Buffer.byteLength(result.setupCommand)).toBeLessThan(256)
|
||||
expect(result.setupCommand).not.toContain('nested-worktree')
|
||||
expect(result.setupEnv?.[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]).toContain('nested-worktree')
|
||||
expect(result.startupCommand.length).toBeLessThan(256)
|
||||
expect(result.startupCommand).not.toContain('nested-worktree')
|
||||
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
|
||||
@@ -44,7 +49,7 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
nonce: 'nonce-remote'
|
||||
})
|
||||
|
||||
expect(result.setupCommand).toContain(
|
||||
expect(setupScript(result)).toContain(
|
||||
'bash /remote/repo/.git/worktrees/feature/orca/setup-runner.sh'
|
||||
)
|
||||
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
|
||||
@@ -61,8 +66,8 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
nonce: 'nonce-wsl-shell'
|
||||
})
|
||||
|
||||
expect(result.setupCommand).toContain('bash /mnt/c/repo/.git/orca/setup-runner.sh')
|
||||
expect(result.setupCommand).toContain(
|
||||
expect(setupScript(result)).toContain('bash /mnt/c/repo/.git/orca/setup-runner.sh')
|
||||
expect(setupScript(result)).toContain(
|
||||
'/mnt/c/repo/.git/orca/setup-runner.sh.nonce-wsl-shell.done'
|
||||
)
|
||||
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
|
||||
@@ -80,11 +85,11 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
nonce: 'nonce-gitbash-cmd'
|
||||
})
|
||||
|
||||
expect(result.setupCommand).toContain(
|
||||
expect(setupScript(result)).toContain(
|
||||
'powershell.exe -NoProfile -NonInteractive -EncodedCommand'
|
||||
)
|
||||
expect(result.setupCommand).not.toMatch(/bash\s+\S*setup-runner/)
|
||||
expect(decodePowerShellScript(result.setupCommand)).toContain(
|
||||
expect(decodePowerShellScript(setupScript(result))).toContain(
|
||||
"$runner = 'C:\\repo\\.git\\orca\\setup-runner.cmd'"
|
||||
)
|
||||
// PowerShell Invoke-Expression cannot parse POSIX apostrophe escaping; bash must evaluate startup.
|
||||
@@ -95,7 +100,7 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
'eval "$ORCA_SEQUENCED_STARTUP_COMMAND"'
|
||||
)
|
||||
// Why: bash writes and reads the marker here, so it needs the /c/... form of the path.
|
||||
expect(result.setupCommand).toContain(
|
||||
expect(setupScript(result)).toContain(
|
||||
'/c/repo/.git/orca/setup-runner.cmd.nonce-gitbash-cmd.done'
|
||||
)
|
||||
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
|
||||
@@ -198,7 +203,10 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
expect(readFileSync(markerPath, 'utf8')).toBe('stale:0\n')
|
||||
|
||||
const setupExit = await waitForExit(
|
||||
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
|
||||
spawn('bash', ['-lc', commands.setupCommand], {
|
||||
stdio: 'pipe',
|
||||
env: { ...process.env, ...commands.setupEnv }
|
||||
})
|
||||
)
|
||||
expect(setupExit.code).toBe(0)
|
||||
|
||||
@@ -232,7 +240,10 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
})
|
||||
|
||||
const setupExitPromise = waitForExit(
|
||||
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
|
||||
spawn('bash', ['-lc', commands.setupCommand], {
|
||||
stdio: 'pipe',
|
||||
env: { ...process.env, ...commands.setupEnv }
|
||||
})
|
||||
)
|
||||
const startupExit = await waitForExit(
|
||||
spawn('bash', ['-lc', commands.startupCommand], {
|
||||
@@ -282,7 +293,10 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
})
|
||||
|
||||
const setupExitPromise = waitForExit(
|
||||
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
|
||||
spawn('bash', ['-lc', commands.setupCommand], {
|
||||
stdio: 'pipe',
|
||||
env: { ...process.env, ...commands.setupEnv }
|
||||
})
|
||||
)
|
||||
const startupExit = await waitForExit(
|
||||
spawn('bash', ['-lc', commands.startupCommand], {
|
||||
@@ -331,6 +345,10 @@ describe('createSequencedSetupAgentCommands', () => {
|
||||
)
|
||||
})
|
||||
|
||||
function setupScript(commands: SequencedSetupAgentCommands): string {
|
||||
return commands.setupEnv?.[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV] ?? commands.setupCommand
|
||||
}
|
||||
|
||||
function makeTempDir(): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'orca-setup-sequencing-'))
|
||||
TEMP_DIRS.push(dir)
|
||||
|
||||
@@ -15,9 +15,11 @@ const DEFAULT_WAIT_TIMEOUT_SECONDS = 2 * 60 * 60
|
||||
export const SETUP_COMPLETE_MESSAGE = 'Setup finished; starting agent.'
|
||||
export const SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV = 'ORCA_SEQUENCED_STARTUP_COMMAND'
|
||||
export const SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV = 'ORCA_SEQUENCED_STARTUP_SCRIPT'
|
||||
export const SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV = 'ORCA_SEQUENCED_SETUP_SCRIPT'
|
||||
|
||||
export type SequencedSetupAgentCommands = {
|
||||
setupCommand: string
|
||||
setupEnv?: Record<string, string>
|
||||
startupCommand: string
|
||||
startupEnv?: Record<string, string>
|
||||
}
|
||||
@@ -75,8 +77,15 @@ export function createSequencedSetupAgentCommands(args: {
|
||||
waitTimeoutSeconds
|
||||
)
|
||||
return {
|
||||
setupCommand: buildPosixSetupCommand(resolution.command, markerPath, nonce),
|
||||
// Why: long worktree paths can push the gate past a PTY's canonical input cap and drop its submit byte.
|
||||
// Why: long worktree paths can push typed wrappers past a PTY's canonical input cap and drop their submit byte.
|
||||
setupCommand: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
|
||||
setupEnv: {
|
||||
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: buildPosixSetupScript(
|
||||
resolution.command,
|
||||
markerPath,
|
||||
nonce
|
||||
)
|
||||
},
|
||||
startupCommand: `bash -lc 'eval "$${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}"'`,
|
||||
startupEnv: {
|
||||
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: args.startupCommand,
|
||||
@@ -85,13 +94,12 @@ export function createSequencedSetupAgentCommands(args: {
|
||||
}
|
||||
}
|
||||
|
||||
function buildPosixSetupCommand(setupCommand: string, markerPath: string, nonce: string): string {
|
||||
function buildPosixSetupScript(setupCommand: string, markerPath: string, nonce: string): string {
|
||||
const marker = quotePosixShellUnlessPlainNoAt(markerPath)
|
||||
const tmp = quotePosixShellUnlessPlainNoAt(`${markerPath}.tmp`)
|
||||
const nonceValue = quotePosixShellUnlessPlainNoAt(nonce)
|
||||
|
||||
// Why no `( … )`: this is typed into the user's line editor, where autopair widgets corrupt it (#18059).
|
||||
const script = [
|
||||
return [
|
||||
`rm -f ${marker} ${tmp} 2>/dev/null`,
|
||||
setupCommand,
|
||||
'status=$?',
|
||||
@@ -99,8 +107,6 @@ function buildPosixSetupCommand(setupCommand: string, markerPath: string, nonce:
|
||||
`mv -f ${tmp} ${marker}`,
|
||||
'exit "$status"'
|
||||
].join('; ')
|
||||
|
||||
return `bash -lc ${quotePosixShellUnlessPlainNoAt(script)}`
|
||||
}
|
||||
|
||||
function buildPosixStartupScript(
|
||||
|
||||
Reference in New Issue
Block a user