// Subagent launcher: the parent-side coordination that takes a Fresh // Subagent from launch-policy resolution, through Native startup adoption, to // registry registration, producing a RunningSubagent. Foreground/background // Completion delivery and model presentation are not part of it. See // CONTEXT.md "Subagent launcher". // Registry construction and lifecycle initialization cross the registry seam. import { createRunningSubagent, type RunningSubagent, } from "./running-registry.ts"; import type { CompletionOperationPreparer, NativeOperationContext, OperationLaunchScope, } from "./completion-operation.ts"; import type { LaunchPolicyRequest, LaunchPolicyResolver } from "./launch-policy.ts"; import type { ResolvedRuntimePlan, ThinkingLevel } from "./runtime-routing.ts"; import { wrapPiModelRegistry } from "./runtime-routing.ts"; /** Parent runtime fields the launcher reads for one spawn. */ export interface SubagentLaunchContext { /** Parent cwd only; a parent Pi session file is never required. */ cwd: string; model?: { provider: string; id: string }; modelRegistry: { find(provider: string, modelId: string): any; getAvailable?: () => any[]; getAll?: () => any[]; hasConfiguredAuth?: (model: any) => boolean; }; } export interface SpawnFreshInput { /** Tool params: launch-policy fields plus a required built-in subagent type name. */ params: LaunchPolicyRequest & { agent: string }; parentThinking: ThinkingLevel; /** True for foreground calls; drives deliveryMode and cleanup ownership. */ foreground: boolean; ctx: SubagentLaunchContext; preparer: CompletionOperationPreparer; } export interface SubagentLauncher { spawnFresh(input: SpawnFreshInput): Promise; } export interface SubagentLauncherDependencies { launchPolicy: LaunchPolicyResolver; /** Parent-owned registry write; the registry Map stays with the parent runtime. */ register: (running: RunningSubagent) => void; /** Count accepted Native startup before RunningSubagent adoption. */ reserveDescendant?: (scope: OperationLaunchScope) => void; /** Release a pre-adoption reservation after startup failure. */ releaseDescendant?: (scope: OperationLaunchScope) => void; } function deliveryModeFor(foreground: boolean): "foreground" | "background" { return foreground ? "foreground" : "background"; } export function createSubagentLauncher( dependencies: SubagentLauncherDependencies, ): SubagentLauncher { const { launchPolicy, register, reserveDescendant, releaseDescendant } = dependencies; function adoptAsRunning( accepted: NativeOperationContext, identity: { name: string; task: string; agent: string; startTime: number; runtimePlan: ResolvedRuntimePlan | undefined; foreground: boolean; }, ): RunningSubagent { const running = createRunningSubagent({ id: accepted.operationId, name: identity.name, task: identity.task, agent: identity.agent, surface: accepted.surface, startTime: identity.startTime, artifacts: accepted.artifacts, activityFile: accepted.activityFile, runtimePlan: identity.runtimePlan, // Foreground cancellation owns cleanup for the active child. cleanupOnCancellation: identity.foreground, }); register(running); return running; } return { async spawnFresh(input: SpawnFreshInput): Promise { const { params, parentThinking, foreground, ctx, preparer } = input; const startTime = Date.now(); if (!ctx.model) { throw new Error("Subagent launch requires a resolved parent model"); } const resolvedPolicy = launchPolicy.resolve(params, { parentCwd: ctx.cwd, parentRuntime: { provider: ctx.model.provider, modelId: ctx.model.id, thinking: parentThinking, }, modelRegistry: wrapPiModelRegistry(ctx.modelRegistry), }); return preparer.fresh({ startedAt: startTime, name: params.name, task: params.task, deliveryMode: deliveryModeFor(foreground), childCwd: resolvedPolicy.targetCwd, // The operation preparer owns the temporary prompt resource. The // launch policy only fills it and builds the argv-based plan. resolveLaunch: ({ launchPromptFile }) => resolvedPolicy.buildLaunchPlan(launchPromptFile), reserve: reserveDescendant, release: releaseDescendant, adopt: (accepted) => adoptAsRunning(accepted, { name: params.name, task: params.task, agent: params.agent.trim(), startTime, runtimePlan: resolvedPolicy.runtimePlan, foreground, }), }); }, }; }