import { ModelThinkingLevel } from '@earendil-works/pi-ai'; /** * lifecycle-interceptor.ts — Ordered, generative lifecycle interception. * * Unlike the child lifecycle events, this registry is intentionally small: it * exists only because callers need the core to consume prompt/result decisions. * It owns ordering, cancellation, registration lifetime, and the finite * continuation bound; it does not expose sessions, records, models, or queues. */ /** The fixed bound prevents a provider from turning completion into an unbounded loop. */ declare const MAX_LIFECYCLE_CONTINUATION_ROUNDS = 3; type SubagentExecutionPhase = "initial" | "resume"; type SubagentExecutionOrigin = "tool" | "service"; type SubagentExecutionMode = "foreground" | "background"; type SubagentExecutionAdmission = "immediate" | "queued"; type SubagentLifecycleOutcome = "completed" | "steered" | "aborted"; /** Immutable identifiers for one initial or resumed execution attempt. */ interface SubagentLifecycleIdentity { readonly agentId: string; readonly sessionId: string; readonly runId: string; readonly agentType: string; readonly parentSessionId?: string; } /** How this execution entered the core; queue admission is captured before it starts. */ interface SubagentLifecycleExecutionPath { readonly phase: SubagentExecutionPhase; readonly origin: SubagentExecutionOrigin; readonly mode: SubagentExecutionMode; readonly admission: SubagentExecutionAdmission; } interface SubagentLifecycleStartContext { readonly identity: SubagentLifecycleIdentity; readonly execution: SubagentLifecycleExecutionPath; /** The exact next value that will be passed to AgentSession.prompt(). */ readonly prompt: string; readonly signal: AbortSignal; } type SubagentLifecycleStartDecision = { readonly action: "continue"; readonly prompt?: string; } | { readonly action: "abort"; readonly reason: string; }; interface SubagentLifecycleCompletionContext { readonly identity: SubagentLifecycleIdentity; readonly execution: SubagentLifecycleExecutionPath; /** Candidate text before workspace teardown, state mutation, and events. */ readonly proposedResult: string; readonly outcome: SubagentLifecycleOutcome; readonly continuationRound: number; readonly maxContinuationRounds: number; readonly signal: AbortSignal; } type SubagentLifecycleCompletionDecision = { readonly action: "complete"; readonly result?: string; } | { readonly action: "continue"; readonly prompt: string; } | { readonly action: "abort"; readonly reason: string; }; /** * A provider can observe and transform boundaries but never receives the live * AgentSession. Omitted callbacks are no-ops, making one provider useful for a * single boundary without creating a second registration kind. */ interface SubagentLifecycleInterceptor { beforeStart?(context: SubagentLifecycleStartContext): SubagentLifecycleStartDecision | undefined | Promise; beforeComplete?(context: SubagentLifecycleCompletionContext): SubagentLifecycleCompletionDecision | undefined | Promise; /** Called exactly once after unregistration or registry shutdown. */ dispose?(): void | Promise; } /** Idempotent registration handle returned by the public service. */ interface SubagentLifecycleRegistration { dispose(): Promise; } /** * types.ts — Type definitions for the subagent system. */ /** Pi's model-capability thinking levels, including the explicit `off` value. */ type ThinkingLevel = ModelThinkingLevel; /** Agent type: any string name (built-in defaults or user-defined). */ type SubagentType = string; interface AgentInvocation { /** Exact effective model label in `provider/id` form. */ modelName?: string; thinking?: ThinkingLevel; maxTurns?: number; inheritContext?: boolean; runInBackground?: boolean; } /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */ /** * Lifetime usage components, accumulated via `message_end` events. Survives * compaction (which replaces session.state.messages and would reset any * stats-derived sum). cacheRead is excluded because each turn's cacheRead is * the cumulative cached prefix re-read on that one call — summing across * turns counts the prefix N times. See issue #38. */ type LifetimeUsage = { input: number; output: number; cacheWrite: number; }; /** * subagent-state.ts — SubagentState value object: lifecycle status, metrics, and live activity. * * Owns the passive, readable state of a subagent — status, result, error, * timestamps, stats (toolUses, lifetimeUsage, compactionCount), and live-activity * fields (turnCount, activeTools, responseText) — together with the transition * methods (markRunning, markCompleted, …), accumulation methods * (incrementToolUses, addUsage, incrementCompactions), and live-activity * transition methods (incrementTurnCount, addActiveTool, removeActiveTool, * resetResponseText, appendResponseText) that mutate them. * * State is encapsulated behind getters; external code reads through them but * mutates only via the transition/accumulation methods. The value object owns * all of its own mutations — no field is written from outside. * * Subagent holds one of these privately and delegates its getters and mutation * methods to it. Extracting it lets the lifecycle state machine and the * session-event observer be unit-tested without constructing an executor. */ type SubagentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error"; /** * workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2). * * "Where does a child run, and what brackets the run?" is a strategy (git * worktree, container, tmpdir, remote sandbox), not core behavior. The core * needs only a working directory plus a disposal hook; the default — the * parent's cwd, with no setup/teardown — is always correct. * * Unlike the observational lifecycle events in child-lifecycle.ts, this is a * *generative* seam: a registered provider returns a value the core consumes * synchronously at run-start. The core has no knowledge of git or worktrees. */ /** Context the core hands a provider when a child run starts. */ interface WorkspacePrepareContext { agentId: string; agentType: SubagentType; baseCwd: string; invocation?: AgentInvocation; } /** Outcome the core reports to a workspace when the run ends. */ interface WorkspaceDisposeOutcome { status: SubagentStatus; description: string; } /** What dispose may hand back for the core to fold into the child result. */ interface WorkspaceDisposeResult { /** Appended verbatim to the child's result text — the provider owns the wording. */ resultAddendum?: string; } /** A prepared working directory plus its bracketed teardown. Born complete. */ interface Workspace { /** The working directory — already exists when the workspace is handed back. */ readonly cwd: string; dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined; } /** The single generative seam: supplies a child's workspace. */ interface WorkspaceProvider { prepare(ctx: WorkspacePrepareContext): Promise; } /** * service.ts — Public API surface for cross-extension access to subagents. * * Consumers declare this package as an optional peer dependency and use * dynamic import to access the accessor functions: * * const { getSubagentsService } = await import("@nklisch/pi-subagents"); * const svc = getSubagentsService(); * svc?.spawn("Explore", "Check for stale TODOs"); */ /** Serializable snapshot of an agent's state — no live session objects. */ interface SubagentRecord { id: string; type: string; description: string; status: SubagentStatus; result?: string; error?: string; toolUses: number; startedAt: number; completedAt?: number; lifetimeUsage: LifetimeUsage; compactionCount: number; } /** Options for spawning an agent via the service. */ interface SpawnOptions { description?: string; model?: string; maxTurns?: number; thinkingLevel?: string; inheritContext?: boolean; foreground?: boolean; bypassQueue?: boolean; } /** The public service contract for cross-extension subagent access. */ interface SubagentsService { /** Spawn an agent. Returns the agent ID immediately. */ spawn(type: string, prompt: string, options?: SpawnOptions): string; /** Get a snapshot of an agent's current state. */ getRecord(id: string): SubagentRecord | undefined; /** List all tracked agents, most recent first. */ listAgents(): SubagentRecord[]; /** Abort a running or queued agent. Returns false if not found. */ abort(id: string): boolean; /** Send a steering message to a running agent. */ steer(id: string, message: string): Promise; /** Wait for all running and queued agents to complete. */ waitForAll(): Promise; /** Whether any agents are running or queued. */ hasRunning(): boolean; /** * Register the single workspace provider that supplies a child's working * directory plus bracketed setup/teardown. Throws if one is already * registered. Returns a disposer that unregisters the provider. */ registerWorkspaceProvider(provider: WorkspaceProvider): () => void; /** * Register an ordered async lifecycle provider. It receives only immutable * execution facts and prompt/result decisions, never a manager or session. */ registerLifecycleInterceptor(interceptor: SubagentLifecycleInterceptor): SubagentLifecycleRegistration; } /** Event channel constants for pi.events subscriptions. */ declare const SUBAGENT_EVENTS: { readonly STARTED: "subagents:started"; readonly COMPLETED: "subagents:completed"; readonly RESUMED: "subagents:resumed"; readonly FAILED: "subagents:failed"; readonly COMPACTED: "subagents:compacted"; readonly CREATED: "subagents:created"; readonly STEERED: "subagents:steered"; }; /** Publish the SubagentsService on globalThis for cross-extension access. */ declare function publishSubagentsService(service: SubagentsService): void; /** Retrieve the published SubagentsService, or undefined if not yet published. */ declare function getSubagentsService(): SubagentsService | undefined; /** Remove the SubagentsService from globalThis (call on shutdown/reload). */ declare function unpublishSubagentsService(): void; export { MAX_LIFECYCLE_CONTINUATION_ROUNDS, SUBAGENT_EVENTS, getSubagentsService, publishSubagentsService, unpublishSubagentsService }; export type { LifetimeUsage, SpawnOptions, SubagentExecutionAdmission, SubagentExecutionMode, SubagentExecutionOrigin, SubagentExecutionPhase, SubagentLifecycleCompletionContext, SubagentLifecycleCompletionDecision, SubagentLifecycleExecutionPath, SubagentLifecycleIdentity, SubagentLifecycleInterceptor, SubagentLifecycleOutcome, SubagentLifecycleRegistration, SubagentLifecycleStartContext, SubagentLifecycleStartDecision, SubagentRecord, SubagentStatus, SubagentsService, Workspace, WorkspaceDisposeOutcome, WorkspaceDisposeResult, WorkspacePrepareContext, WorkspaceProvider };