mirror of
https://github.com/stablyai/orca.git
synced 2026-10-14 00:02:42 +00:00
#24257 moved every multi-line or over-512-byte prompt off the typed launch line and pasted it after readiness. The phone's AI buttons and review notes, whose multi-line prompts main typed whole into zsh, then reached Claude 0.5-3 s later and their RPC reply waited for the paste. The host now names the shell a local macOS or Linux line is typed into, the way the spawn picks it, and a multi-line prompt rides a zsh line when every line is at most 512 bytes and the whole line at most 8 KB. A real-zsh test types such a line through Orca's own ready barrier and startup write, including when a slow user config makes the write land early. Elsewhere the measured unsafe cases keep the paste: bash 3.2 runs multi-line lines piecemeal, fish drops an early multi-line write, and any shell loses a line over 1 KB written early.
197 lines
7.2 KiB
TypeScript
197 lines
7.2 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 } from '../../shared/tui-agent-startup'
|
|
import { planStartupWithPromptCandidate } from '../../shared/startup-line-prompt-carry'
|
|
import { nameLocalTypedLineShell } from './agent-launch-typed-line-shell'
|
|
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 delivers an uncarried prompt itself: the prompt then rides only a typed
|
|
* line that can carry it, and this reports whether it did. Absent keeps the CLI's fold. */
|
|
onPromptCarry?: (carried: boolean) => void
|
|
}
|
|
): {
|
|
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 prompt = environment.prompt ?? ''
|
|
let startupPlan: ReturnType<typeof buildAgentStartupPlan>
|
|
if (environment.onPromptCarry) {
|
|
const offered = planStartupWithPromptCandidate(
|
|
planInputs,
|
|
prompt,
|
|
nameLocalTypedLineShell({
|
|
isRemote: repoIsRemote(repo),
|
|
...(settings.terminalDefaultShell
|
|
? { defaultShellSetting: settings.terminalDefaultShell }
|
|
: {})
|
|
})
|
|
)
|
|
startupPlan = offered.plan
|
|
if (startupPlan && prompt.trim()) {
|
|
environment.onPromptCarry(offered.promptCarried)
|
|
}
|
|
} else {
|
|
startupPlan = buildAgentStartupPlan({ ...planInputs, prompt, allowEmptyPromptLaunch: true })
|
|
}
|
|
if (!startupPlan) {
|
|
throw new Error(`Could not build launch command for ${agent}.`)
|
|
}
|
|
return {
|
|
agent,
|
|
startup: {
|
|
command: startupPlan.launchCommand,
|
|
launchConfig: startupPlan.launchConfig,
|
|
...(startupPlan.startupCommandDelivery
|
|
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
|
|
: {}),
|
|
...(startupPlan.env ? { env: startupPlan.env } : {}),
|
|
telemetry: agentStartedTelemetry(agent, environment.launchSource)
|
|
},
|
|
...(startupPlan.followupPrompt
|
|
? {
|
|
followup: {
|
|
expectedProcess: startupPlan.expectedProcess,
|
|
prompt: startupPlan.followupPrompt
|
|
}
|
|
}
|
|
: {})
|
|
}
|
|
}
|