@wrongstack/core 0.63.4 → 0.66.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-B5rxWrg3.d.ts → agent-bridge-D-j6OOBT.d.ts} +1 -1
- package/dist/agent-subagent-runner-DRZ9-NnR.d.ts +1042 -0
- package/dist/{compactor-0vjZ8KTk.d.ts → compactor-D_ExJajC.d.ts} +1 -1
- package/dist/{config-BdDuaZmB.d.ts → config--86aHSln.d.ts} +1 -1
- package/dist/{context-iFMEO2rN.d.ts → context-y87Jc5ei.d.ts} +3 -3
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +87 -69
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +22 -22
- package/dist/defaults/index.js +113 -84
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-k8CHjcrN.d.ts → events-CIplI98R.d.ts} +1 -1
- package/dist/execution/index.d.ts +16 -385
- package/dist/execution/index.js +59 -51
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/goal-store-C7jcumEh.d.ts +96 -0
- package/dist/{index-Bc6BiP5q.d.ts → index-DKUvyTvV.d.ts} +28 -442
- package/dist/{index-CWdW_CJt.d.ts → index-b5uhfTSl.d.ts} +8 -8
- package/dist/index.d.ts +34 -32
- package/dist/index.js +647 -677
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/{mcp-servers-CwqQDMYy.d.ts → mcp-servers-DwoNBf6r.d.ts} +3 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/{multi-agent-coordinator-CNUJYq7U.d.ts → multi-agent-coordinator-CWnH-CiX.d.ts} +10 -2
- package/dist/{null-fleet-bus-DRoJ0uOY.d.ts → null-fleet-bus-VApKRxcp.d.ts} +6 -7
- package/dist/observability/index.d.ts +2 -2
- package/dist/parallel-eternal-engine-0UwotoSx.d.ts +483 -0
- package/dist/{path-resolver-C5sPVne8.d.ts → path-resolver-DVkEcIw8.d.ts} +2 -2
- package/dist/{permission-Ld-i5ugf.d.ts → permission-C1A5whY5.d.ts} +5 -1
- package/dist/{permission-policy-CL-mPufp.d.ts → permission-policy-B2dK-T5N.d.ts} +19 -5
- package/dist/{plan-templates-ThBHOjaM.d.ts → plan-templates-Bprrzhbu.d.ts} +4 -4
- package/dist/{provider-runner-DJQa211J.d.ts → provider-runner-mXvXGSIw.d.ts} +3 -3
- package/dist/{retry-policy-BfBScewS.d.ts → retry-policy-CG3qvH_e.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +58 -51
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +31 -22
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-DxhW7ML3.d.ts → selector-RvBR_YRW.d.ts} +1 -1
- package/dist/session-event-bridge-CDHxcmQU.d.ts +93 -0
- package/dist/{session-reader-q2ThszgG.d.ts → session-reader-BIpwM60D.d.ts} +1 -1
- package/dist/storage/index.d.ts +7 -6
- package/dist/{system-prompt-7LHyBbIf.d.ts → system-prompt-b61lOd49.d.ts} +2 -2
- package/dist/types/index.d.ts +23 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/multi-agent/SKILL.md +0 -2
- package/dist/agent-subagent-runner-Zc3f37Sg.d.ts +0 -182
- package/dist/goal-store-iHltMi5n.d.ts +0 -188
- package/dist/multi-agent-SASYOrWA.d.ts +0 -554
- package/dist/tool-executor-CIjpGaRA.d.ts +0 -111
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
import { t as ToolRegistry, m as ProviderRegistry, f as AgentPipelines, q as ToolExecutorLike, E as ExtensionRegistry, d as AgentInit, e as AgentInput, R as RunResult, p as ToolCallPipelinePayload, u as ToolWrapper, S as SystemPromptContributor } from './index-DKUvyTvV.js';
|
|
2
|
+
import { C as Container, e as Renderer, R as ReadonlyPipeline } from './system-prompt-b61lOd49.js';
|
|
3
|
+
import { E as EventBus, k as EventName, L as Listener } from './events-CIplI98R.js';
|
|
4
|
+
import { a as RetryPolicy, E as ErrorHandler } from './retry-policy-CG3qvH_e.js';
|
|
5
|
+
import { a as Logger } from './logger-DDd5C--Z.js';
|
|
6
|
+
import { T as Tracer } from './observability-BhnVLBLS.js';
|
|
7
|
+
import { a as PermissionPolicy } from './permission-C1A5whY5.js';
|
|
8
|
+
import { d as Context, Q as Tool, u as RunOptions, J as JSONSchema, p as Request, q as Response, c as ContentBlock, T as TextBlock, m as Provider, $ as Usage } from './context-y87Jc5ei.js';
|
|
9
|
+
import { l as HookEvent, n as HookMatcher, I as InProcessHook, a as Config } from './config--86aHSln.js';
|
|
10
|
+
import { W as WireFamily } from './models-registry-BcYJDKLm.js';
|
|
11
|
+
|
|
12
|
+
declare class Agent {
|
|
13
|
+
readonly container: Container;
|
|
14
|
+
readonly tools: ToolRegistry;
|
|
15
|
+
readonly providers: ProviderRegistry;
|
|
16
|
+
readonly events: EventBus;
|
|
17
|
+
readonly pipelines: AgentPipelines;
|
|
18
|
+
readonly ctx: Context;
|
|
19
|
+
readonly maxIterations: number;
|
|
20
|
+
readonly executionStrategy: 'parallel' | 'sequential' | 'smart';
|
|
21
|
+
readonly perIterationOutputCapBytes: number;
|
|
22
|
+
private readonly plugins;
|
|
23
|
+
readonly toolExecutor: ToolExecutorLike;
|
|
24
|
+
readonly autoExtendLimit: boolean;
|
|
25
|
+
private readonly autonomousContinue;
|
|
26
|
+
readonly tracer: Tracer | undefined;
|
|
27
|
+
readonly extensions: ExtensionRegistry;
|
|
28
|
+
private readonly _toolHandler;
|
|
29
|
+
private readonly _responseHandler;
|
|
30
|
+
private readonly _loopHandler;
|
|
31
|
+
constructor(init: AgentInit);
|
|
32
|
+
get logger(): Logger;
|
|
33
|
+
get retry(): RetryPolicy;
|
|
34
|
+
get errorHandler(): ErrorHandler;
|
|
35
|
+
get permission(): PermissionPolicy;
|
|
36
|
+
get renderer(): Renderer | undefined;
|
|
37
|
+
disableInteractiveConfirmation(): void;
|
|
38
|
+
register(tool: Tool): void;
|
|
39
|
+
use(plugin: Plugin, api: PluginAPI): Promise<void>;
|
|
40
|
+
teardown(): Promise<void>;
|
|
41
|
+
run(userInput: AgentInput, opts?: RunOptions): Promise<RunResult>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A slash command registered with the CLI or available to plugins.
|
|
46
|
+
* Plugins receive a view of the registry via PluginAPI.slashCommands.
|
|
47
|
+
*
|
|
48
|
+
* Commands registered by plugins use a namespaced name: `pluginName:commandName`.
|
|
49
|
+
* This prevents collisions with built-in commands and other plugins.
|
|
50
|
+
*/
|
|
51
|
+
interface SlashCommand {
|
|
52
|
+
/** Unique command name. For plugins: `pluginName:commandName`. */
|
|
53
|
+
name: string;
|
|
54
|
+
/** Short aliases — also prefixed automatically: `pluginName:alias`. */
|
|
55
|
+
aliases?: string[];
|
|
56
|
+
description: string;
|
|
57
|
+
/**
|
|
58
|
+
* Optional compact argument hint for interactive menus. This is not parsed
|
|
59
|
+
* by the registry; it only helps TUI/REPL surfaces show the expected shape,
|
|
60
|
+
* for example `[list|install <alias>|disable <name>]`.
|
|
61
|
+
*/
|
|
62
|
+
argsHint?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Optional detailed help shown by `/help <name>`. Use this for usage,
|
|
65
|
+
* arguments, examples, side-effects — anything that doesn't fit in
|
|
66
|
+
* `description`. Renders verbatim, so format with line breaks.
|
|
67
|
+
* If absent, `/help <name>` falls back to `description`.
|
|
68
|
+
*/
|
|
69
|
+
help?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Execute the command.
|
|
72
|
+
* @param args Everything after the command name (trimmed by dispatch).
|
|
73
|
+
* @param ctx The current agent context.
|
|
74
|
+
* @returns `{ exit: true }` to quit the REPL. `{ message }` to print and
|
|
75
|
+
* continue. `{ runText }` to send a follow-up user-role message to the
|
|
76
|
+
* model immediately (e.g. `/steer <text>` builds a STEERING preamble
|
|
77
|
+
* here and asks the TUI to run it as the next turn). The TUI prints
|
|
78
|
+
* `message` first (if any) so the user sees the slash result before
|
|
79
|
+
* the model's response starts streaming. `{ metadata }` carries
|
|
80
|
+
* structured data for the REPL/TUI to act on (e.g. SDD session state).
|
|
81
|
+
*/
|
|
82
|
+
run(args: string, ctx: Context): Promise<{
|
|
83
|
+
exit?: boolean;
|
|
84
|
+
message?: string;
|
|
85
|
+
runText?: string;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
} | void>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface ToolRegistryView {
|
|
91
|
+
register(t: Tool): void;
|
|
92
|
+
unregister(name: string): void;
|
|
93
|
+
/** Wrap (decorate) an existing tool. The wrapper gets the current tool and returns the decorated version. */
|
|
94
|
+
wrap(name: string, wrapper: ToolWrapper): void;
|
|
95
|
+
get(name: string): Tool | undefined;
|
|
96
|
+
list(): Tool[];
|
|
97
|
+
}
|
|
98
|
+
interface ProviderFactory {
|
|
99
|
+
type: string;
|
|
100
|
+
family: WireFamily;
|
|
101
|
+
create(cfg: unknown): Provider;
|
|
102
|
+
}
|
|
103
|
+
interface ProviderRegistryView {
|
|
104
|
+
register(f: ProviderFactory): void;
|
|
105
|
+
create(cfg: {
|
|
106
|
+
type: string;
|
|
107
|
+
} & Record<string, unknown>): Provider;
|
|
108
|
+
list(): string[];
|
|
109
|
+
}
|
|
110
|
+
interface MCPRegistryView {
|
|
111
|
+
start(cfg: unknown): Promise<void>;
|
|
112
|
+
stop(name: string): Promise<void>;
|
|
113
|
+
restart(name: string): Promise<void>;
|
|
114
|
+
list(): {
|
|
115
|
+
name: string;
|
|
116
|
+
state: string;
|
|
117
|
+
toolCount: number;
|
|
118
|
+
}[];
|
|
119
|
+
}
|
|
120
|
+
interface SlashCommandRegistryView {
|
|
121
|
+
register(cmd: SlashCommand): void;
|
|
122
|
+
unregister(name: string): boolean;
|
|
123
|
+
get(name: string): SlashCommand | undefined;
|
|
124
|
+
list(): SlashCommand[];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Read-only view of the session writer. Plugins can append custom events
|
|
128
|
+
* to the JSONL session log and read the transcript path.
|
|
129
|
+
*
|
|
130
|
+
* The `append` method accepts any JSON-serializable payload — custom
|
|
131
|
+
* event types are persisted verbatim next to the built-in events.
|
|
132
|
+
*/
|
|
133
|
+
interface SessionWriterView {
|
|
134
|
+
readonly transcriptPath?: string;
|
|
135
|
+
append(event: Record<string, unknown> & {
|
|
136
|
+
type: string;
|
|
137
|
+
ts: string;
|
|
138
|
+
}): Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Metrics sink scoped to a plugin. The host auto-prefixes metric names
|
|
142
|
+
* with `plugin.<pluginName>.` so plugins don't need to namespace
|
|
143
|
+
* manually. Plugins call counter/histogram/gauge directly; the values
|
|
144
|
+
* flow to the host's MetricsSink (Prometheus, OTLP, or noop).
|
|
145
|
+
*/
|
|
146
|
+
interface MetricsSinkView {
|
|
147
|
+
counter(name: string, value?: number, labels?: Record<string, string>): void;
|
|
148
|
+
histogram(name: string, value: number, labels?: Record<string, string>): void;
|
|
149
|
+
gauge(name: string, value: number, labels?: Record<string, string>): void;
|
|
150
|
+
}
|
|
151
|
+
interface PluginPipelines {
|
|
152
|
+
request: ReadonlyPipeline<Request>;
|
|
153
|
+
response: ReadonlyPipeline<Response>;
|
|
154
|
+
toolCall: ReadonlyPipeline<ToolCallPipelinePayload>;
|
|
155
|
+
userInput: ReadonlyPipeline<{
|
|
156
|
+
content: ContentBlock[];
|
|
157
|
+
text: string;
|
|
158
|
+
ctx: Context;
|
|
159
|
+
}>;
|
|
160
|
+
assistantOutput: ReadonlyPipeline<TextBlock>;
|
|
161
|
+
contextWindow: ReadonlyPipeline<Context>;
|
|
162
|
+
[k: string]: ReadonlyPipeline<any>;
|
|
163
|
+
}
|
|
164
|
+
interface PluginAPI {
|
|
165
|
+
container: Container;
|
|
166
|
+
pipelines: PluginPipelines;
|
|
167
|
+
events: EventBus;
|
|
168
|
+
tools: ToolRegistryView;
|
|
169
|
+
providers: ProviderRegistryView;
|
|
170
|
+
mcp: MCPRegistryView;
|
|
171
|
+
slashCommands: SlashCommandRegistryView;
|
|
172
|
+
/** Live session writer — plugins can append custom events here. */
|
|
173
|
+
session: SessionWriterView;
|
|
174
|
+
/** Scoped metrics sink — counters/histograms/gauges auto-namespaced under `plugin.<name>.` */
|
|
175
|
+
metrics: MetricsSinkView;
|
|
176
|
+
/** Registry for agent lifecycle extensions — hooks like beforeRun, beforeIteration, onError, etc. */
|
|
177
|
+
extensions: ExtensionRegistry;
|
|
178
|
+
/**
|
|
179
|
+
* Register a system prompt contributor. Plugins call this to inject
|
|
180
|
+
* ephemeral TextBlocks into the system prompt on every build.
|
|
181
|
+
* Returns an unregister function.
|
|
182
|
+
*/
|
|
183
|
+
registerSystemPromptContributor(c: SystemPromptContributor): () => void;
|
|
184
|
+
/**
|
|
185
|
+
* Register an in-process lifecycle hook. `matcher` is a tool-name filter for
|
|
186
|
+
* `PreToolUse`/`PostToolUse` (`"Bash"`, `"edit|write"`, `"*"`) and ignored
|
|
187
|
+
* for other events. The hook can block, rewrite tool input, or inject extra
|
|
188
|
+
* context — see `HookOutcome`. Automatically removed when the plugin is
|
|
189
|
+
* uninstalled. Returns an unregister function.
|
|
190
|
+
*/
|
|
191
|
+
registerHook(event: HookEvent, matcher: HookMatcher | undefined, hook: InProcessHook): () => void;
|
|
192
|
+
config: Config;
|
|
193
|
+
log: Logger;
|
|
194
|
+
/**
|
|
195
|
+
* Register a one-time event listener. The handler is automatically removed
|
|
196
|
+
* after the first emission, or when the plugin is uninstalled — whichever
|
|
197
|
+
* comes first.
|
|
198
|
+
*/
|
|
199
|
+
onEvent<K extends EventName>(event: K, handler: Listener<K>): () => void;
|
|
200
|
+
/**
|
|
201
|
+
* Subscribe to all events matching a glob-style pattern.
|
|
202
|
+
* `'tool.*'` matches all tool events. `'*'` matches everything.
|
|
203
|
+
* Returns an unsubscribe function.
|
|
204
|
+
*/
|
|
205
|
+
onPattern(pattern: string, handler: (event: string, payload: unknown) => void): () => void;
|
|
206
|
+
/**
|
|
207
|
+
* Emit a custom event on the agent's EventBus. Use for inter-plugin
|
|
208
|
+
* communication or to surface plugin-specific state to the host.
|
|
209
|
+
*
|
|
210
|
+
* Custom events use a `pluginName:eventName` convention to avoid
|
|
211
|
+
* collisions with built-in events (e.g. `my-plugin:cache_hit`).
|
|
212
|
+
* The payload is passed through to all subscribers.
|
|
213
|
+
*/
|
|
214
|
+
emitCustom(event: string, payload: unknown): void;
|
|
215
|
+
/**
|
|
216
|
+
* Register a callback that fires when the configuration changes at
|
|
217
|
+
* runtime (e.g. via `/config` slash command or programmatic update).
|
|
218
|
+
* The handler receives the new and previous config snapshots.
|
|
219
|
+
* Returns an unsubscribe function.
|
|
220
|
+
*/
|
|
221
|
+
onConfigChange(handler: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Capability declaration — informs the host which subsystems a plugin
|
|
225
|
+
* intends to touch. Used for diagnostics and per-plugin enable/disable UX
|
|
226
|
+
* (e.g. "this plugin registers tools — disable to remove them"). Not
|
|
227
|
+
* enforced at runtime: a plugin that declares `tools: false` can still
|
|
228
|
+
* call `api.tools.register()`, but the host can flag the discrepancy.
|
|
229
|
+
*/
|
|
230
|
+
interface PluginCapabilities {
|
|
231
|
+
/** Will register tools via `api.tools.register()`. */
|
|
232
|
+
tools?: boolean;
|
|
233
|
+
/** Will register provider factories via `api.providers.register()`. */
|
|
234
|
+
providers?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Pipelines the plugin hooks into. Use the standard names
|
|
237
|
+
* (`request | response | toolCall | userInput | assistantOutput | contextWindow`)
|
|
238
|
+
* or custom pipeline names exposed by other plugins.
|
|
239
|
+
*/
|
|
240
|
+
pipelines?: string[];
|
|
241
|
+
/** Will register slash commands via `api.slashCommands.register()`. */
|
|
242
|
+
slashCommands?: boolean;
|
|
243
|
+
/** Will start MCP servers via `api.mcp.start()`. */
|
|
244
|
+
mcp?: boolean;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Structured dependency declaration. The string form (`dependsOn: ['foo']`)
|
|
248
|
+
* is shorthand for `[{ name: 'foo' }]` — both work. Use the structured form
|
|
249
|
+
* when you need a version constraint:
|
|
250
|
+
*
|
|
251
|
+
* dependsOn: [{ name: 'wstack-auth', version: '^1.2.0' }]
|
|
252
|
+
*/
|
|
253
|
+
interface PluginDependency {
|
|
254
|
+
name: string;
|
|
255
|
+
/** npm-style semver range. Supports `^`, `~`, exact, and unprefixed. */
|
|
256
|
+
version?: string;
|
|
257
|
+
}
|
|
258
|
+
interface Plugin {
|
|
259
|
+
name: string;
|
|
260
|
+
version?: string;
|
|
261
|
+
/** One-line summary for `wstack plugins list` and error messages. */
|
|
262
|
+
description?: string;
|
|
263
|
+
/** Semver range against the kernel API version (KERNEL_API_VERSION). */
|
|
264
|
+
apiVersion: string;
|
|
265
|
+
/**
|
|
266
|
+
* Capability hints — what subsystems the plugin will register against.
|
|
267
|
+
* Optional; provided for diagnostics and UX. The loader does not enforce
|
|
268
|
+
* these, but mismatch is surfaced via logger at warn level.
|
|
269
|
+
*/
|
|
270
|
+
capabilities?: PluginCapabilities;
|
|
271
|
+
/**
|
|
272
|
+
* JSON Schema for the options under `Config.plugins[<name>].options`.
|
|
273
|
+
* When present, the loader validates that section before calling `setup`
|
|
274
|
+
* and rejects the plugin with a clear error path on failure.
|
|
275
|
+
*/
|
|
276
|
+
configSchema?: JSONSchema;
|
|
277
|
+
/**
|
|
278
|
+
* Mandatory plugin dependencies — loading fails if any are absent or
|
|
279
|
+
* version-incompatible. Accepts both the legacy string-array form and
|
|
280
|
+
* the structured form with version constraints.
|
|
281
|
+
*/
|
|
282
|
+
dependsOn?: (string | PluginDependency)[];
|
|
283
|
+
/** Optional plugin dependencies — silently skipped if absent. */
|
|
284
|
+
optionalDeps?: (string | PluginDependency)[];
|
|
285
|
+
conflictsWith?: string[];
|
|
286
|
+
/**
|
|
287
|
+
* Default configuration values, deep-merged under the plugin's options
|
|
288
|
+
* key before `configSchema` validation. User-provided values take
|
|
289
|
+
* precedence over defaults — this is a fallback, not an override.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* defaultConfig: { ttl: 3600, maxSize: 100 }
|
|
293
|
+
*/
|
|
294
|
+
defaultConfig?: Record<string, unknown>;
|
|
295
|
+
setup(api: PluginAPI): void | Promise<void>;
|
|
296
|
+
teardown?(api: PluginAPI): void | Promise<void>;
|
|
297
|
+
/**
|
|
298
|
+
* Optional health check. Called by the host (e.g. `/diag plugins` slash
|
|
299
|
+
* command or health endpoint) to surface plugin status. Return
|
|
300
|
+
* `{ ok: false, message: '...' }` when the plugin is degraded.
|
|
301
|
+
*/
|
|
302
|
+
health?(): Promise<{
|
|
303
|
+
ok: boolean;
|
|
304
|
+
message?: string;
|
|
305
|
+
}>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
type BridgeMessageType = 'task' | 'result' | 'progress' | 'error' | 'heartbeat' | 'stop' | 'delegate' | 'budget_threshold';
|
|
309
|
+
interface BridgeMessage<T = unknown> {
|
|
310
|
+
id: string;
|
|
311
|
+
type: BridgeMessageType;
|
|
312
|
+
from: string;
|
|
313
|
+
to?: string;
|
|
314
|
+
payload: T;
|
|
315
|
+
timestamp: number;
|
|
316
|
+
priority?: 'low' | 'normal' | 'high' | 'critical';
|
|
317
|
+
}
|
|
318
|
+
interface AgentBridgeConfig {
|
|
319
|
+
agentId: string;
|
|
320
|
+
coordinatorId: string;
|
|
321
|
+
timeoutMs?: number;
|
|
322
|
+
bufferSize?: number;
|
|
323
|
+
}
|
|
324
|
+
interface AgentBridge {
|
|
325
|
+
readonly agentId: string;
|
|
326
|
+
readonly coordinatorId: string;
|
|
327
|
+
send(msg: BridgeMessage): Promise<void>;
|
|
328
|
+
broadcast(msg: BridgeMessage): Promise<void>;
|
|
329
|
+
subscribe(handler: (msg: BridgeMessage) => void | Promise<void>): () => void;
|
|
330
|
+
request<T>(msg: BridgeMessage, timeoutMs?: number): Promise<BridgeMessage<T>>;
|
|
331
|
+
stop(): Promise<void>;
|
|
332
|
+
}
|
|
333
|
+
interface BridgeTransport {
|
|
334
|
+
send(msg: BridgeMessage, to: string): Promise<void>;
|
|
335
|
+
subscribe(agentId: string, handler: (msg: BridgeMessage) => void | Promise<void>): () => void;
|
|
336
|
+
close(agentId: string): Promise<void>;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
type BudgetKind = 'tool_calls' | 'iterations' | 'tokens' | 'timeout' | 'idle_timeout' | 'cost';
|
|
340
|
+
declare class BudgetExceededError extends Error {
|
|
341
|
+
readonly kind: BudgetKind;
|
|
342
|
+
readonly limit: number;
|
|
343
|
+
readonly observed: number;
|
|
344
|
+
constructor(kind: BudgetKind, limit: number, observed: number);
|
|
345
|
+
}
|
|
346
|
+
interface BudgetLimits {
|
|
347
|
+
maxIterations?: number;
|
|
348
|
+
maxToolCalls?: number;
|
|
349
|
+
maxTokens?: number;
|
|
350
|
+
/** Estimated USD cost ceiling. */
|
|
351
|
+
maxCostUsd?: number;
|
|
352
|
+
/**
|
|
353
|
+
* Hard wall-clock timeout measured from `start()`. Off by default — set it
|
|
354
|
+
* explicitly only when a task must finish within an absolute window. For
|
|
355
|
+
* the everyday "don't kill an agent that's still working" guard, prefer
|
|
356
|
+
* `idleTimeoutMs`, which resets on activity.
|
|
357
|
+
*/
|
|
358
|
+
timeoutMs?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Idle timeout: the maximum gap (ms) between activity signals (iterations,
|
|
361
|
+
* tool calls, token usage, streamed progress) before the subagent is
|
|
362
|
+
* considered hung and reaped. Unlike `timeoutMs`, an actively-working
|
|
363
|
+
* agent continuously resets this clock via `markActivity()`, so it never
|
|
364
|
+
* trips on a long-but-productive run — only on a genuine stall.
|
|
365
|
+
*/
|
|
366
|
+
idleTimeoutMs?: number;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Controls how the budget behaves when `onThreshold` is set and a limit is hit.
|
|
370
|
+
*
|
|
371
|
+
* `'auto'` — emit `budget.threshold_reached` on the EventBus and wait for a
|
|
372
|
+
* coordinator response (extend/stop). If no listener responds within
|
|
373
|
+
* `DECISION_TIMEOUT_MS` the decision defaults to `'stop'`.
|
|
374
|
+
* `'sync'` — do not emit any event; treat the threshold as a hard stop and
|
|
375
|
+
* throw `BudgetExceededError` synchronously. Useful for fire-and-forget
|
|
376
|
+
* subagents that have an `onThreshold` handler for logging/metrics but are
|
|
377
|
+
* not wired into a coordinator.
|
|
378
|
+
*
|
|
379
|
+
* @default 'auto'
|
|
380
|
+
*/
|
|
381
|
+
type BudgetNegotiationMode = 'auto' | 'sync';
|
|
382
|
+
interface BudgetUsage {
|
|
383
|
+
iterations: number;
|
|
384
|
+
toolCalls: number;
|
|
385
|
+
tokens: {
|
|
386
|
+
input: number;
|
|
387
|
+
output: number;
|
|
388
|
+
total: number;
|
|
389
|
+
};
|
|
390
|
+
costUsd: number;
|
|
391
|
+
elapsedMs: number;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Thrown by `SubagentBudget.record*` when a soft limit is hit and
|
|
395
|
+
* an `onThreshold` handler is configured that wants to ask the
|
|
396
|
+
* coordinator (via `budget.threshold_reached` event). The runner
|
|
397
|
+
* catches this and awaits the embedded `decision` promise to get
|
|
398
|
+
* the coordinator's extend/stop decision.
|
|
399
|
+
*
|
|
400
|
+
* Distinct from `BudgetExceededError` which is a hard stop.
|
|
401
|
+
*/
|
|
402
|
+
declare class BudgetThresholdSignal extends Error {
|
|
403
|
+
readonly kind: BudgetKind;
|
|
404
|
+
readonly limit: number;
|
|
405
|
+
readonly used: number;
|
|
406
|
+
/** Resolves to 'extend' (with optional new limits) or 'stop' */
|
|
407
|
+
readonly decision: Promise<BudgetThresholdDecision>;
|
|
408
|
+
constructor(kind: BudgetKind, limit: number, used: number, decision: Promise<BudgetThresholdDecision>);
|
|
409
|
+
}
|
|
410
|
+
type BudgetThresholdDecision = 'stop' | {
|
|
411
|
+
extend: Partial<BudgetLimits>;
|
|
412
|
+
};
|
|
413
|
+
/**
|
|
414
|
+
* Callback invoked when a budget limit is about to be exceeded.
|
|
415
|
+
* Return 'throw' for hard stop (default — throws BudgetExceededError).
|
|
416
|
+
* Return 'continue' to allow one more unit and re-check next time.
|
|
417
|
+
* Return a Promise to ask the coordinator via `budget.threshold_reached`
|
|
418
|
+
* event (uses the same grant/deny pattern as `iteration.limit_reached`).
|
|
419
|
+
*/
|
|
420
|
+
type BudgetThresholdHandler = (info: {
|
|
421
|
+
kind: BudgetKind;
|
|
422
|
+
used: number;
|
|
423
|
+
limit: number;
|
|
424
|
+
requestDecision: () => Promise<BudgetThresholdDecision>;
|
|
425
|
+
}) => 'throw' | 'continue' | Promise<BudgetThresholdDecision>;
|
|
426
|
+
/**
|
|
427
|
+
* Per-subagent budget enforcement. Each subagent gets its own instance so a
|
|
428
|
+
* runaway agent can't drain the cost ceiling of its siblings. All record/check
|
|
429
|
+
* methods are O(1) and safe to call from hot paths.
|
|
430
|
+
*
|
|
431
|
+
* Behavior without `onThreshold`: hard stops synchronously on every limit hit.
|
|
432
|
+
*
|
|
433
|
+
* Behavior with `onThreshold` and `_mode === 'auto'`: emits `budget.threshold_reached`
|
|
434
|
+
* on the EventBus and throws `BudgetThresholdSignal`. The coordinator's verdict
|
|
435
|
+
* (extend/stop) resolves the embedded promise. If no listener responds within
|
|
436
|
+
* `DECISION_TIMEOUT_MS` the decision defaults to `'stop'`.
|
|
437
|
+
*
|
|
438
|
+
* Behavior with `onThreshold` and `_mode === 'sync'`: throws `BudgetExceededError`
|
|
439
|
+
* synchronously regardless of EventBus state or listener presence. This is useful
|
|
440
|
+
* for fire-and-forget subagents that have an `onThreshold` handler for logging/metrics
|
|
441
|
+
* but are not wired into a coordinator — the `'sync'` mode makes the hard-stop
|
|
442
|
+
* behavior explicit and means tests can use `expect().toThrow()` even without
|
|
443
|
+
* a fully-wired EventBus.
|
|
444
|
+
*/
|
|
445
|
+
declare class SubagentBudget {
|
|
446
|
+
readonly limits: Readonly<BudgetLimits>;
|
|
447
|
+
private iterations;
|
|
448
|
+
private toolCalls;
|
|
449
|
+
private tokenInput;
|
|
450
|
+
private tokenOutput;
|
|
451
|
+
private costUsd;
|
|
452
|
+
private startTime;
|
|
453
|
+
/**
|
|
454
|
+
* Timestamp of the most recent activity (iteration / tool call / token
|
|
455
|
+
* usage / streamed progress). Drives the idle timeout — reset by
|
|
456
|
+
* `markActivity()`. Initialised to `start()` time so a never-active agent
|
|
457
|
+
* still eventually trips its idle window.
|
|
458
|
+
*/
|
|
459
|
+
private lastActivityTime;
|
|
460
|
+
private _onThreshold;
|
|
461
|
+
/**
|
|
462
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
463
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
464
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
465
|
+
* leaves the budget over-limit and never enforces anything.
|
|
466
|
+
*/
|
|
467
|
+
private static readonly DECISION_TIMEOUT_MS;
|
|
468
|
+
/**
|
|
469
|
+
* Injected by the runner when wiring the budget to its EventBus.
|
|
470
|
+
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
471
|
+
*/
|
|
472
|
+
_events?: EventBus;
|
|
473
|
+
/**
|
|
474
|
+
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
475
|
+
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
476
|
+
* falls straight through to a synchronous hard stop.
|
|
477
|
+
*
|
|
478
|
+
* `'auto'` (default) — emit on the EventBus and wait; times out to 'stop'.
|
|
479
|
+
* `'sync'` — throw `BudgetExceededError` immediately regardless of listeners.
|
|
480
|
+
*/
|
|
481
|
+
private _mode;
|
|
482
|
+
/**
|
|
483
|
+
* Optional callback for soft-limit handling. When set, the budget will
|
|
484
|
+
* invoke it rather than throw immediately. The handler decides whether to
|
|
485
|
+
* throw synchronously, continue, or ask the coordinator for an extension.
|
|
486
|
+
*/
|
|
487
|
+
get onThreshold(): BudgetThresholdHandler | undefined;
|
|
488
|
+
set onThreshold(fn: BudgetThresholdHandler | undefined);
|
|
489
|
+
/** Returns the current negotiation mode. */
|
|
490
|
+
get mode(): BudgetNegotiationMode;
|
|
491
|
+
constructor(limits?: BudgetLimits, mode?: BudgetNegotiationMode);
|
|
492
|
+
start(): void;
|
|
493
|
+
/**
|
|
494
|
+
* Reset the idle clock. Called on any sign of forward progress —
|
|
495
|
+
* iterations, tool calls, token usage, and streamed tool/text progress —
|
|
496
|
+
* so a long-but-productive subagent never trips its `idleTimeoutMs`.
|
|
497
|
+
*/
|
|
498
|
+
markActivity(): void;
|
|
499
|
+
/**
|
|
500
|
+
* Milliseconds since the last activity signal. Returns 0 before `start()`
|
|
501
|
+
* (nothing to measure yet). Used by the coordinator watchdog to decide
|
|
502
|
+
* whether to re-arm (still active) or reap (genuinely idle).
|
|
503
|
+
*/
|
|
504
|
+
idleMs(): number;
|
|
505
|
+
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
506
|
+
isNearLimit(): boolean;
|
|
507
|
+
/**
|
|
508
|
+
* Synchronous budget check. Always throws synchronously so callers (especially
|
|
509
|
+
* test event handlers using `expect().toThrow()`) get an unhandled rejection
|
|
510
|
+
* when the budget is exceeded without a handler.
|
|
511
|
+
*
|
|
512
|
+
* Decision table:
|
|
513
|
+
* - no `onThreshold` handler → throw `BudgetExceededError` (hard stop, always)
|
|
514
|
+
* - `mode === 'sync'` → throw `BudgetExceededError` (hard stop, always)
|
|
515
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
516
|
+
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
517
|
+
*/
|
|
518
|
+
/**
|
|
519
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
520
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
521
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
522
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
523
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
524
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
525
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
526
|
+
*
|
|
527
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
528
|
+
*/
|
|
529
|
+
private checkLimits;
|
|
530
|
+
/**
|
|
531
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
532
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
533
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
534
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
535
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
536
|
+
*/
|
|
537
|
+
private _pendingNegotiations;
|
|
538
|
+
/**
|
|
539
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
540
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
541
|
+
* patched in-place; the runner should not abort). Clears the
|
|
542
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
543
|
+
*
|
|
544
|
+
* The 'continue' return from a sync handler is treated as
|
|
545
|
+
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
546
|
+
* a fresh signal.
|
|
547
|
+
*/
|
|
548
|
+
private _negotiateExtension;
|
|
549
|
+
recordIteration(): void;
|
|
550
|
+
recordToolCall(): void;
|
|
551
|
+
recordUsage(usage: Usage, costUsd?: number): void;
|
|
552
|
+
/**
|
|
553
|
+
* Wall-clock / idle budget check. Delegates to `checkLimits(elapsed)`, so
|
|
554
|
+
* `timeout` and `idle_timeout` follow the SAME negotiation path as the other
|
|
555
|
+
* kinds — they are NOT a special-cased hard stop. This is deliberate: a
|
|
556
|
+
* heartbeat-aware policy (see `attachAutoExtend` and `CollabSession`) grants
|
|
557
|
+
* a timeout extension only while the agent is making progress and denies it
|
|
558
|
+
* once the agent is genuinely stuck, which is safer than an unconditional
|
|
559
|
+
* hard kill of a long-but-working agent. The runner translates the resulting
|
|
560
|
+
* `BudgetThresholdSignal` decision (`extend` → patch limits in place,
|
|
561
|
+
* `stop` → abort) just like every other kind.
|
|
562
|
+
*
|
|
563
|
+
* Decision table (same as `checkLimits`):
|
|
564
|
+
* - no `onThreshold` handler → throw `BudgetExceededError` (hard stop)
|
|
565
|
+
* - `mode === 'sync'` → throw `BudgetExceededError` (hard stop)
|
|
566
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (no one to ask)
|
|
567
|
+
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` (negotiated;
|
|
568
|
+
* a heartbeat-aware policy may extend the timeout)
|
|
569
|
+
*/
|
|
570
|
+
checkTimeout(): void;
|
|
571
|
+
/** Returns true if a wall-clock or idle timeout has occurred without throwing. */
|
|
572
|
+
isTimedOut(): boolean;
|
|
573
|
+
usage(): BudgetUsage;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
interface SubagentConfig {
|
|
577
|
+
id?: string;
|
|
578
|
+
name: string;
|
|
579
|
+
role?: string;
|
|
580
|
+
prompt?: string;
|
|
581
|
+
maxIterations?: number;
|
|
582
|
+
maxToolCalls?: number;
|
|
583
|
+
maxTokens?: number;
|
|
584
|
+
maxCostUsd?: number;
|
|
585
|
+
/** Hard wall-clock cap (ms) from start. Opt-in; prefer `idleTimeoutMs`. */
|
|
586
|
+
timeoutMs?: number;
|
|
587
|
+
/**
|
|
588
|
+
* Idle timeout (ms): reap the subagent only after this long with no
|
|
589
|
+
* activity. Resets on every iteration / tool call / streamed progress, so
|
|
590
|
+
* an actively-working agent runs until its task naturally ends. This is the
|
|
591
|
+
* default reaper for delegated subagents (see `applyRosterBudget`).
|
|
592
|
+
*/
|
|
593
|
+
idleTimeoutMs?: number;
|
|
594
|
+
tools?: string[];
|
|
595
|
+
model?: string;
|
|
596
|
+
priority?: number;
|
|
597
|
+
/**
|
|
598
|
+
* Working directory for this subagent's tools. Defaults to the factory's
|
|
599
|
+
* cwd. AutoPhase sets this to a per-phase git worktree so parallel phases
|
|
600
|
+
* edit isolated checkouts instead of clobbering one shared working tree.
|
|
601
|
+
* `projectRoot` is intentionally left unchanged — tools resolve the
|
|
602
|
+
* worktree's `.git` gitlink from `cwd` while staying bounded to the repo.
|
|
603
|
+
*/
|
|
604
|
+
cwd?: string;
|
|
605
|
+
/**
|
|
606
|
+
* Provider registry id (e.g. `'anthropic'`, `'openai'`, `'google'`).
|
|
607
|
+
* Allows a director to mix providers across siblings — one subagent on
|
|
608
|
+
* Sonnet, another on GPT-5, another on Haiku. Falls back to the
|
|
609
|
+
* factory's default provider when omitted, which is the legacy
|
|
610
|
+
* single-provider behavior.
|
|
611
|
+
*/
|
|
612
|
+
provider?: string;
|
|
613
|
+
/**
|
|
614
|
+
* Per-subagent session JSONL path. When omitted the orchestrator-
|
|
615
|
+
* supplied factory derives a path under `<sessionRoot>/<runId>/`.
|
|
616
|
+
* Override to redirect the transcript elsewhere (long-term storage,
|
|
617
|
+
* a different filesystem, etc.).
|
|
618
|
+
*/
|
|
619
|
+
sessionPath?: string;
|
|
620
|
+
/**
|
|
621
|
+
* Additional text appended to the role's base system prompt. Does not
|
|
622
|
+
* replace it. Useful for last-mile guidance like "you may only call
|
|
623
|
+
* read tools, never write" or "respond in JSON only".
|
|
624
|
+
*/
|
|
625
|
+
systemPromptOverride?: string;
|
|
626
|
+
/**
|
|
627
|
+
* Routing for streaming output. `'director'` (default) forwards
|
|
628
|
+
* text/tool events to the parent's FleetBus so the director can read
|
|
629
|
+
* the subagent's stream. `'silent'` keeps everything subagent-local;
|
|
630
|
+
* the director only sees the final task result. `'user'` forwards
|
|
631
|
+
* direct to the user-facing renderer (gate this behind an explicit
|
|
632
|
+
* config flag — it can confuse the chat surface).
|
|
633
|
+
*/
|
|
634
|
+
textStream?: 'director' | 'silent' | 'user';
|
|
635
|
+
toolStream?: 'director' | 'silent' | 'user';
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Discriminator for every distinct failure mode a subagent can hit. The
|
|
639
|
+
* coordinator's classifier (`classifySubagentError` in
|
|
640
|
+
* coordination/multi-agent-coordinator.ts) maps raw exceptions to one of
|
|
641
|
+
* these — callers (delegate tool, /agents UI, retry policies) can then
|
|
642
|
+
* branch on `kind` instead of grepping `error.message`. Each kind
|
|
643
|
+
* documents its retryability so an orchestrator can act on it without
|
|
644
|
+
* extra knowledge.
|
|
645
|
+
*/
|
|
646
|
+
type SubagentErrorKind =
|
|
647
|
+
/** Provider returned 5xx. Transient server-side issue — safe to retry with backoff. */
|
|
648
|
+
'provider_5xx'
|
|
649
|
+
/** Provider returned 429. Rate-limited — retry with `backoffMs` delay. */
|
|
650
|
+
| 'provider_rate_limit'
|
|
651
|
+
/** Provider call timed out at the network layer (TCP / TLS / read). Retry safe. */
|
|
652
|
+
| 'provider_timeout'
|
|
653
|
+
/** Provider rejected the credentials (401/403). NOT retryable — config fix required. */
|
|
654
|
+
| 'provider_auth'
|
|
655
|
+
/** Model returned a "context length exceeded" error. Retrying without trimming will fail again. */
|
|
656
|
+
| 'context_overflow'
|
|
657
|
+
/** A tool's `execute()` returned `ok:false`. Logical task failure, not a crash. */
|
|
658
|
+
| 'tool_failed'
|
|
659
|
+
/** A tool's `execute()` threw an exception. Often retryable but cause-dependent. */
|
|
660
|
+
| 'tool_threw'
|
|
661
|
+
/** Hit the per-subagent `maxIterations` budget. Either raise budget or narrow task. */
|
|
662
|
+
| 'budget_iterations'
|
|
663
|
+
/** Hit the per-subagent `maxToolCalls` budget. Either raise budget or narrow task. */
|
|
664
|
+
| 'budget_tool_calls'
|
|
665
|
+
/** Hit the per-subagent `maxTokens` budget. */
|
|
666
|
+
| 'budget_tokens'
|
|
667
|
+
/** Hit the per-subagent `maxCostUsd` budget. */
|
|
668
|
+
| 'budget_cost'
|
|
669
|
+
/** Hit the per-subagent `timeoutMs` wall-clock budget. */
|
|
670
|
+
| 'budget_timeout'
|
|
671
|
+
/** Parent agent's AbortController fired (user Ctrl+C, parent unwound, sibling failure cascade). */
|
|
672
|
+
| 'aborted_by_parent'
|
|
673
|
+
/** LLM returned end_turn with no textual content. Often a prompt issue. */
|
|
674
|
+
| 'empty_response'
|
|
675
|
+
/** Parent-child bridge transport failed (rare — IPC / writer crash). */
|
|
676
|
+
| 'bridge_failed'
|
|
677
|
+
/** Everything else. Classifier fallback — should narrow over time as new modes appear. */
|
|
678
|
+
| 'unknown';
|
|
679
|
+
/**
|
|
680
|
+
* Structured failure envelope. Replaces the prior `error?: string` so
|
|
681
|
+
* callers can switch on `kind`, respect `retryable`, and apply
|
|
682
|
+
* provider-suggested `backoffMs` instead of guessing from substring
|
|
683
|
+
* matches on the message.
|
|
684
|
+
*/
|
|
685
|
+
interface SubagentError {
|
|
686
|
+
/** Discriminator — see SubagentErrorKind doc strings for semantics. */
|
|
687
|
+
kind: SubagentErrorKind;
|
|
688
|
+
/** Human-readable summary, suitable for direct UI display. Always populated. */
|
|
689
|
+
message: string;
|
|
690
|
+
/** True if the operation can be retried as-is (possibly with backoff). */
|
|
691
|
+
retryable: boolean;
|
|
692
|
+
/** Suggested backoff before retry, in ms. Set for `provider_rate_limit` and `provider_5xx`. */
|
|
693
|
+
backoffMs?: number;
|
|
694
|
+
/** Original cause snapshot for diagnostics — never used for control flow. */
|
|
695
|
+
cause?: {
|
|
696
|
+
name: string;
|
|
697
|
+
message: string;
|
|
698
|
+
stack?: string;
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
interface TaskResult<T = unknown> {
|
|
702
|
+
subagentId: string;
|
|
703
|
+
taskId: string;
|
|
704
|
+
status: 'success' | 'failed' | 'timeout' | 'stopped';
|
|
705
|
+
result?: T;
|
|
706
|
+
/**
|
|
707
|
+
* Structured failure envelope. Populated whenever `status !== 'success'`.
|
|
708
|
+
* Prefer reading `error.kind` over substring-matching `error.message`.
|
|
709
|
+
*/
|
|
710
|
+
error?: SubagentError;
|
|
711
|
+
iterations: number;
|
|
712
|
+
toolCalls: number;
|
|
713
|
+
durationMs: number;
|
|
714
|
+
}
|
|
715
|
+
interface TaskSpec {
|
|
716
|
+
id: string;
|
|
717
|
+
description: string;
|
|
718
|
+
subagentId?: string;
|
|
719
|
+
priority?: number;
|
|
720
|
+
maxToolCalls?: number;
|
|
721
|
+
timeoutMs?: number;
|
|
722
|
+
context?: Record<string, unknown>;
|
|
723
|
+
}
|
|
724
|
+
interface DoneCondition {
|
|
725
|
+
type: 'iterations' | 'tool_calls' | 'output_match' | 'custom' | 'all_tasks_done' | 'directive';
|
|
726
|
+
maxIterations?: number;
|
|
727
|
+
maxToolCalls?: number;
|
|
728
|
+
pattern?: string;
|
|
729
|
+
predicate?: string;
|
|
730
|
+
/**
|
|
731
|
+
* For `directive` type — stop when model emits [done] and keep going
|
|
732
|
+
* on [continue]/[next step]/[proceed] WITHOUT returning to the outer runner.
|
|
733
|
+
* When false (default), the runner behaves normally (one agent.run per loop).
|
|
734
|
+
* When true, the runner passes `autonomousContinue: true` to the agent and
|
|
735
|
+
* re-runs internally when the model signals continue.
|
|
736
|
+
*/
|
|
737
|
+
autonomous?: boolean;
|
|
738
|
+
}
|
|
739
|
+
interface MultiAgentConfig {
|
|
740
|
+
coordinatorId: string;
|
|
741
|
+
leaderSystemPrompt?: string;
|
|
742
|
+
subagents?: SubagentConfig[];
|
|
743
|
+
maxConcurrent?: number;
|
|
744
|
+
doneCondition: DoneCondition;
|
|
745
|
+
timeoutMs?: number;
|
|
746
|
+
/**
|
|
747
|
+
* Optional default budget applied to every spawned subagent. Per-subagent
|
|
748
|
+
* fields in `SubagentConfig` override these. Coordinator enforces them by
|
|
749
|
+
* constructing a `SubagentBudget` per spawn — see `SubagentRunContext.budget`.
|
|
750
|
+
*/
|
|
751
|
+
defaultBudget?: {
|
|
752
|
+
maxIterations?: number;
|
|
753
|
+
maxToolCalls?: number;
|
|
754
|
+
maxTokens?: number;
|
|
755
|
+
maxCostUsd?: number;
|
|
756
|
+
timeoutMs?: number;
|
|
757
|
+
idleTimeoutMs?: number;
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
interface SpawnResult {
|
|
761
|
+
subagentId: string;
|
|
762
|
+
agentId: string;
|
|
763
|
+
}
|
|
764
|
+
interface TaskDelegation {
|
|
765
|
+
task: TaskSpec;
|
|
766
|
+
subagentId: string;
|
|
767
|
+
}
|
|
768
|
+
interface CoordinatorEvents {
|
|
769
|
+
'task.assigned': {
|
|
770
|
+
task: TaskSpec;
|
|
771
|
+
subagentId: string;
|
|
772
|
+
};
|
|
773
|
+
'task.completed': {
|
|
774
|
+
task: TaskSpec;
|
|
775
|
+
result: TaskResult;
|
|
776
|
+
};
|
|
777
|
+
'subagent.started': {
|
|
778
|
+
subagent: SubagentConfig;
|
|
779
|
+
};
|
|
780
|
+
'subagent.stopped': {
|
|
781
|
+
subagentId: string;
|
|
782
|
+
reason: string;
|
|
783
|
+
};
|
|
784
|
+
done: {
|
|
785
|
+
results: TaskResult[];
|
|
786
|
+
totalIterations: number;
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
interface MultiAgentCoordinator {
|
|
790
|
+
readonly coordinatorId: string;
|
|
791
|
+
readonly config: MultiAgentConfig;
|
|
792
|
+
spawn(subagent: SubagentConfig): Promise<SpawnResult>;
|
|
793
|
+
assign(task: TaskSpec): Promise<void>;
|
|
794
|
+
delegate(to: string, msg: BridgeMessage): Promise<void>;
|
|
795
|
+
stop(subagentId: string): Promise<void>;
|
|
796
|
+
stopAll(): Promise<void>;
|
|
797
|
+
/**
|
|
798
|
+
* Stop a subagent and remove it from the coordinator. Releases all
|
|
799
|
+
* associated resources. The subagent id can be reused in a future spawn.
|
|
800
|
+
*/
|
|
801
|
+
remove(subagentId: string): Promise<void>;
|
|
802
|
+
getStatus(): CoordinatorStatus;
|
|
803
|
+
/**
|
|
804
|
+
* Wait for one or more tasks to complete and return their results.
|
|
805
|
+
* If a task is already done when called, returns immediately.
|
|
806
|
+
* Resolves to an array in the same order as `taskIds`.
|
|
807
|
+
*/
|
|
808
|
+
awaitTasks(taskIds: string[]): Promise<TaskResult[]>;
|
|
809
|
+
/** Snapshot of completed task results. */
|
|
810
|
+
results(): readonly TaskResult[];
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Caller-supplied runner that actually executes a task. The coordinator
|
|
814
|
+
* provides isolated state (own budget, own AbortSignal, own bridge handle)
|
|
815
|
+
* and enforces concurrency limits — the runner just runs the task and reports
|
|
816
|
+
* the outcome. This is the injection seam that decouples the coordinator
|
|
817
|
+
* from `Agent` so it can be tested with mocks and reused for non-Agent
|
|
818
|
+
* subagents (workers, MCP-driven subagents, etc.).
|
|
819
|
+
*/
|
|
820
|
+
type SubagentRunner = (task: TaskSpec, ctx: SubagentRunContext) => Promise<SubagentRunOutcome>;
|
|
821
|
+
interface SubagentRunContext {
|
|
822
|
+
subagentId: string;
|
|
823
|
+
config: SubagentConfig;
|
|
824
|
+
budget: SubagentBudget;
|
|
825
|
+
signal: AbortSignal;
|
|
826
|
+
/** Null until `setSubagentBridge` is called for this subagent. */
|
|
827
|
+
bridge: AgentBridge | null;
|
|
828
|
+
}
|
|
829
|
+
interface SubagentRunOutcome {
|
|
830
|
+
result?: unknown;
|
|
831
|
+
iterations: number;
|
|
832
|
+
toolCalls: number;
|
|
833
|
+
}
|
|
834
|
+
interface CoordinatorStatus {
|
|
835
|
+
coordinatorId: string;
|
|
836
|
+
subagents: {
|
|
837
|
+
id: string;
|
|
838
|
+
name: string;
|
|
839
|
+
status: 'running' | 'idle' | 'stopped' | 'error';
|
|
840
|
+
currentTask?: string;
|
|
841
|
+
/** Cumulative budget auto-extensions granted to this subagent, when the
|
|
842
|
+
* status is produced by a Director that tracks them. */
|
|
843
|
+
extensions?: number;
|
|
844
|
+
}[];
|
|
845
|
+
pendingTasks: number;
|
|
846
|
+
completedTasks: number;
|
|
847
|
+
totalIterations: number;
|
|
848
|
+
done: boolean;
|
|
849
|
+
}
|
|
850
|
+
interface SubagentContext {
|
|
851
|
+
subagentId: string;
|
|
852
|
+
tasks: TaskSpec[];
|
|
853
|
+
/**
|
|
854
|
+
* Two-phase initialization: `spawn()` creates the subagent before the
|
|
855
|
+
* bridge is wired (`setSubagentBridge()`), so `parentBridge` is nullable
|
|
856
|
+
* by design. Readers must `hasParentBridge()`-guard or null-check before
|
|
857
|
+
* use; the prior `null as unknown as AgentBridge` cast was a type lie
|
|
858
|
+
* that hid this from the compiler.
|
|
859
|
+
*/
|
|
860
|
+
parentBridge: AgentBridge | null;
|
|
861
|
+
doneCondition: DoneCondition;
|
|
862
|
+
maxConcurrent: number;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Single fleet-wide event with subagent attribution. Whatever a child
|
|
867
|
+
* agent emits on its own EventBus gets re-published here, prefixed with
|
|
868
|
+
* `subagentId` so a single subscriber can multiplex across the fleet.
|
|
869
|
+
*
|
|
870
|
+
* The director uses `FleetBus.filter('tool.executed', …)` to see every
|
|
871
|
+
* tool call across the fleet; the TUI uses
|
|
872
|
+
* `FleetBus.subscribe(id, handler)` to render a per-subagent panel.
|
|
873
|
+
*/
|
|
874
|
+
interface FleetEvent {
|
|
875
|
+
subagentId: string;
|
|
876
|
+
taskId?: string;
|
|
877
|
+
ts: number;
|
|
878
|
+
type: string;
|
|
879
|
+
payload: unknown;
|
|
880
|
+
}
|
|
881
|
+
type FleetHandler = (event: FleetEvent) => void;
|
|
882
|
+
/**
|
|
883
|
+
* Fan-in for per-subagent EventBuses. Each subagent's bus is plugged in
|
|
884
|
+
* via `attach()`; the FleetBus re-emits every event with subagent
|
|
885
|
+
* attribution. Detachment is automatic via the returned disposer — call
|
|
886
|
+
* it when a subagent terminates so we don't leak listeners.
|
|
887
|
+
*
|
|
888
|
+
* The bus exposes two subscription modes: by `subagentId` (everything
|
|
889
|
+
* from one child) and by `type` (one event-type across the fleet). They
|
|
890
|
+
* compose — if you need a per-subagent + per-type slice, subscribe by
|
|
891
|
+
* type and filter on `event.subagentId` in your handler.
|
|
892
|
+
*/
|
|
893
|
+
declare class FleetBus {
|
|
894
|
+
private readonly byId;
|
|
895
|
+
private readonly byType;
|
|
896
|
+
private readonly any;
|
|
897
|
+
/**
|
|
898
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
899
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
900
|
+
* new kernel event types are automatically forwarded without any manual
|
|
901
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
902
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
903
|
+
*
|
|
904
|
+
* Returns a disposer that detaches every subscription; call on
|
|
905
|
+
* subagent teardown so the listeners don't outlive the run.
|
|
906
|
+
*/
|
|
907
|
+
attach(subagentId: string, bus: EventBus, taskId?: string): () => void;
|
|
908
|
+
/** Subscribe to every event from one subagent. */
|
|
909
|
+
subscribe(subagentId: string, handler: FleetHandler): () => void;
|
|
910
|
+
/** Subscribe to one event type across all subagents. */
|
|
911
|
+
filter(type: string, handler: FleetHandler): () => void;
|
|
912
|
+
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
913
|
+
onAny(handler: FleetHandler): () => void;
|
|
914
|
+
emit(event: FleetEvent): void;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Roll-up of token usage + cost across an entire director run. The
|
|
918
|
+
* director's `fleet_status` tool returns this so the model can reason
|
|
919
|
+
* about budget in its next turn ("the researcher already burned $0.40,
|
|
920
|
+
* lean on summaries for the next task").
|
|
921
|
+
*/
|
|
922
|
+
interface FleetUsage {
|
|
923
|
+
total: {
|
|
924
|
+
input: number;
|
|
925
|
+
output: number;
|
|
926
|
+
cacheRead: number;
|
|
927
|
+
cacheWrite: number;
|
|
928
|
+
cost: number;
|
|
929
|
+
};
|
|
930
|
+
perSubagent: Record<string, SubagentUsageSnapshot>;
|
|
931
|
+
}
|
|
932
|
+
interface SubagentUsageSnapshot {
|
|
933
|
+
subagentId: string;
|
|
934
|
+
provider?: string;
|
|
935
|
+
model?: string;
|
|
936
|
+
input: number;
|
|
937
|
+
output: number;
|
|
938
|
+
cacheRead: number;
|
|
939
|
+
cacheWrite: number;
|
|
940
|
+
cost: number;
|
|
941
|
+
toolCalls: number;
|
|
942
|
+
iterations: number;
|
|
943
|
+
startedAt: number;
|
|
944
|
+
lastEventAt: number;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Aggregates provider.response + tool.executed events from the FleetBus
|
|
948
|
+
* into a live `FleetUsage` snapshot. Costs are computed by the caller
|
|
949
|
+
* via a `priceLookup(subagentId)` so we don't bake provider-pricing
|
|
950
|
+
* coupling into core; the CLI/tests supply a function that resolves
|
|
951
|
+
* each subagent's per-token rates from the models registry.
|
|
952
|
+
*/
|
|
953
|
+
declare class FleetUsageAggregator {
|
|
954
|
+
private readonly priceLookup?;
|
|
955
|
+
private readonly metaLookup?;
|
|
956
|
+
private readonly perSubagent;
|
|
957
|
+
private readonly total;
|
|
958
|
+
private readonly unsub;
|
|
959
|
+
constructor(bus: FleetBus, priceLookup?: ((subagentId: string, provider?: string, model?: string) => {
|
|
960
|
+
input?: number;
|
|
961
|
+
output?: number;
|
|
962
|
+
cacheRead?: number;
|
|
963
|
+
cacheWrite?: number;
|
|
964
|
+
} | undefined) | undefined, metaLookup?: ((subagentId: string) => {
|
|
965
|
+
provider?: string;
|
|
966
|
+
model?: string;
|
|
967
|
+
} | undefined) | undefined);
|
|
968
|
+
/**
|
|
969
|
+
* Remove a terminated subagent's data from the aggregator and subtract its
|
|
970
|
+
* contribution from the running totals. Call this when a subagent is removed
|
|
971
|
+
* from the fleet so the aggregator doesn't accumulate unbounded data for
|
|
972
|
+
* entities that will never emit events again.
|
|
973
|
+
*/
|
|
974
|
+
removeSubagent(subagentId: string): void;
|
|
975
|
+
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
976
|
+
dispose(): void;
|
|
977
|
+
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
978
|
+
snapshot(): FleetUsage;
|
|
979
|
+
private ensure;
|
|
980
|
+
private onProviderResponse;
|
|
981
|
+
private onToolExecuted;
|
|
982
|
+
private onIterationStarted;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Caller-supplied factory that builds an isolated `Agent` for a subagent.
|
|
987
|
+
* The factory MUST construct a fresh `Context` per call — sharing context
|
|
988
|
+
* between subagents defeats isolation. Each Agent should also use either
|
|
989
|
+
* its own `EventBus` or a forwarded view, so per-subagent metrics can be
|
|
990
|
+
* attributed correctly.
|
|
991
|
+
*/
|
|
992
|
+
type AgentFactory = (config: SubagentConfig) => Promise<AgentFactoryResult>;
|
|
993
|
+
interface AgentFactoryResult {
|
|
994
|
+
agent: Agent;
|
|
995
|
+
/** Event bus the factory wired to this agent — required for budget hookup. */
|
|
996
|
+
events: EventBus;
|
|
997
|
+
/**
|
|
998
|
+
* Optional cleanup hook invoked in the runner's `finally` block once
|
|
999
|
+
* the task ends (success, failure, abort — same exit path). Factories
|
|
1000
|
+
* that own resources scoped to a single task (per-subagent JSONL
|
|
1001
|
+
* writers, transient providers, throwaway containers) implement this
|
|
1002
|
+
* to close them deterministically instead of relying on GC. Errors
|
|
1003
|
+
* thrown here are swallowed so a flaky cleanup can't mask the task's
|
|
1004
|
+
* real result.
|
|
1005
|
+
*/
|
|
1006
|
+
dispose?: () => Promise<void> | void;
|
|
1007
|
+
}
|
|
1008
|
+
interface AgentRunnerOptions {
|
|
1009
|
+
factory: AgentFactory;
|
|
1010
|
+
/**
|
|
1011
|
+
* Format a TaskSpec into the user input the agent will receive. Defaults
|
|
1012
|
+
* to `task.description ?? ''`. Override when subagents expect structured
|
|
1013
|
+
* input (e.g. JSON contracts, role-prefixed prompts).
|
|
1014
|
+
*/
|
|
1015
|
+
formatTaskInput?: (task: TaskSpec, config: SubagentConfig) => AgentInput;
|
|
1016
|
+
/**
|
|
1017
|
+
* When set, the runner attaches the subagent's EventBus to this FleetBus
|
|
1018
|
+
* on task start and detaches it when the task finishes. This is the
|
|
1019
|
+
* injection seam that lets the TUI fleet panel observe subagent activity
|
|
1020
|
+
* live — without it, FleetBus stays empty.
|
|
1021
|
+
*/
|
|
1022
|
+
fleetBus?: FleetBus;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Builds a `SubagentRunner` that drives a real `Agent` per task while honoring
|
|
1026
|
+
* the coordinator's budget and abort signal. This is the production adapter —
|
|
1027
|
+
* the coordinator's `runner` option in CLI/TUI assemblies points here.
|
|
1028
|
+
*
|
|
1029
|
+
* Lifecycle per task:
|
|
1030
|
+
* 1. factory(config) → fresh Agent + EventBus.
|
|
1031
|
+
* 2. Subscribe to events to feed the budget (tool calls, token usage).
|
|
1032
|
+
* 3. Call agent.run(input, { signal }) — the coordinator's signal cancels.
|
|
1033
|
+
* 4. Map RunResult.status onto a `SubagentRunOutcome` or throw on failure.
|
|
1034
|
+
* 5. Unsubscribe and let the factory's resources be GC'd.
|
|
1035
|
+
*
|
|
1036
|
+
* The budget is checked synchronously from event handlers — a runaway agent
|
|
1037
|
+
* that crosses its tool-call limit triggers `BudgetExceededError`, which the
|
|
1038
|
+
* coordinator surfaces as `status: 'failed'` on the task result.
|
|
1039
|
+
*/
|
|
1040
|
+
declare function makeAgentSubagentRunner(opts: AgentRunnerOptions): SubagentRunner;
|
|
1041
|
+
|
|
1042
|
+
export { Agent as A, type BridgeMessage as B, type CoordinatorEvents as C, type DoneCondition as D, type ProviderFactory as E, FleetBus as F, type ProviderRegistryView as G, type SlashCommand as H, type SlashCommandRegistryView as I, type SpawnResult as J, SubagentBudget as K, type SubagentConfig as L, type MCPRegistryView as M, type SubagentContext as N, type SubagentError as O, type Plugin as P, type SubagentErrorKind as Q, type SubagentRunContext as R, type SessionWriterView as S, type SubagentRunOutcome as T, type SubagentRunner as U, type SubagentUsageSnapshot as V, type TaskDelegation as W, type TaskResult as X, type TaskSpec as Y, type ToolRegistryView as Z, makeAgentSubagentRunner as _, type AgentBridge as a, type AgentBridgeConfig as b, type AgentFactory as c, type AgentFactoryResult as d, type AgentRunnerOptions as e, type BridgeTransport as f, BudgetExceededError as g, type BudgetKind as h, type BudgetLimits as i, type BudgetNegotiationMode as j, type BudgetThresholdDecision as k, type BudgetThresholdHandler as l, BudgetThresholdSignal as m, type BudgetUsage as n, type CoordinatorStatus as o, type FleetEvent as p, type FleetHandler as q, type FleetUsage as r, FleetUsageAggregator as s, type MetricsSinkView as t, type MultiAgentConfig as u, type MultiAgentCoordinator as v, type PluginAPI as w, type PluginCapabilities as x, type PluginDependency as y, type PluginPipelines as z };
|