// 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, promptHerdrAgent, startHerdrAgent, } from "./herdr.ts"; export type NativeStartupResult = | { kind: "accepted"; surface: string } | { kind: "rejected"; error: unknown; surface?: string } | { kind: "unknown"; error: unknown; surface?: string }; export interface NativeStartupInput { displayName: string; operationId: string; cwd: string; env: Record; args: string[]; task?: string; operation: "start"; } export interface NativeStartupAdapter { /** Adapters must convert transport failures into a structured result. */ start(input: NativeStartupInput): NativeStartupResult | Promise; close(surface: string): void | Promise; } export interface NativeLaunchPlan { cwd: string; args: string[]; env: Record; /** Final task prompt delivered after operation preparation. */ task?: string; } /** Transport seam for Native startup; tests substitute deterministic fakes. */ export interface NativePiStartupTransport { createSurface( displayName: string, options: { cwd?: string; env?: Record }, ): string; startAgent(nativeName: string, surface: string, args: string[], timeoutMs: number): void; promptAgent(surface: string, task: string): void; closeSurface(surface: string): void; } const productionNativePiStartupTransport: NativePiStartupTransport = { createSurface: createHerdrSurface, startAgent: startHerdrAgent, promptAgent: promptHerdrAgent, closeSurface: closeHerdrSurface, }; 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 performNativePiStartup( options: NativeStartupInput, transport: NativePiStartupTransport, startTimeoutMs: number, ): NativeStartupResult { const nativeName = buildHerdrAgentName(options.displayName, options.operationId); let surface: string | undefined; try { surface = transport.createSurface(options.displayName, { cwd: options.cwd, env: options.env }); } catch (error) { return { kind: "rejected", error }; } try { transport.startAgent(nativeName, surface, options.args, startTimeoutMs); } catch (error) { return { kind: "unknown", surface, error }; } try { if (options.task !== undefined) transport.promptAgent(surface, options.task); } catch (error) { return { kind: "unknown", surface, error }; } return { kind: "accepted", surface }; } /** 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) => transport.closeSurface(surface), }; }