import type { AgentRegistry } from '../../registry/agent/definitions.js'; import { type CapacityValidator, DelegationCapacityExceeded } from '../../session/handoff/capacity.js'; import type { SessionPaths } from '../../session/paths.js'; import type { SessionSummaryMaterializer } from '../../session/summary/materialize.js'; import type { WorkspaceBackendRegistry } from '../../session/workspace/registry.js'; import type { AgentLifecycleListener } from '../../types/agent/lifecycle-event.js'; import type { AgentManagerConfig, AgentTask, AgentTaskContext, AgentTaskState, SendMessageOptions } from '../../types/agent/task.js'; import type { SessionId, TaskId, TenantId, TurnId } from '../../types/ids/index.js'; import type { Message } from '../../types/message/index.js'; import { type CancelCause } from '../../types/session/cancel-cause.js'; import type { SessionEventListener } from '../../types/session/events.js'; import type { SubSessionId } from '../../types/session/ids.js'; import type { SessionStore } from '../../types/session/store.js'; import type { WorkspaceRef } from '../../types/workspace/ref.js'; import { type Logger } from '../../utils/logger.js'; import { type TopicManagerDependency } from '../topic/dependency.js'; /** * Dependencies threaded into {@link AgentManager}. Phase 6 promoted the * SubSession + Session + WorkspaceRef triple to mandatory spawn primitives — * these collaborators replace the old `Object.assign({sourceAgentId, * parentTaskId})` loose-cast cadence with a typed {@link Lineage} + * {@link SessionSummaryMaterializer} closure of the parent→child message gap. * * Phase 9 Known Delta #5: fields are now unconditional required. The legacy * "run without deps" compat branch was removed; every `AgentManager` consumer * (SDK internals and `@namzu/cli`; the list here once also named two packages * that do not exist) MUST wire the full set before instantiating. Convention #0 (no workarounds): the * partially-wired mode was a migration-window bridge; 0.2.0 closes it. * * `workspaceRegistry` is required but may be empty — spawns without a * registered workspace backend still succeed with `workspaceRef: undefined` * (the runtime uses `.has(backend)` to gate provisioning). This keeps the * registry deny-by-default while matching pattern doc §7.1 (lazy workspace * provisioning). */ interface AgentManagerBaseDeps { readonly sessionStore: SessionStore; readonly workspaceRegistry: WorkspaceBackendRegistry; readonly summaryMaterializer: SessionSummaryMaterializer; readonly capacity: CapacityValidator; /** * A pre-built logger. No in-package caller threads a real one today — * `packages/cli/src/integrations/subagents/runtime.ts` is the only * production `new AgentManager(...)` call, and CLI wiring is out of this * task's scope (`packages/cli` is not counted by `getRootLoggerCount`, * which is SDK-only) — but the field is genuinely host-reachable: it is * a plain object-literal parameter (no exported type import required to * satisfy it structurally) on `AgentManager`, which IS exported from * `public-runtime.ts`. Same standing as `TurnConfig.logger` when it was * first added. */ readonly log?: Logger; /** * The project layout child sessions are written into when neither the * child config (`sessionLog`, `paths`) nor the parent * (`AgentTaskContext.childStorage`) names one: the child's log at * `/subagents/.jsonl` and its * `.meta.json` beside it, nested under each ancestor. A parent * on disk hands down its own layout, which wins over this, because a * child's log nests under its parent's session directory. A parent held * in memory gives its children in-memory logs whatever this says. */ readonly paths?: SessionPaths; } /** * Dependencies for {@link AgentManager}. * * `topicManager` gates child-session creation on the parent Topic being open. * The deprecated `threadManager` spelling remains accepted for one migration * window through {@link TopicManagerDependency}; new callers use * `topicManager`. */ export type AgentManagerDeps = AgentManagerBaseDeps & TopicManagerDependency; interface ChildSpawnRecord { subSessionId: SubSessionId; childSessionId: SessionId; tenantId: TenantId; parentSessionId: SessionId; /** The parent turn whose tool call spawned the child. */ parentTurnId: TurnId; rootSessionId: SessionId; /** The parent's ancestors and the parent itself, root first: the child's `SessionLocator.ancestors`. */ ancestry: readonly SessionId[]; childDepth: number; /** * Where the child's log and meta document were placed, when this manager * placed them (a layout was known and the child named no storage). */ placement?: { readonly paths: SessionPaths; readonly metaPath: string; readonly createdAt: string; readonly toolCallId: string; readonly agentType: string; readonly description: string; }; /** Removes the child's log from the process-local lookup. */ releaseLog?: () => void; workspaceRef?: WorkspaceRef; /** * What this child was actually granted, after the ancestor union. * * Recorded rather than left implicit so a test — and an operator reading * a spawn record — can ask what a child was allowed, instead of * inferring it from whether a call happened to be refused. */ resolvedToolDenies?: readonly string[]; } export declare class AgentManager { private registry; private instances; private spawnRecords; private completionCallbacks; private listeners; private log; private config; private evictionTimers; /** One provisioning at a time per parent — see {@link provisionSpawn}. */ private spawnLocks; private deps; private topicManager; private readonly pendingSpawns; private readonly drainingParents; private readonly executingTasks; private readonly cancelingTasks; private admissionTimer; private disposed; constructor(registry: AgentRegistry, config: Partial | undefined, deps: AgentManagerDeps); sendMessage(options: SendMessageOptions, context: AgentTaskContext, listener?: SessionEventListener): Promise; private enqueueMessage; private pumpAdmissions; private drainAdmissions; private failAdmission; private startMessage; private rollbackUnstartedSpawn; private rollbackSpawnResources; /** Forget a spawn record, and take its child's log out of the process-local lookup. */ private dropSpawnRecord; /** The parent session's place in the tree: its ancestors, root first. */ private parentLocator; /** The meta document for a placed child, as it stands at spawn. */ private childMeta; /** * Decide where a child session's log lives, and write its meta document * when this manager places it. * * - A config that names a `sessionLog` keeps it; one that names `paths` * gets its log in that layout, under its parent. * - A parent held in memory gives a fresh `InMemorySessionLog`, plus its * checkpoint store when it named one. * - Otherwise, with {@link AgentManagerDeps.paths} known, the log goes to * `/subagents/.jsonl`, nested under every * ancestor, with `.meta.json` beside it. * * Returns the log the child should append to, or `undefined` to leave * the child's config as it is. */ private placeChildSession; /** * Open a child session placed by this manager with its `session_started`, * naming where it sits in the tree (`parent`: the spawning session, turn * and tool call, the root session, the depth, and the kind of spawn). * * Written here, before the child sessions, because only the manager knows all * of it: a child's config carries its parent session and turn but not the * tool call or the root. The child's own turn then finds the log started * and appends after it. A log that already has records is left alone. */ private startChildSessionLog; cancel(taskId: TaskId, cause?: CancelCause): void; cancelAll(parentSessionId: SessionId, cause?: CancelCause): void; continueTask(taskId: TaskId, message: string): Promise; queueMessage(taskId: TaskId, message: Message): void; drainMessages(taskId: TaskId): Message[]; waitForCompletion(taskId: TaskId): Promise; getInstance(taskId: TaskId): AgentTask | undefined; getSpawnRecord(taskId: TaskId): ChildSpawnRecord | undefined; listByParent(parentSessionId: SessionId): AgentTask[]; listActive(): AgentTask[]; getState(taskId: TaskId): AgentTaskState | undefined; getRegistry(): AgentRegistry; on(listener: AgentLifecycleListener): void; off(listener: AgentLifecycleListener): void; cleanup(): void; dispose(): void; /** * Serializes provisioning per parent session. * * The width cap counted existing children and then created one, with * every remaining provisioning step in between. Two concurrent spawns * under the same parent both read the same count, both saw room, and * both created — so a cap of N admitted N+1. The check and the write * that invalidates it have to be one critical section, and the parent * session is the narrowest key that makes them one: spawns under * different parents never contend. * * In-process only, which is the honest scope. Cross-process capacity is * the store's to enforce, and no store here spans processes. */ private provisionSpawn; private validateSpawn; private provisionSpawnUnlocked; private runChild; /** * Wraps the parent listener so every event relayed from the child session * carries its `lineage`. The child's own `seq` is dropped: it is a * position in the CHILD's log, and a parent listener keeping a reconnect * cursor per session must not read it as one in the parent's. Replaces the * old `Object.assign({sourceAgentId, parentTaskId}, event)` loose-cast * pattern entirely — the types now encode the linkage. */ private wrapChildListener; private finalizeChild; /** * The child's turn is over: its meta document records how, and the * parent hears `child_session_idled`, before the task is marked settled — * consumers expect `turn_completed (child) → child_session_idled → * turn_completed (parent)`. * * Idled on every outcome, not only success: the event says the child's * turn ended and nothing is queued, which a failure is too. It is the * parent writer's cue to append `child_session_ended`, read from the * child's own terminal record (`childSessionEnded`). */ private settleChildSession; private markCompleted; private markFailed; private failSubSession; /** * Release the workspace this manager provisioned for a child. * * Called on every terminal path, success included. `has(backend)` before * `get(backend)` because the registry is deny-by-default and throws on an * unknown kind — a driver deregistered mid-turn must not turn cleanup into * an exception on a child that already finished. * * Never throws. Disposal is cleanup, not part of the child's result: the * sub-session state is already persisted by the time this runs, and * failing here would report a delegation that worked as one that did not. * The failure is logged instead, because a worktree that could not be * removed is an operator's problem and silence is how it stays one. */ private disposeChildWorkspace; private markCanceled; private updateState; private requireInstance; private scheduleEviction; private resolveCompletionCallbacks; private clearEvictionTimer; private emit; private emitSessionEvent; } export { DelegationCapacityExceeded }; //# sourceMappingURL=lifecycle.d.ts.map