Files
orca/src/main/runtime/runtime-remote-managed-worktree-create.ts
T
Brennan BensonandMerge Sim 2ffac471bd fix(workspaces): seed shells only for blank selection (#19940)
* fix(workspaces): seed shells only for blank selection

* fix(workspaces): create runtime-owned launch surfaces

* fix(workspaces): report runtime surface failures

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-10 22:05:30 -07:00

250 lines
9.8 KiB
TypeScript

import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
import type { Repo } from '../../shared/repo-types'
import { getSetupRunnerCommandPlatformForPath } from '../../shared/setup-runner-command'
import { createSequencedSetupAgentCommands } from '../../shared/setup-agent-sequencing'
import type { RuntimeStore } from './runtime-store-contract'
import type { TerminalCreateOptions } from './runtime-terminal-contracts'
import type { WorktreeTerminalProvisioningArgs } from './runtime-worktree-terminal-provisioning'
import type {
WorktreeStartupDraftPaste,
WorktreeStartupFollowup
} from './runtime-worktree-agent-startup'
import {
requestRuntimeRemoteWorktree,
type RuntimeRemoteWorktreeCreateArgs
} from './runtime-remote-worktree-create-request'
import { finishRuntimeRemoteWorktreeCreate } from './runtime-remote-worktree-create-result'
type Dependencies = {
store: RuntimeStore
canSpawn(): boolean
markTrusted(
agent: NonNullable<RuntimeRemoteWorktreeCreateArgs['createdWithAgent']>,
connectionId: string,
path: string
): Promise<void>
createTerminal(
selector: string,
options: TerminalCreateOptions
): Promise<{
handle: string
tabId?: string | null
paneKey?: string | null
ptyId?: string | null
}>
pasteDraft(handle: string, draft: WorktreeStartupDraftPaste): void
sendFollowup(handle: string, followup: WorktreeStartupFollowup): void
provision(
args: WorktreeTerminalProvisioningArgs
): Promise<{ setupSpawned: boolean; setupTerminalHandle: string | null }>
activate(
repoId: string,
worktreeId: string,
setup?: CreateWorktreeResult['setup'],
startup?: RuntimeRemoteWorktreeCreateArgs['startup'],
defaultTabs?: CreateWorktreeResult['defaultTabs']
): void
invalidateResolvedWorktrees(): void
invalidateWorktreeScan(repoId: string): void
notifyWorktreesChanged(repoId: string): void
}
function setupPlatform(setup: CreateWorktreeResult['setup']): 'windows' | 'posix' {
return getSetupRunnerCommandPlatformForPath(setup?.runnerScriptPath ?? '', 'posix')
}
function ownerSurfacing(surface: boolean): { surfaceOwner?: false } {
return surface ? {} : { surfaceOwner: false }
}
export async function createRuntimeRemoteManagedWorktree(
repo: Repo,
args: RuntimeRemoteWorktreeCreateArgs,
deps: Dependencies
): Promise<CreateWorktreeResult> {
if (!deps.store) {
throw new Error('runtime_unavailable')
}
const result = await requestRuntimeRemoteWorktree(repo, args, deps.store)
deps.invalidateResolvedWorktrees()
deps.invalidateWorktreeScan(repo.id)
deps.notifyWorktreesChanged(repo.id)
const shouldActivate = args.activate === true || args.runHooks === true
let warning = result.warning
let didSpawnStartup = false
// Why: same no-double-spawn contract as the local path — once runtime
// provisions setup, omit it from activation and the RPC result.
let didSpawnSetup = false
let setupTerminalHandle: string | null = null
let startupTerminalHandle: string | null = null
let startupTerminalTabId: string | null = null
let startupTerminalPaneKey: string | null = null
let startupTerminalPtyId: string | null = null
let sequencedStartup = args.startup
let wrappedSetupCommandStr: string | undefined
if (args.startup && result.setup?.waitForAgentStartup === true) {
const platform = setupPlatform(result.setup)
const sequenced = createSequencedSetupAgentCommands({
runnerScriptPath: result.setup.runnerScriptPath,
startupCommand: args.startup.command,
platform,
shell: result.setup.shell
})
sequencedStartup = {
...args.startup,
command: sequenced.startupCommand,
...(sequenced.startupEnv ? { env: { ...args.startup.env, ...sequenced.startupEnv } } : {})
}
wrappedSetupCommandStr = sequenced.setupCommand
}
if (sequencedStartup && deps.canSpawn()) {
try {
const startupTrustAgent = args.startupDraftPaste?.agent ?? args.createdWithAgent
if (startupTrustAgent) {
await deps.markTrusted(startupTrustAgent, repo.connectionId!, result.worktree.path)
}
const terminal = await deps.createTerminal(`path:${result.worktree.path}`, {
command: sequencedStartup.command,
...(result.setup && args.startup
? { claudeAgentTeamsSourceCommand: args.startup.command }
: {}),
env: sequencedStartup.env,
...(sequencedStartup.launchConfig ? { launchConfig: sequencedStartup.launchConfig } : {}),
...(args.createdWithAgent ? { launchAgent: args.createdWithAgent } : {}),
...(sequencedStartup.viewMode ? { viewMode: sequencedStartup.viewMode } : {}),
startupCommandDelivery: sequencedStartup.startupCommandDelivery,
telemetry: sequencedStartup.telemetry,
...ownerSurfacing(shouldActivate)
})
if (args.startupDraftPaste) {
deps.pasteDraft(terminal.handle, args.startupDraftPaste)
}
if (args.startupFollowup) {
deps.sendFollowup(terminal.handle, args.startupFollowup)
}
didSpawnStartup = true
startupTerminalHandle = terminal.handle
startupTerminalTabId = terminal.tabId ?? null
startupTerminalPaneKey = terminal.paneKey ?? null
startupTerminalPtyId = terminal.ptyId ?? null
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
warning = warning
? `${warning} Also failed to create the startup terminal for ${result.worktree.path}: ${message}`
: `Failed to create the startup terminal for ${result.worktree.path}: ${message}`
}
}
if (shouldActivate) {
const runtimeWillProvisionTerminals =
didSpawnStartup && Boolean(result.setup || result.defaultTabs)
if (runtimeWillProvisionTerminals) {
// Why: remote/mobile task creates spawn the agent terminal in runtime,
// so renderer activation may not materialize setup/default tabs. Await so
// a failed setup spawn falls back to renderer activation for retry.
const provisioned = await deps.provision({
worktreeSelector: `path:${result.worktree.path}`,
worktreeId: result.worktree.id,
worktreePath: result.worktree.path,
...(result.setup ? { setup: result.setup } : {}),
...(result.defaultTabs ? { defaultTabs: result.defaultTabs } : {}),
primaryTerminalHandle: startupTerminalHandle,
hasStartupTerminal: didSpawnStartup,
setupCommandPlatform: setupPlatform(result.setup),
observeSetupCompletion: args.observeSetupCompletion,
// Why: carry the wait-for-agent wrapped setup command (#6298) so the
// remote Setup tab runs the same script the sequenced agent waits on.
...(wrappedSetupCommandStr ? { wrappedSetupCommand: wrappedSetupCommandStr } : {})
})
didSpawnSetup = provisioned.setupSpawned
setupTerminalHandle = provisioned.setupTerminalHandle
}
// Why: omit setup from activation when runtime spawned it; on spawn
// failure fall through with the wrapped command so renderer retries.
const activationSetup = didSpawnSetup
? undefined
: result.setup
? {
...result.setup,
...(didSpawnStartup && wrappedSetupCommandStr
? { command: wrappedSetupCommandStr }
: {})
}
: undefined
const activationDefaultTabs = runtimeWillProvisionTerminals ? undefined : result.defaultTabs
if (args.startup && !didSpawnStartup) {
deps.activate(
repo.id,
result.worktree.id,
activationSetup,
args.startup,
activationDefaultTabs
)
} else {
deps.activate(repo.id, result.worktree.id, activationSetup, undefined, activationDefaultTabs)
}
}
if (
!shouldActivate &&
deps.canSpawn() &&
(result.setup || result.defaultTabs || didSpawnStartup)
) {
// Why: inactive terminal materialization matches normal worktree creation,
// but setup/default tab failures must not gate automation dispatch.
const provisioning = deps.provision({
worktreeSelector: `path:${result.worktree.path}`,
worktreeId: result.worktree.id,
worktreePath: result.worktree.path,
...(result.setup ? { setup: result.setup } : {}),
...(result.defaultTabs ? { defaultTabs: result.defaultTabs } : {}),
primaryTerminalHandle: startupTerminalHandle,
hasStartupTerminal: didSpawnStartup,
setupCommandPlatform: setupPlatform(result.setup),
observeSetupCompletion: args.observeSetupCompletion,
...(wrappedSetupCommandStr ? { wrappedSetupCommand: wrappedSetupCommandStr } : {}),
surfaceOwner: false
})
// Why: runtime owns setup spawning here, so omit setup from the RPC result
// to keep the headless/mobile caller from launching it a second time.
if (args.awaitTerminalProvisioning) {
const provisioned = await provisioning
didSpawnSetup = provisioned.setupSpawned
setupTerminalHandle = provisioned.setupTerminalHandle
} else {
void provisioning
if (result.setup) {
didSpawnSetup = true
}
}
} else if (!shouldActivate && deps.canSpawn() && !args.createdWithAgent) {
try {
await deps.createTerminal(`path:${result.worktree.path}`, { surfaceOwner: false })
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
warning = warning
? `${warning} Also failed to create the initial terminal for ${result.worktree.path}: ${message}`
: `Failed to create the initial terminal for ${result.worktree.path}: ${message}`
}
}
return finishRuntimeRemoteWorktreeCreate({
result,
request: args,
...(warning ? { warning } : {}),
didSpawnSetup,
didSpawnStartup,
...(wrappedSetupCommandStr ? { wrappedSetupCommand: wrappedSetupCommandStr } : {}),
setupTerminalHandle,
startupTerminalHandle,
startupTerminalTabId,
startupTerminalPaneKey,
startupTerminalPtyId
})
}