// Native startup: Herdr agent identity derivation, startup timeout policy, // and the pane → agent start → task prompt sequence behind the // NativeStartupAdapter seam (ADR-0005). The transport is injectable so // startup classification is testable without a Herdr server. import { closeHerdrSurface, createHerdrSurface, createHerdrWorktree, inspectGitWorktree, promptHerdrAgent, removeHerdrWorktree, startHerdrAgent, } from "./herdr.ts"; import { createSubagentWorktree, reclaimSubagentSurface, type ManagedSubagentWorktree, type SubagentSurfaceCleanup, type SubagentWorktreeTransport, } from "./subagent-worktree.ts"; export type NativeStartupResult = | { kind: "accepted"; surface: string; worktree?: ManagedSubagentWorktree } | { kind: "rejected"; error: unknown; surface?: string; worktree?: ManagedSubagentWorktree } | { kind: "unknown"; error: unknown; surface?: string; worktree?: ManagedSubagentWorktree }; export interface NativeStartupInput { displayName: string; operationId: string; cwd: string; env: Record; args: string[]; task?: string; worktree?: boolean; operation: "start"; } export interface NativeStartupAdapter { /** Adapters must convert transport failures into a structured result. */ start(input: NativeStartupInput): NativeStartupResult | Promise; close( surface: string, worktree?: ManagedSubagentWorktree, ): SubagentSurfaceCleanup | void | Promise; } export interface NativeLaunchPlan { cwd: string; args: string[]; env: Record; /** Final task prompt delivered after operation preparation. */ task?: string; worktree?: boolean; } /** Transport seam for Native startup; tests substitute deterministic fakes. */ export interface NativePiStartupTransport { createSurface( displayName: string, options: { cwd?: string; env?: Record }, ): string; createWorktree?( displayName: string, operationId: string, cwd: string, env: Record, ): { surface: string; worktree: ManagedSubagentWorktree }; startAgent(nativeName: string, surface: string, args: string[], timeoutMs: number): void; promptAgent(surface: string, task: string): void; closeSurface(surface: string, worktree?: ManagedSubagentWorktree): SubagentSurfaceCleanup | void; } const productionWorktreeTransport: SubagentWorktreeTransport = { inspect: inspectGitWorktree, create: createHerdrWorktree, remove: removeHerdrWorktree, close: closeHerdrSurface, }; const productionNativePiStartupTransport: NativePiStartupTransport = { createSurface: createHerdrSurface, createWorktree: (displayName, operationId, cwd, env) => createSubagentWorktree({ name: displayName, operationId, cwd, env }, productionWorktreeTransport), startAgent: startHerdrAgent, promptAgent: promptHerdrAgent, closeSurface: (surface, worktree) => reclaimSubagentSurface(surface, worktree, productionWorktreeTransport), }; export function getNativeAgentStartTimeoutMs(): number { const raw = process.env.PI_SUBAGENT_AGENT_START_TIMEOUT_MS?.trim(); const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN; if (!Number.isFinite(parsed)) return 30_000; return Math.min(300_000, Math.max(1, parsed)); } function slugifyHerdrAgentName(value: string): string { return value .normalize("NFKD") .replace(/[^a-zA-Z0-9\s_-]/g, "") .toLowerCase() .replace(/[\s_]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); } /** Derive the Herdr agent identity for one Completion operation (CONTEXT.md). */ export function buildHerdrAgentName(displayName: string, id: string): string { const suffix = `-${id}`; const slug = slugifyHerdrAgentName(displayName); const prefixed = /^[a-z]/.test(slug) ? slug : `subagent-${slug}`; const base = prefixed.slice(0, 32 - suffix.length).replace(/-+$/, "") || "subagent"; return `${base}${suffix}`.slice(0, 32); } function partialWorktreeFromError(error: unknown): { surface?: string; worktree?: ManagedSubagentWorktree; } { if (error == null || typeof error !== "object") return {}; const candidate = error as { surface?: unknown; worktree?: Partial; }; const surface = typeof candidate.surface === "string" && candidate.surface.length > 0 ? candidate.surface : undefined; const worktree = candidate.worktree && typeof candidate.worktree.workspaceId === "string" && typeof candidate.worktree.path === "string" && typeof candidate.worktree.branch === "string" && typeof candidate.worktree.baseCommit === "string" ? candidate.worktree as ManagedSubagentWorktree : undefined; return { ...(surface ? { surface } : {}), ...(worktree ? { worktree } : {}) }; } function performNativePiStartup( options: NativeStartupInput, transport: NativePiStartupTransport, startTimeoutMs: number, ): NativeStartupResult { const nativeName = buildHerdrAgentName(options.displayName, options.operationId); let surface: string | undefined; let worktree: ManagedSubagentWorktree | undefined; try { if (options.worktree) { if (!transport.createWorktree) { throw new Error("Native startup transport does not support Herdr worktrees."); } const created = transport.createWorktree( options.displayName, options.operationId, options.cwd, options.env, ); surface = created.surface; worktree = created.worktree; } else { surface = transport.createSurface(options.displayName, { cwd: options.cwd, env: options.env }); } } catch (error) { const partial = partialWorktreeFromError(error); surface ??= partial.surface; worktree ??= partial.worktree; return { kind: "rejected", error, ...(surface ? { surface } : {}), ...(worktree ? { worktree } : {}), }; } try { transport.startAgent(nativeName, surface, options.args, startTimeoutMs); } catch (error) { return { kind: "unknown", surface, error, ...(worktree ? { worktree } : {}) }; } try { if (options.task !== undefined) transport.promptAgent(surface, options.task); } catch (error) { return { kind: "unknown", surface, error, ...(worktree ? { worktree } : {}) }; } return { kind: "accepted", surface, ...(worktree ? { worktree } : {}) }; } /** Create the NativeStartupAdapter for Ephemeral Child runtimes inside Herdr. */ export function createNativePiStartupAdapter( transport: NativePiStartupTransport = productionNativePiStartupTransport, startTimeoutMs?: number, ): NativeStartupAdapter { return { start: (input) => performNativePiStartup(input, transport, startTimeoutMs ?? getNativeAgentStartTimeoutMs()), close: (surface, worktree) => transport.closeSurface(surface, worktree), }; }