mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
Round-1 review fixes. The worker brief no longer forces its own sensitive launch file past the host checks. Worker start offers the brief as an ordinary startup prompt with main's paste (`once-agent-runs`): it rides the agent's launch line where the line carries it exactly, and is otherwise pasted once the agent is ready, as main pasted it, so a host that cannot take a launch file no longer fails the start. With no sensitive file, the agent's hook reports the brief itself, which status readers compact to its task as on main. - Created worktrees are agent-first again, as on main: the startup terminal keeps the handle the brief names, setup and default tabs follow it, and a wait-for-setup sequence still holds the agent until setup succeeds. - The brief names the CLI command the spawn's own WSL decision will give the terminal. - A launch the shell reports finished, with no agent in front, fails and tears down the start. Where hooks are off or never reach the pane, the launch is judged by main's evidence: a title turn-start edge, or the agent holding its terminal.
212 lines
7.6 KiB
TypeScript
212 lines
7.6 KiB
TypeScript
import { agentStartedTelemetry } from '../agent-launch/agent-started-telemetry'
|
|
import type { AgentLaunchPreferences } from '../../shared/agent-session-host-authority'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import type { TuiAgent } from '../../shared/tui-agent'
|
|
import type { WorktreeStartupLaunch } from '../../shared/worktree/launch-types'
|
|
import { repoIsRemote } from '../../shared/agent-launch-remote'
|
|
import { getRepoSshConnectionId } from '../../shared/execution-host'
|
|
import { isTuiAgent } from '../../shared/tui-agent-config'
|
|
import { isTuiAgentEnabled, pickTuiAgent } from '../../shared/tui-agent-selection'
|
|
import { resolveAgentStartupPlanInputs } from '../../shared/agent-startup-plan-inputs'
|
|
import {
|
|
buildAgentDraftLaunchPlan,
|
|
buildAgentStartupPlan,
|
|
planLaunchPrompt,
|
|
type AgentStartupPlan
|
|
} from '../../shared/tui-agent-startup'
|
|
import type { LaunchFile } from '../../shared/launch-prompt-file'
|
|
import type { LaunchPromptPaste } from '../../shared/launch-prompt-carry'
|
|
import { thisOrcaLaunchHost } from './this-orca-launch-host'
|
|
import {
|
|
detectInstalledAgentsWithShellPathHydration,
|
|
detectRemoteAgents
|
|
} from '../preflight/agent-detection'
|
|
import type { RuntimeStore } from './runtime-store-contract'
|
|
|
|
export type WorktreeStartupDraftPaste = { agent: TuiAgent; content: string }
|
|
export type WorktreeStartupFollowup = { expectedProcess: string; prompt: string }
|
|
|
|
/** A fresh agent the host builds always carries its `agent_started` record; dropping it fails to compile. */
|
|
type AttributedWorktreeStartupLaunch = WorktreeStartupLaunch & {
|
|
telemetry: NonNullable<WorktreeStartupLaunch['telemetry']>
|
|
}
|
|
|
|
type StartupEnvironment = {
|
|
repo: Repo
|
|
settings: ReturnType<RuntimeStore['getSettings']>
|
|
getLaunchPlatform: () => NodeJS.Platform
|
|
/** Replaces the configured arguments for this launch; `null` means none. */
|
|
agentArgs?: string | null
|
|
/** Caller-supplied telemetry attribution, validated leniently at the host boundary. */
|
|
launchSource?: string
|
|
}
|
|
|
|
export async function buildWorktreeStartupForDraft(
|
|
environment: StartupEnvironment & { draft: string; requestedAgent?: TuiAgent }
|
|
): Promise<{
|
|
agent: TuiAgent
|
|
startup: AttributedWorktreeStartupLaunch
|
|
draftPaste?: WorktreeStartupDraftPaste
|
|
} | null> {
|
|
const content = environment.draft.trim()
|
|
if (!content) {
|
|
return null
|
|
}
|
|
const { repo, settings } = environment
|
|
const preferredAgent = environment.requestedAgent ?? settings.defaultTuiAgent
|
|
// Why: `blank` is an explicit shell-only preference, so linked drafts must not auto-pick an agent.
|
|
if (preferredAgent === 'blank') {
|
|
return null
|
|
}
|
|
let agent =
|
|
isTuiAgent(preferredAgent) && isTuiAgentEnabled(preferredAgent, settings.disabledTuiAgents)
|
|
? preferredAgent
|
|
: null
|
|
if (!agent) {
|
|
let detected: string[] = []
|
|
// Why: detection has to run on the machine that will run the agent, and SSH ownership has two
|
|
// spellings — the raw field probes this client for an `executionHostId: 'ssh:*'`-only repo.
|
|
const sshConnectionId = getRepoSshConnectionId(repo)
|
|
try {
|
|
// Why: startup-draft fallback can run from sparse runtime launch envs too.
|
|
detected = sshConnectionId
|
|
? await detectRemoteAgents({ connectionId: sshConnectionId })
|
|
: await detectInstalledAgentsWithShellPathHydration()
|
|
} catch {
|
|
detected = []
|
|
}
|
|
agent = pickTuiAgent(null, detected.filter(isTuiAgent), settings.disabledTuiAgents)
|
|
}
|
|
if (!agent) {
|
|
return null
|
|
}
|
|
|
|
const launchArgs = resolveAgentStartupPlanInputs({
|
|
agent,
|
|
settings,
|
|
platform: environment.getLaunchPlatform(),
|
|
isRemote: repoIsRemote(repo),
|
|
...(environment.agentArgs !== undefined ? { agentArgs: environment.agentArgs } : {})
|
|
})
|
|
const telemetry = agentStartedTelemetry(agent, environment.launchSource)
|
|
const draftPlan = buildAgentDraftLaunchPlan({ ...launchArgs, draft: content })
|
|
if (draftPlan) {
|
|
return {
|
|
agent,
|
|
startup: {
|
|
command: draftPlan.launchCommand,
|
|
launchConfig: draftPlan.launchConfig,
|
|
...(draftPlan.startupCommandDelivery
|
|
? { startupCommandDelivery: draftPlan.startupCommandDelivery }
|
|
: {}),
|
|
...(draftPlan.env ? { env: draftPlan.env } : {}),
|
|
telemetry
|
|
}
|
|
}
|
|
}
|
|
const startupPlan = buildAgentStartupPlan({
|
|
...launchArgs,
|
|
prompt: '',
|
|
allowEmptyPromptLaunch: true
|
|
})
|
|
if (!startupPlan) {
|
|
return null
|
|
}
|
|
return {
|
|
agent,
|
|
startup: {
|
|
command: startupPlan.launchCommand,
|
|
launchConfig: startupPlan.launchConfig,
|
|
...(startupPlan.startupCommandDelivery
|
|
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
|
|
: {}),
|
|
...(startupPlan.env ? { env: startupPlan.env } : {}),
|
|
telemetry
|
|
},
|
|
draftPaste: { agent, content }
|
|
}
|
|
}
|
|
|
|
export function buildWorktreeStartupForAgent(
|
|
environment: StartupEnvironment & {
|
|
agent: TuiAgent
|
|
prompt?: string
|
|
launchPreferences?: AgentLaunchPreferences
|
|
toSessionOptions: (
|
|
preferences?: AgentLaunchPreferences
|
|
) => Parameters<typeof buildAgentStartupPlan>[0]['sessionOptions'] | undefined
|
|
/** Set by a caller that pastes an uncarried prompt itself; reports whether it rode the command,
|
|
* and no follow-up is returned for it to paste twice. */
|
|
onPromptCarry?: (carried: boolean) => void
|
|
/** The caller's paste for what the line leaves; #24257's guarded paste by default. */
|
|
promptPaste?: LaunchPromptPaste
|
|
}
|
|
): {
|
|
agent: TuiAgent
|
|
startup: AttributedWorktreeStartupLaunch
|
|
followup?: WorktreeStartupFollowup
|
|
} {
|
|
const { agent, repo, settings } = environment
|
|
if (!isTuiAgentEnabled(agent, settings.disabledTuiAgents)) {
|
|
throw new Error('Selected agent is disabled. Choose an enabled agent before creating.')
|
|
}
|
|
const planInputs = resolveAgentStartupPlanInputs({
|
|
agent,
|
|
settings,
|
|
platform: environment.getLaunchPlatform(),
|
|
isRemote: repoIsRemote(repo),
|
|
...(environment.agentArgs !== undefined ? { agentArgs: environment.agentArgs } : {}),
|
|
sessionOptions: environment.toSessionOptions(environment.launchPreferences)
|
|
})
|
|
const planned = planLaunchPrompt({
|
|
...planInputs,
|
|
prompt: environment.prompt ?? '',
|
|
host: thisOrcaLaunchHost({
|
|
isRemote: repoIsRemote(repo),
|
|
launchPlatform: environment.getLaunchPlatform(),
|
|
settings
|
|
}),
|
|
// The caller, or else the host's follow-up, pastes what the line leaves once the agent runs.
|
|
paste: environment.promptPaste ?? 'when-host-proves-agent'
|
|
})
|
|
if (!planned) {
|
|
throw new Error(`Could not build launch command for ${agent}.`)
|
|
}
|
|
let startupPlan: AgentStartupPlan
|
|
let launchFile: LaunchFile | undefined
|
|
let followup: WorktreeStartupFollowup | undefined
|
|
switch (planned.carry) {
|
|
case 'none':
|
|
startupPlan = planned.plan
|
|
break
|
|
case 'on-line':
|
|
case 'launch-file':
|
|
startupPlan = planned.plan
|
|
launchFile = planned.carry === 'launch-file' ? planned.launchFile : undefined
|
|
environment.onPromptCarry?.(true)
|
|
break
|
|
case 'paste-after-ready':
|
|
startupPlan = planned.cleanPlan
|
|
if (environment.onPromptCarry) {
|
|
environment.onPromptCarry(false)
|
|
} else {
|
|
followup = { expectedProcess: startupPlan.expectedProcess, prompt: planned.text }
|
|
}
|
|
break
|
|
}
|
|
return {
|
|
agent,
|
|
startup: {
|
|
command: startupPlan.launchCommand,
|
|
launchConfig: startupPlan.launchConfig,
|
|
...(startupPlan.startupCommandDelivery
|
|
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
|
|
: {}),
|
|
...(startupPlan.env ? { env: startupPlan.env } : {}),
|
|
...(launchFile ? { launchFile } : {}),
|
|
telemetry: agentStartedTelemetry(agent, environment.launchSource)
|
|
},
|
|
...(followup ? { followup } : {})
|
|
}
|
|
}
|