/** * Subagents v2 — Core types. * * Normalized types shared across harnesses (Pi, Codex) and tools. */ import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; // ── Harness types ──────────────────────────────────────────────────────────── export type HarnessKind = "pi" | "codex"; export interface HarnessConfig { kind: HarnessKind; /** Model to use. For pi: provider/modelId. For codex: model slug. */ model?: string; /** Thinking level. For pi: off..max. For codex: effort (low/medium/high). */ thinking?: string; /** Codex-specific: sandbox policy. */ codexSandbox?: CodexSandbox; /** Codex-specific: path to codex binary. */ codexPath?: string; /** Max concurrent for this harness (overrides global). */ maxConcurrent?: number; } export interface SubagentProfileConfig { /** Harness selected when the spawn call does not override it. */ harness: HarnessKind; /** Model selected when the spawn call does not override it. */ model?: string; /** Thinking/effort selected when the spawn call does not override it. */ thinking?: string; /** Codex sandbox selected when the spawn call does not override it. */ codexSandbox?: CodexSandbox; /** Turn limit selected when the spawn call does not override it. */ maxTurns?: number; } export type CodexSandbox = | "workspace-write" | "read-only" | "danger-full-access"; export interface SubagentsConfig { /** Default harness when none specified. */ defaultHarness?: HarnessKind; /** Per-harness overrides. */ harnesses?: Partial>; /** Reusable named role profiles (for example planner, coder, reviewer). */ profiles?: Record; /** Global max concurrent subagents (default 4). */ maxConcurrent?: number; /** Trust store config for cwd allowlisting. */ trust?: TrustConfig; /** Codex global config. */ codex?: { sandbox?: CodexSandbox; path?: string; /** Clamp effort to a max. */ maxEffort?: "low" | "medium" | "high"; }; } export interface TrustConfig { /** If true, only allowlisted directories may be used as cwd. */ restrictCwd?: boolean; /** Allowlisted directories. */ allowedDirs?: string[]; } // ── Agent lifecycle types ──────────────────────────────────────────────────── export type SubagentStatus = | "queued" | "running" | "completed" | "failed" | "cancelled"; export interface SubagentSpawnParams { /** Named profile from subagents.json. Explicit spawn fields override it. */ profile?: string; /** Harness to use. Default: profile, config default, or "pi". */ harness?: HarnessKind; /** Human-readable label for UI. */ label?: string; /** Task prompt. */ task: string; /** Working directory. Only honoured if trust store permits. */ cwd?: string; /** Model override. */ model?: string; /** Thinking/effort override. */ thinking?: string; /** Max turns before wrap-up. */ maxTurns?: number; /** Codex-specific sandbox override. Requires opt-in in config for danger-full-access. */ codexSandbox?: CodexSandbox; } export interface SubagentRecord { /** Manager-assigned ID (single source of truth). */ id: string; /** Named profile used for this run, if any. */ profile?: string; harness: HarnessKind; status: SubagentStatus; label: string; task: string; cwd: string; model?: string; thinking?: string; startedAt: number; completedAt: number | null; toolCount: number; output: string; error?: string; /** Transcript file path (if enabled). */ transcriptPath?: string; /** Follow-up already delivered via pi.sendMessage. */ followUpDelivered: boolean; /** Internal: pending waiter callbacks (supports multiple concurrent wait calls). */ _waiters?: Array<{ resolve: () => void; reject: (err: Error) => void }>; /** Internal: abort controller. */ abortController?: AbortController; /** Internal: usage tracking. */ usage: SubagentUsage; /** Internal: recent tool events for live display. */ recentTools: ToolEvent[]; } export interface SubagentUsage { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number; } export interface ToolEvent { tool: string; args: string; status: "running" | "done"; timestamp: number; } // ── Event types ────────────────────────────────────────────────────────────── export type SubagentEventType = | "subagent:spawned" | "subagent:started" | "subagent:progress" | "subagent:completed" | "subagent:failed" | "subagent:cancelled" | "subagent:followup"; export interface SubagentEvent { type: SubagentEventType; id: string; harness: HarnessKind; timestamp: number; record?: SubagentRecord; error?: string; } export type SubagentEventListener = (event: SubagentEvent) => void; // ── Harness interface ──────────────────────────────────────────────────────── export interface SubagentHarness { readonly kind: HarnessKind; /** * Spawn a subagent process. The manager generates the ID and passes it. * `ctx` is the parent ExtensionContext for model resolution and tool allowlisting. * Harness must NOT emit subagent:spawned — the manager owns that event. */ spawn( id: string, params: SubagentSpawnParams, ctx: ExtensionContext, onEvent: (event: SubagentEvent) => void, signal?: AbortSignal, ): Promise; /** Cancel a running subagent. */ cancel(id: string): Promise; /** Dispose the harness (kill processes, clean up). */ dispose(): void; }