eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
128 lines (127 loc) • 4.95 kB
TypeScript
/**
* The subagent child-stream subsystem: for every `subagent.called` on the
* parent stream, a parallel pump over the child session folds its events
* into the renderer's nested subagent view. Extracted from the runner —
* the subsystem touches nothing but its own run state, the client, and the
* {@link SubagentView} seam.
*/
import type { Client } from "#client/index.js";
import { type ActionResultStreamEvent, type SubagentCalledStreamEvent } from "#protocol/message.js";
/**
* The renderer's subagent surface. One cohesive capability: a renderer that
* implements it renders whole sections — header, nested steps and tools,
* ghost sweeps, completion — and a renderer without it simply has no
* subagent view. Individually-optional methods would let a renderer
* implement a type-legal subset that ghosts placeholders or duplicates
* parent tool rows.
*/
export interface SubagentView {
/** Opens a call's section the moment its dispatch is announced. */
begin(update: {
callId: string;
name: string;
}): void;
upsertStep(update: SubagentStepUpdate): void;
upsertTool(update: SubagentToolUpdate): void;
/** Drops a child tool row whose call never materialized. */
removeTool(update: {
callId: string;
childCallId: string;
}): void;
/** Marks a call complete so its section collapses on `└ Done…`. */
complete(update: {
callId: string;
}): void;
/** Suppresses the parent-level tool row for a child-owned call id. */
markChildToolCallId(callId: string): void;
}
type SubagentChildStep = {
reasoning: string;
message: string;
finalized: boolean;
};
type SubagentToolStatus = "preparing" | "approval-requested" | "executing" | "done" | "failed" | "rejected";
type SubagentToolState = {
toolName: string;
input: unknown;
status: SubagentToolStatus;
output?: unknown;
errorText?: string;
};
export type SubagentRun = {
name: string;
/**
* The run's one lifecycle authority. `settled` means the final assistant
* message is in (the child's turn boundary, or the parent's
* `subagent.completed` fallback); a late child event — a HITL-parked
* turn resuming — explicitly reopens the run rather than mutating a
* completed section by accident.
*/
status: "running" | "settled";
/**
* One entry per logical "child message" — independent of the child's
* `stepIndex` field, which the harness can reuse across multiple
* assistant messages within a turn (e.g. a message before a tool call
* and another message after the tool result both arrive under
* `stepIndex: 0`). The key is a monotonic counter so each
* `message.completed` opens a new box on the next inbound delta.
*/
steps: Map<number, SubagentChildStep>;
/**
* Section currently accepting reasoning/message deltas. `null` means
* the next delta opens a new section.
*/
currentSectionKey: number | null;
/** Monotonic counter for new section keys. */
nextSectionKey: number;
tools: Map<string, SubagentToolState>;
};
export type SubagentStepUpdate = {
callId: string;
subagentName: string;
sectionKey: number;
reasoning: string;
message: string;
finalized: boolean;
};
export type SubagentToolUpdate = {
callId: string;
subagentName: string;
childCallId: string;
toolName: string;
input: unknown;
status: SubagentToolStatus;
output?: unknown;
errorText?: string;
};
export interface SubagentPumpOptions {
client?: Client;
view?: SubagentView;
formatActionResultError: (event: ActionResultStreamEvent) => string;
}
export declare class SubagentPump {
#private;
constructor(options: SubagentPumpOptions);
/**
* The moment a dispatch is known to be a subagent call, its section
* header replaces the parent-level tool row (or its still-preparing
* placeholder — subagent dispatches never upgrade one, since their
* actions are not tool-call kind). Without this the placeholder is
* swept at the step boundary and nothing shows until the child's first
* content arrives. Idempotent for SSE-resume re-entries, which only
* refresh the name.
*/
begin(called: SubagentCalledStreamEvent): void;
/**
* Parent reports subagent.completed. The child stream pump terminates
* itself on the child's own turn boundary — the authoritative finish
* signal, which already finalized the section — so this is a fallback
* for runs whose boundary never reached us (a dropped child stream, a
* HITL-parked turn resuming later). We do NOT abort the pump here,
* because the child's `message.completed` event may still be in flight
* (the parent and child streams are independent HTTP connections).
*/
settle(callId: string): void;
abortAll(): void;
}
export {};