@graphorin/agent 0.5.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +159 -0
  4. package/dist/errors/index.d.ts +170 -0
  5. package/dist/errors/index.d.ts.map +1 -0
  6. package/dist/errors/index.js +204 -0
  7. package/dist/errors/index.js.map +1 -0
  8. package/dist/evaluator-optimizer/index.d.ts +91 -0
  9. package/dist/evaluator-optimizer/index.d.ts.map +1 -0
  10. package/dist/evaluator-optimizer/index.js +85 -0
  11. package/dist/evaluator-optimizer/index.js.map +1 -0
  12. package/dist/factory.d.ts +13 -0
  13. package/dist/factory.d.ts.map +1 -0
  14. package/dist/factory.js +1853 -0
  15. package/dist/factory.js.map +1 -0
  16. package/dist/fallback/index.d.ts +52 -0
  17. package/dist/fallback/index.d.ts.map +1 -0
  18. package/dist/fallback/index.js +53 -0
  19. package/dist/fallback/index.js.map +1 -0
  20. package/dist/fanout/index.d.ts +142 -0
  21. package/dist/fanout/index.d.ts.map +1 -0
  22. package/dist/fanout/index.js +252 -0
  23. package/dist/fanout/index.js.map +1 -0
  24. package/dist/filters/index.d.ts +137 -0
  25. package/dist/filters/index.d.ts.map +1 -0
  26. package/dist/filters/index.js +273 -0
  27. package/dist/filters/index.js.map +1 -0
  28. package/dist/index.d.ts +51 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +49 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/internal/ids.js +46 -0
  33. package/dist/internal/ids.js.map +1 -0
  34. package/dist/internal/usage-accumulator.js +62 -0
  35. package/dist/internal/usage-accumulator.js.map +1 -0
  36. package/dist/lateral-leak/causality-monitor.d.ts +97 -0
  37. package/dist/lateral-leak/causality-monitor.d.ts.map +1 -0
  38. package/dist/lateral-leak/causality-monitor.js +139 -0
  39. package/dist/lateral-leak/causality-monitor.js.map +1 -0
  40. package/dist/lateral-leak/index.d.ts +4 -0
  41. package/dist/lateral-leak/index.js +5 -0
  42. package/dist/lateral-leak/merge-guard.d.ts +89 -0
  43. package/dist/lateral-leak/merge-guard.d.ts.map +1 -0
  44. package/dist/lateral-leak/merge-guard.js +65 -0
  45. package/dist/lateral-leak/merge-guard.js.map +1 -0
  46. package/dist/lateral-leak/protocol-guard.d.ts +76 -0
  47. package/dist/lateral-leak/protocol-guard.d.ts.map +1 -0
  48. package/dist/lateral-leak/protocol-guard.js +147 -0
  49. package/dist/lateral-leak/protocol-guard.js.map +1 -0
  50. package/dist/preferred-model/index.d.ts +53 -0
  51. package/dist/preferred-model/index.d.ts.map +1 -0
  52. package/dist/preferred-model/index.js +141 -0
  53. package/dist/preferred-model/index.js.map +1 -0
  54. package/dist/progress/index.d.ts +62 -0
  55. package/dist/progress/index.d.ts.map +1 -0
  56. package/dist/progress/index.js +150 -0
  57. package/dist/progress/index.js.map +1 -0
  58. package/dist/run-state/index.d.ts +152 -0
  59. package/dist/run-state/index.d.ts.map +1 -0
  60. package/dist/run-state/index.js +311 -0
  61. package/dist/run-state/index.js.map +1 -0
  62. package/dist/tooling/adapters.js +154 -0
  63. package/dist/tooling/adapters.js.map +1 -0
  64. package/dist/tooling/catalogue.js +37 -0
  65. package/dist/tooling/catalogue.js.map +1 -0
  66. package/dist/tooling/dataflow.js +99 -0
  67. package/dist/tooling/dataflow.js.map +1 -0
  68. package/dist/tooling/registry-build.js +85 -0
  69. package/dist/tooling/registry-build.js.map +1 -0
  70. package/dist/types.d.ts +413 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/package.json +115 -0
@@ -0,0 +1,1853 @@
1
+ import { AgentRuntimeError, ConcurrentRunError, InvalidAgentConfigError, InvalidPreferredModelError, MultipleHandoffsInStepError, ToolNotFoundError } from "./errors/index.js";
2
+ import { isAgentFallbackEligible } from "./fallback/index.js";
3
+ import { runFanOut } from "./fanout/index.js";
4
+ import { filters } from "./filters/index.js";
5
+ import { newId } from "./internal/ids.js";
6
+ import { InMemoryUsageAccumulator } from "./internal/usage-accumulator.js";
7
+ import { CausalityMonitor } from "./lateral-leak/causality-monitor.js";
8
+ import { resolvePreferredModel } from "./preferred-model/index.js";
9
+ import { createProgressIO } from "./progress/index.js";
10
+ import { addModelUsage, createInitialRunState, serializeRunState } from "./run-state/index.js";
11
+ import { buildMemoryGuard, buildSecretResolver, buildToolTokenCounter, createExecutorEventBridge, createMemoryRegionReader } from "./tooling/adapters.js";
12
+ import { orderPromotedTools } from "./tooling/catalogue.js";
13
+ import { buildDataFlowGuard } from "./tooling/dataflow.js";
14
+ import { buildToolRegistry } from "./tooling/registry-build.js";
15
+ import { createHash } from "node:crypto";
16
+ import { NOOP_LOGGER, NOOP_TRACER, isStepCount, zeroUsage } from "@graphorin/core";
17
+ import { composeGuardrails } from "@graphorin/security/guardrails";
18
+ import { createReadResultTool, createToolSearchTool } from "@graphorin/tools/built-in";
19
+ import { createCodeExecuteTool, createCodeSearchTool, projectToolApi } from "@graphorin/tools/code-mode";
20
+ import { createToolExecutor } from "@graphorin/tools/executor";
21
+ import { createDefaultSpillWriter, createFileResultReader } from "@graphorin/tools/result";
22
+
23
+ //#region src/factory.ts
24
+ /**
25
+ * `createAgent({...})` — the agent factory entry point.
26
+ *
27
+ * Wires the typed `model -> tool calls -> model` loop, the streamed
28
+ * event surface, the steering / followUp queues, durable HITL via
29
+ * `RunState`, the multi-agent handoff layer, the agent-level model
30
+ * fallback chain, and the per-tool preferred-model resolution.
31
+ *
32
+ * Custom adapters override behaviour by supplying alternative
33
+ * `Provider` / `Memory` / `CheckpointStore` instances; the loop
34
+ * never reaches into adapter internals.
35
+ *
36
+ * @packageDocumentation
37
+ */
38
+ /**
39
+ * Replacement content committed in place of assistant commentary the
40
+ * causality monitor blocked (AG-10). Worded to not match any built-in
41
+ * denial pattern, so the notice itself never re-triggers the monitor.
42
+ */
43
+ const LATERAL_LEAK_BLOCKED_NOTICE = "[graphorin] assistant commentary withheld by the lateral-leak defense.";
44
+ const sha256Hex = (input) => createHash("sha256").update(input, "utf8").digest("hex");
45
+ const HANDOFF_TOOL_PREFIX = "transfer_to_";
46
+ /** The built-in deferred-discovery tool's stable name (WI-05 / P0-3). */
47
+ const TOOL_SEARCH_NAME = "tool_search";
48
+ /**
49
+ * Register the built-in `tool_search` into `registry` when — and only
50
+ * when — the registry holds at least one deferred tool
51
+ * (`defer_loading: true`). `tool_search` is itself eager (so it is
52
+ * always advertised while a deferred pool exists) and resolvable by the
53
+ * executor like any other tool, so a model can both *see* it in the
54
+ * per-step catalogue and *call* it.
55
+ *
56
+ * No-op when nothing defers (zero overhead — the tool never appears) or
57
+ * when a user tool already occupies the name (the user's tool wins; we
58
+ * never clobber it). Because deferral is decided at registration time
59
+ * (`normaliseTool`), the deferred set is fixed for the life of the
60
+ * registry — this runs once per registry, not per step.
61
+ */
62
+ function registerToolSearch(registry) {
63
+ if (registry.listDeferred().length === 0) return;
64
+ if (registry.get(TOOL_SEARCH_NAME) !== void 0) return;
65
+ registry.register(createToolSearchTool({ registry }), {
66
+ kind: "built-in",
67
+ subsystem: "tool-discovery"
68
+ });
69
+ }
70
+ /** The built-in result-handle reader tool's stable name (WI-10 / P1-4). */
71
+ const READ_RESULT_NAME = "read_result";
72
+ /**
73
+ * Register the built-in `read_result` into `registry` when at least one
74
+ * registered tool opts into the `'spill-to-file'` truncation strategy
75
+ * (the sole producer of spill handles today) — or when `force` is set,
76
+ * which the agent passes when an operator wires external result readers
77
+ * (e.g. an MCP `resource_link` reader; WI-13). The tool is eager, so it
78
+ * is advertised alongside the producing tool and the model can fetch a
79
+ * handle back on demand instead of inlining the full blob. No-op when
80
+ * nothing produces handles (zero overhead) or when a user tool already
81
+ * occupies the name (the user's tool wins).
82
+ */
83
+ function registerReadResult(registry, reader, opts) {
84
+ if (opts?.force !== true && !registry.list().some((entry) => entry.truncationStrategy === "spill-to-file")) return;
85
+ if (registry.get(READ_RESULT_NAME) !== void 0) return;
86
+ registry.register(createReadResultTool({ reader }), {
87
+ kind: "built-in",
88
+ subsystem: "result-handle"
89
+ });
90
+ }
91
+ /**
92
+ * Compose result readers into one that tries each in order, returning
93
+ * the first that resolves the handle (WI-13). The spill-file reader is
94
+ * placed first so `graphorin-spill:` handles resolve locally; operator
95
+ * readers (e.g. an MCP resource reader) resolve the rest. Each reader
96
+ * rejects handles it does not own, so resolution falls through cleanly.
97
+ */
98
+ function composeResultReaders(readers) {
99
+ return { async read(uri, range) {
100
+ let lastError;
101
+ for (const r of readers) try {
102
+ return await r.read(uri, range);
103
+ } catch (err) {
104
+ lastError = err;
105
+ }
106
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`No result reader resolved handle ${JSON.stringify(uri)}.`);
107
+ } };
108
+ }
109
+ /** The code-mode meta-tools' stable names (WI-11 / P1-2). */
110
+ const CODE_EXECUTE_NAME = "code_execute";
111
+ const CODE_SEARCH_NAME = "code_search";
112
+ /**
113
+ * Wire code-mode (P1-2) into `registry`: register the `code_search` /
114
+ * `code_execute` meta-tools and return them as the tools to advertise in
115
+ * place of the full catalogue. The model reaches every other tool through
116
+ * `code_execute`, whose in-script `tools.<name>(args)` calls route back
117
+ * through `quietExecutor.executeOne(...)` under the calling step's
118
+ * `runContext` — so per-tool ACL / sanitization / truncation still apply,
119
+ * exactly as in direct mode. `quietExecutor` carries no `streamingSink`,
120
+ * so the inner calls do not interleave `tool.execute.*` events into the
121
+ * outer stream.
122
+ *
123
+ * Excluded from the code API (`reservedNames`): the meta-tools, the
124
+ * discovery / handle built-ins, handoff tools (which stay first-class
125
+ * provider tools), and — supplied by the caller — any approval-gated
126
+ * tool, since code-mode has no durable-HITL path to suspend mid-script.
127
+ *
128
+ * Returns `[]` (registering nothing) when no real tool is exposable.
129
+ */
130
+ function registerCodeMode(registry, quietExecutor, reservedNames) {
131
+ if (registry.get(CODE_EXECUTE_NAME) !== void 0) return [];
132
+ const isApprovalGated = (t) => t.needsApproval === true || typeof t.needsApproval === "function";
133
+ const codeTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && !isApprovalGated(entry));
134
+ if (codeTools.length === 0) return [];
135
+ const approvalGatedTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && isApprovalGated(entry)).map((entry) => entry.name);
136
+ const approvalGatedSet = new Set(approvalGatedTools);
137
+ const allowedTools = [...codeTools.map((entry) => entry.name), ...approvalGatedTools];
138
+ const allowedSet = new Set(codeTools.map((entry) => entry.name));
139
+ const projection = projectToolApi(codeTools.filter((entry) => entry.__effectiveDeferLoading !== true));
140
+ const executeTool = async (call, ctx) => {
141
+ if (approvalGatedSet.has(call.name)) throw new Error(`${call.name} requires human approval and cannot run inside code_execute — call it directly as a standalone tool call so the run can suspend for the approval.`);
142
+ const { outcome } = await quietExecutor.executeOne({
143
+ call: {
144
+ toolCallId: newId("codecall"),
145
+ toolName: call.name,
146
+ args: call.args
147
+ },
148
+ runContext: ctx.runContext,
149
+ stepNumber: ctx.runContext.stepNumber
150
+ });
151
+ if ("kind" in outcome) throw new Error(`${call.name}: ${outcome.message}`);
152
+ return outcome.output;
153
+ };
154
+ const codeSearch = createCodeSearchTool({
155
+ projection,
156
+ approvalGatedTools,
157
+ searchDeferred: async (query, k) => (await registry.searchDeferred(query, k)).filter((match) => allowedSet.has(match.name))
158
+ });
159
+ const codeExecute = createCodeExecuteTool({
160
+ projection,
161
+ allowedTools,
162
+ executeTool,
163
+ approvalGatedTools
164
+ });
165
+ registry.register(codeSearch, {
166
+ kind: "built-in",
167
+ subsystem: "code-mode"
168
+ });
169
+ registry.register(codeExecute, {
170
+ kind: "built-in",
171
+ subsystem: "code-mode"
172
+ });
173
+ return [codeSearch, codeExecute];
174
+ }
175
+ /**
176
+ * Fold a completed `tool_search` result into the per-run promotion set:
177
+ * every matched tool name becomes advertised (and thus callable) on the
178
+ * next step. Tolerant of unexpected shapes (e.g. a user-shadowed
179
+ * `tool_search`) — only string `name`s inside a `matches` array promote.
180
+ */
181
+ function recordToolSearchPromotions(output, promoted) {
182
+ if (typeof output !== "object" || output === null) return;
183
+ const matches = output.matches;
184
+ if (!Array.isArray(matches)) return;
185
+ for (const match of matches) {
186
+ const name = match?.name;
187
+ if (typeof name === "string") promoted.add(name);
188
+ }
189
+ }
190
+ function isModelHintLike(value) {
191
+ return value === "fast" || value === "balanced" || value === "smart";
192
+ }
193
+ function isModelSpecLike(value) {
194
+ if (typeof value !== "object" || value === null) return false;
195
+ const v = value;
196
+ if (typeof v.modelId === "string" && typeof v.name === "string") return true;
197
+ if (typeof v.provider === "object" && v.provider !== null && typeof v.model === "string") return true;
198
+ return false;
199
+ }
200
+ function validatePreferredModel(value) {
201
+ if (value === void 0) return;
202
+ if (isModelHintLike(value)) return;
203
+ if (isModelSpecLike(value)) return;
204
+ throw new InvalidPreferredModelError(value);
205
+ }
206
+ function isMessageObject(value) {
207
+ if (typeof value !== "object" || value === null) return false;
208
+ const role = value.role;
209
+ return role === "system" || role === "user" || role === "assistant" || role === "tool";
210
+ }
211
+ function isRunStateObject(value) {
212
+ if (typeof value !== "object" || value === null) return false;
213
+ const v = value;
214
+ return typeof v.id === "string" && typeof v.agentId === "string" && Array.isArray(v.messages) && Array.isArray(v.steps);
215
+ }
216
+ function asMessages(input) {
217
+ if (typeof input === "string") return { seed: [{
218
+ role: "user",
219
+ content: input
220
+ }] };
221
+ if (Array.isArray(input)) return { seed: [...input] };
222
+ if (isMessageObject(input)) return { seed: [input] };
223
+ if (isRunStateObject(input)) return {
224
+ seed: [],
225
+ resumed: input
226
+ };
227
+ throw new InvalidAgentConfigError(`unrecognized AgentInput shape`);
228
+ }
229
+ /**
230
+ * Strip a single Markdown code fence around a JSON payload (AG-3).
231
+ * Models often wrap structured output in ```json fences even when told
232
+ * not to. ReDoS-safe: the info string is matched with `[^\n]*`.
233
+ */
234
+ function stripJsonFence(text) {
235
+ const trimmed = text.trim();
236
+ return /^```[^\n]*\n([\s\S]*?)\n?```$/.exec(trimmed)?.[1] ?? trimmed;
237
+ }
238
+ /**
239
+ * The AG-3 fallback instruction: one trailing system message that
240
+ * pins JSON-only output and embeds the wire schema / description.
241
+ * This is the documented structured-output contract for adapters that
242
+ * do not yet consume `ProviderRequest.outputType` natively (PS-24).
243
+ */
244
+ function buildStructuredInstruction(spec) {
245
+ const parts = ["Respond with a single valid JSON value only — no prose, no Markdown code fences."];
246
+ if (spec.description !== void 0 && spec.description.length > 0) parts.push(spec.description);
247
+ if (spec.jsonSchema !== void 0) parts.push(`The JSON MUST conform to this JSON Schema:\n${JSON.stringify(spec.jsonSchema)}`);
248
+ return parts.join("\n");
249
+ }
250
+ /** Most-recent user-role text in `messages` (for context-engine auto-recall). */
251
+ function lastUserText(messages) {
252
+ for (let i = messages.length - 1; i >= 0; i--) {
253
+ const m = messages[i];
254
+ if (m?.role !== "user") continue;
255
+ if (typeof m.content === "string") return m.content;
256
+ const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
257
+ return text.length > 0 ? text : void 0;
258
+ }
259
+ }
260
+ /**
261
+ * AG-21: classify a **thrown** provider error into a {@link ProviderErrorKind}
262
+ * so the fallback chain can act on it, instead of flattening every exception to
263
+ * `'unknown'` (which is always fallback-ineligible). Structural — reads the
264
+ * `kind` carried by `@graphorin/provider`'s `GraphorinProviderError` subclasses
265
+ * without importing them, keeping the agent decoupled from the provider package.
266
+ */
267
+ function classifyThrownProviderErrorKind(cause) {
268
+ if (typeof cause === "object" && cause !== null && "kind" in cause) switch (cause.kind) {
269
+ case "rate-limit-exceeded": return "rate-limit";
270
+ }
271
+ return "unknown";
272
+ }
273
+ /** Resolve a Zod-like schema to a JSON-Schema record (via `toJSON`), else pass an object through. */
274
+ function projectSchema(raw) {
275
+ if (raw === null || raw === void 0) return void 0;
276
+ const toJson = raw.toJSON;
277
+ if (typeof toJson === "function") return toJson.call(raw);
278
+ if (typeof raw === "object") return raw;
279
+ }
280
+ function toolToDefinition(tool) {
281
+ const ts = tool;
282
+ const inputSchema = projectSchema(ts.inputSchema) ?? {};
283
+ const outputSchema = projectSchema(ts.outputSchema);
284
+ const examples = renderToolExamples(tool);
285
+ return {
286
+ name: tool.name,
287
+ description: tool.description,
288
+ inputSchema,
289
+ ...outputSchema !== void 0 ? { outputSchema } : {},
290
+ ...examples !== void 0 ? { examples } : {}
291
+ };
292
+ }
293
+ /**
294
+ * Project a tool's worked `examples` onto the provider wire contract
295
+ * (WI-06 / P2-3). Examples are rendered only when the tool eagerly
296
+ * renders them: the registry resolves the `defer_loading` auto-rule into
297
+ * `examplesEagerlyRendered`, so a deferred tool resolves to `false` and is
298
+ * skipped (its examples stay out of context even once `tool_search`
299
+ * promotes it). `undefined` — the "runtime decides" case for a plain
300
+ * eager tool — renders, since the tool is already advertised this step.
301
+ *
302
+ * Bounded to ≤5 to honour the `ToolExample` `[1,5]` contract even when a
303
+ * tool slipped past the registry's overflow WARN (which does not truncate).
304
+ */
305
+ function renderToolExamples(tool) {
306
+ const examples = tool.examples;
307
+ if (examples === void 0 || examples.length === 0) return void 0;
308
+ if (tool.examplesEagerlyRendered === false) return void 0;
309
+ return examples.slice(0, 5).map((ex) => ({
310
+ input: ex.input,
311
+ output: ex.output,
312
+ ...ex.comment !== void 0 ? { comment: ex.comment } : {}
313
+ }));
314
+ }
315
+ const PASSTHROUGH_SCHEMA = {
316
+ parse: (value) => value,
317
+ safeParse: (value) => ({
318
+ success: true,
319
+ data: value
320
+ }),
321
+ toJSON: () => ({ type: "object" })
322
+ };
323
+ function isDescribedFilter(value) {
324
+ return typeof value === "function" && "descriptor" in value && typeof value.descriptor === "object";
325
+ }
326
+ function buildHandoffTool(target) {
327
+ const cfg = target.config;
328
+ return {
329
+ name: `${HANDOFF_TOOL_PREFIX}${cfg.name}`,
330
+ description: `Hand off control to agent '${cfg.name}'.`,
331
+ inputSchema: PASSTHROUGH_SCHEMA,
332
+ sideEffectClass: "pure",
333
+ async execute() {
334
+ return `[handoff: ${cfg.name}]`;
335
+ }
336
+ };
337
+ }
338
+ function specToProvider(spec) {
339
+ if ("provider" in spec) return spec.provider;
340
+ return spec;
341
+ }
342
+ function handleProviderEvent(ev, state) {
343
+ switch (ev.type) {
344
+ case "stream-start": return {};
345
+ case "reasoning-delta":
346
+ state.reasoningBuffer += ev.delta;
347
+ return { emit: {
348
+ type: "reasoning.delta",
349
+ delta: ev.delta
350
+ } };
351
+ case "text-delta":
352
+ state.textBuffer += ev.delta;
353
+ return { emit: {
354
+ type: "text.delta",
355
+ delta: ev.delta
356
+ } };
357
+ case "tool-call-start":
358
+ state.calls.set(ev.toolCallId, {
359
+ toolCallId: ev.toolCallId,
360
+ toolName: ev.toolName,
361
+ argsBuffer: ""
362
+ });
363
+ return { emit: {
364
+ type: "tool.call.start",
365
+ toolCallId: ev.toolCallId,
366
+ toolName: ev.toolName,
367
+ args: void 0
368
+ } };
369
+ case "tool-call-input-delta": {
370
+ const acc = state.calls.get(ev.toolCallId);
371
+ if (acc !== void 0) acc.argsBuffer += ev.argsDelta;
372
+ return { emit: {
373
+ type: "tool.call.delta",
374
+ toolCallId: ev.toolCallId,
375
+ argsDelta: ev.argsDelta
376
+ } };
377
+ }
378
+ case "tool-call-end": {
379
+ const acc = state.calls.get(ev.toolCallId);
380
+ if (acc === void 0) {
381
+ process.stderr.write(`[graphorin/agent] dropped tool-call-end '${ev.toolCallId}' with no matching tool-call-start.\n`);
382
+ return {};
383
+ }
384
+ state.finalCalls.push({
385
+ toolCallId: ev.toolCallId,
386
+ toolName: acc.toolName,
387
+ args: ev.finalArgs
388
+ });
389
+ return { emit: {
390
+ type: "tool.call.end",
391
+ toolCallId: ev.toolCallId,
392
+ finalArgs: ev.finalArgs
393
+ } };
394
+ }
395
+ case "file": return { emit: {
396
+ type: "file.generated",
397
+ mimeType: ev.mimeType,
398
+ data: ev.data
399
+ } };
400
+ case "source": return { emit: {
401
+ type: "source.cited",
402
+ uri: ev.uri,
403
+ ...ev.title !== void 0 ? { title: ev.title } : {}
404
+ } };
405
+ case "finish": return {
406
+ usage: ev.usage,
407
+ finished: true
408
+ };
409
+ case "error": return { providerError: ev.error };
410
+ default: return {};
411
+ }
412
+ }
413
+ /**
414
+ * Resolve the effective {@link ReasoningRetention} for a step. The
415
+ * agent-level setting wins over the provider-level default; when
416
+ * neither is supplied, the provider's `reasoningContract`
417
+ * capability drives the default per RB-42 / suggested DEC-158.
418
+ */
419
+ function effectiveReasoningRetention(agentOverride, provider) {
420
+ if (agentOverride !== void 0) return agentOverride;
421
+ switch (provider.capabilities.reasoningContract) {
422
+ case "round-trip-required": return "pass-through-claude";
423
+ case "optional": return "pass-through-all";
424
+ case "hidden": return "strip";
425
+ default: return "strip";
426
+ }
427
+ }
428
+ /**
429
+ * Build the assistant message that the runtime appends to the
430
+ * message buffer after a successful provider call. When the
431
+ * effective {@link ReasoningRetention} is not `'strip'`, the
432
+ * assembled `reasoning` content parts ride along on `content` so
433
+ * the next provider call honours the wire-correct round-trip
434
+ * contract per RB-42.
435
+ */
436
+ function buildAssistantMessage(text, reasoningParts, toolCalls, agentId, retention) {
437
+ if (retention !== "strip" && reasoningParts.length > 0) {
438
+ const parts = [...reasoningParts];
439
+ if (text.length > 0) parts.push({
440
+ type: "text",
441
+ text
442
+ });
443
+ return {
444
+ role: "assistant",
445
+ content: parts,
446
+ ...toolCalls.length > 0 ? { toolCalls } : {},
447
+ agentId
448
+ };
449
+ }
450
+ return {
451
+ role: "assistant",
452
+ content: text,
453
+ ...toolCalls.length > 0 ? { toolCalls } : {},
454
+ agentId
455
+ };
456
+ }
457
+ /**
458
+ * Strip every {@link ReasoningContent} part from each message in
459
+ * the supplied list. Used at the swap point when `prepareStep`
460
+ * downgrades the provider's `reasoningContract` mid-run.
461
+ */
462
+ function stripReasoningFromMessages(messages) {
463
+ let stripped = 0;
464
+ for (let i = 0; i < messages.length; i++) {
465
+ const msg = messages[i];
466
+ if (msg === void 0) continue;
467
+ if (msg.role === "system" || msg.role === "tool") continue;
468
+ if (typeof msg.content === "string") continue;
469
+ const filtered = msg.content.filter((p) => p.type !== "reasoning");
470
+ if (filtered.length === msg.content.length) continue;
471
+ stripped += msg.content.length - filtered.length;
472
+ if (msg.role === "assistant") messages[i] = {
473
+ ...msg,
474
+ content: filtered
475
+ };
476
+ else messages[i] = {
477
+ ...msg,
478
+ content: filtered
479
+ };
480
+ }
481
+ return { stripped };
482
+ }
483
+ /**
484
+ * Count the leading contiguous run of `system` messages in the initial
485
+ * buffer — the trusted, KV-cache-stable instruction prefix. Captured
486
+ * once at run start (WI-09 / P1-1): auto-compaction summarises only the
487
+ * messages after this prefix, so the prefix stays byte-identical across
488
+ * every step (the provider's cache breakpoint is real) and a long run
489
+ * never re-pays for the system prompt.
490
+ *
491
+ * The length is fixed for the run rather than re-derived per compaction
492
+ * on purpose: each compaction inserts its summary as a `system` message
493
+ * right after the prefix, so re-scanning the leading run would absorb
494
+ * that summary into the prefix and shield it from the next compaction —
495
+ * summaries would stack unbounded. Pinning the original length keeps
496
+ * each prior summary inside the compactable body, where the next pass
497
+ * folds it into a fresh summary-of-summary.
498
+ */
499
+ function countLeadingSystemMessages(messages) {
500
+ let i = 0;
501
+ while (i < messages.length && messages[i]?.role === "system") i += 1;
502
+ return i;
503
+ }
504
+ /**
505
+ * Build a fresh {@link Agent} from the supplied configuration.
506
+ *
507
+ * @stable
508
+ */
509
+ function createAgent(config) {
510
+ if (typeof config.name !== "string" || config.name.length === 0) throw new InvalidAgentConfigError("missing 'name'");
511
+ if (config.provider === void 0 || config.provider === null) throw new InvalidAgentConfigError("missing 'provider'");
512
+ if (config.outputType?.kind === "text" && config.outputType.schema !== void 0) throw new InvalidAgentConfigError("outputType.kind 'text' with a schema — did you mean kind: 'structured'?");
513
+ validatePreferredModel(config.preferredModel);
514
+ if (config.modelTierMap !== void 0) for (const [tier, spec] of Object.entries(config.modelTierMap)) {
515
+ if (!isModelHintLike(tier)) throw new InvalidPreferredModelError({ tier });
516
+ if (spec === void 0) continue;
517
+ if (!isModelSpecLike(spec)) throw new InvalidPreferredModelError(spec);
518
+ }
519
+ if (config.fallbackModels !== void 0) {
520
+ for (const spec of config.fallbackModels) if (!isModelSpecLike(spec)) throw new InvalidPreferredModelError(spec);
521
+ }
522
+ const agentId = newId("agent");
523
+ const tracer = config.tracer ?? NOOP_TRACER;
524
+ const stopWhen = config.stopWhen ?? isStepCount(50);
525
+ const fallbackPolicy = config.fallbackPolicy ?? {};
526
+ const handoffMap = /* @__PURE__ */ new Map();
527
+ for (const entry of config.handoffs ?? []) {
528
+ const isWrappedHandoff = typeof entry === "object" && entry !== null && "target" in entry;
529
+ const subAgent = isWrappedHandoff ? entry.target : entry;
530
+ const userFilter = isWrappedHandoff ? entry.inputFilter : void 0;
531
+ const filter = isDescribedFilter(userFilter) ? userFilter : void 0;
532
+ const toolName = `${HANDOFF_TOOL_PREFIX}${subAgent.config.name}`;
533
+ handoffMap.set(toolName, {
534
+ agent: subAgent,
535
+ filter
536
+ });
537
+ }
538
+ let pendingSteer = [];
539
+ const pendingFollowUp = [];
540
+ let abortController;
541
+ let pendingAbort;
542
+ let activeRunState;
543
+ /** AG-11: guards the one-in-flight-run-per-instance invariant. */
544
+ let runInFlight = false;
545
+ const externalEventQueue = [];
546
+ const pendingManualCompacts = [];
547
+ const noopCompactionResult = (skippedReason) => ({
548
+ beforeTokens: 0,
549
+ afterTokens: 0,
550
+ summaryTokens: 0,
551
+ durationMs: 0,
552
+ hooksFiredCount: 0,
553
+ summary: "",
554
+ applied: false,
555
+ skippedReason
556
+ });
557
+ const memory = config.memory;
558
+ const progressIO = createProgressIO({ ...config.sensitivity !== void 0 ? { defaultSensitivity: config.sensitivity } : {} });
559
+ const toolRegistry = buildToolRegistry({
560
+ ...config.tools !== void 0 ? { tools: config.tools } : {},
561
+ ...config.skills !== void 0 ? { skills: config.skills } : {}
562
+ }).registry;
563
+ registerToolSearch(toolRegistry);
564
+ const spillWriter = createDefaultSpillWriter();
565
+ const fileResultReader = createFileResultReader({ artifactRoot: spillWriter.artifactRoot });
566
+ const externalResultReaders = config.resultReaders ?? [];
567
+ const resultReader = externalResultReaders.length === 0 ? fileResultReader : composeResultReaders([fileResultReader, ...externalResultReaders]);
568
+ registerReadResult(toolRegistry, resultReader, { force: externalResultReaders.length > 0 });
569
+ let activeExecutorBridge;
570
+ const toolApprovalGate = { request: async () => ({ granted: true }) };
571
+ const toolSecretResolver = buildSecretResolver();
572
+ const toolTokenCounter = buildToolTokenCounter();
573
+ const memoryGuardWiring = buildMemoryGuard(memory, memory === void 0 ? {} : { regionReader: createMemoryRegionReader(["working"], async (region) => {
574
+ if (region !== "working") return "";
575
+ const scope = {
576
+ userId: activeRunState?.userId ?? agentId,
577
+ ...activeRunState?.sessionId !== void 0 ? { sessionId: activeRunState.sessionId } : {},
578
+ agentId
579
+ };
580
+ const working = memory.working;
581
+ if (working?.compile === void 0) return "";
582
+ try {
583
+ return await working.compile(scope, agentId);
584
+ } catch {
585
+ return "";
586
+ }
587
+ }) });
588
+ const toolMemoryGuardFactory = memoryGuardWiring.memoryGuardFactory;
589
+ if (memory === void 0) {
590
+ const guarded = (config.tools ?? []).filter((t) => t.memoryGuardTier !== void 0);
591
+ if (guarded.length > 0) console.warn(`[graphorin/agent] ${guarded.length} tool(s) declare memoryGuardTier but no memory is wired — the DEC-153 snapshot/verify guard is skipped (SDF-1). Wire \`memory\` to activate it.`);
592
+ }
593
+ const toolDataFlowGuard = config.dataFlowPolicy !== void 0 && config.dataFlowPolicy.mode !== "off" ? buildDataFlowGuard(config.dataFlowPolicy) : void 0;
594
+ const toolStreamingSink = (event) => activeExecutorBridge?.sink(event);
595
+ const makeToolExecutor = (registry, opts) => createToolExecutor({
596
+ registry,
597
+ approvalGate: toolApprovalGate,
598
+ secretResolver: toolSecretResolver,
599
+ tokenCounter: toolTokenCounter,
600
+ memoryGuardFactory: toolMemoryGuardFactory,
601
+ ...memoryGuardWiring.memoryRegionReader !== void 0 ? { memoryRegionReader: memoryGuardWiring.memoryRegionReader } : {},
602
+ spill: spillWriter,
603
+ ...toolDataFlowGuard !== void 0 ? { dataFlowGuard: toolDataFlowGuard } : {},
604
+ ...opts?.quiet === true ? {} : { streamingSink: toolStreamingSink },
605
+ ...config.maxParallelTools !== void 0 ? { maxParallelTools: config.maxParallelTools } : {}
606
+ });
607
+ const toolExecutor = makeToolExecutor(toolRegistry);
608
+ const isCodeMode = config.toolInvocation === "code-mode";
609
+ let codeModeAdvertised = [];
610
+ if (isCodeMode) {
611
+ const reserved = new Set([
612
+ CODE_EXECUTE_NAME,
613
+ CODE_SEARCH_NAME,
614
+ TOOL_SEARCH_NAME,
615
+ READ_RESULT_NAME,
616
+ ...handoffMap.keys()
617
+ ]);
618
+ const metas = registerCodeMode(toolRegistry, makeToolExecutor(toolRegistry, { quiet: true }), reserved);
619
+ registerReadResult(toolRegistry, resultReader);
620
+ const readResult = toolRegistry.get(READ_RESULT_NAME);
621
+ codeModeAdvertised = [...metas, ...readResult !== void 0 ? [readResult] : []];
622
+ }
623
+ const causalityMonitor = config.causalityMonitor ? new CausalityMonitor(config.causalityMonitor) : void 0;
624
+ /**
625
+ * AG-11: one in-flight run per Agent instance. `steer` / `followUp` /
626
+ * `abort` / `compact` address "the run" with no run handle, so
627
+ * overlapping runs on one instance cannot be expressed safely — they
628
+ * would share the abort controller, steer queue, active-run ref and
629
+ * executor bridge. A second concurrent `run()` / `stream()` rejects
630
+ * with {@link ConcurrentRunError}; run-scoped state is reset on entry
631
+ * (a steer/abort queued after the previous run ended belongs to NO
632
+ * run) and cleared in a `finally` that also covers abandoned streams
633
+ * (consumer `break`) and thrown runs.
634
+ */
635
+ async function* runLoop(input, options) {
636
+ if (runInFlight) throw new ConcurrentRunError();
637
+ runInFlight = true;
638
+ pendingSteer = [];
639
+ pendingAbort = void 0;
640
+ causalityMonitor?.reset();
641
+ try {
642
+ return yield* runLoopInner(input, options);
643
+ } finally {
644
+ runInFlight = false;
645
+ activeRunState = void 0;
646
+ while (pendingManualCompacts.length > 0) pendingManualCompacts.shift()?.resolve(noopCompactionResult("no-active-run"));
647
+ }
648
+ }
649
+ async function* runLoopInner(input, options) {
650
+ const { seed: rawSeed, resumed } = asMessages(input);
651
+ const seed = resumed === void 0 && pendingFollowUp.length > 0 ? [...pendingFollowUp.splice(0, pendingFollowUp.length), ...rawSeed] : rawSeed;
652
+ const sessionId = options.sessionId ?? config.sessionId ?? `session_${newId()}`;
653
+ const userId = options.userId ?? config.userId;
654
+ const localCtl = new AbortController();
655
+ abortController = localCtl;
656
+ const signal = localCtl.signal;
657
+ const parentSignal = options.signal;
658
+ const onParentAbort = () => localCtl.abort();
659
+ if (parentSignal !== void 0) if (parentSignal.aborted) localCtl.abort();
660
+ else parentSignal.addEventListener("abort", onParentAbort);
661
+ const usageAcc = new InMemoryUsageAccumulator();
662
+ const state = resumed ? resumed : createInitialRunState({
663
+ id: newId("run"),
664
+ agentId,
665
+ sessionId,
666
+ ...userId !== void 0 ? { userId } : {}
667
+ });
668
+ activeRunState = state;
669
+ if (resumed && state.taintSummary !== void 0) toolDataFlowGuard?.seedLedger(state.id, state.taintSummary);
670
+ const messages = resumed ? [...state.messages] : [];
671
+ if (!resumed) {
672
+ const instructionsRaw = config.instructions;
673
+ let instructionsText;
674
+ if (typeof instructionsRaw === "string") instructionsText = instructionsRaw;
675
+ else instructionsText = await instructionsRaw({
676
+ runId: state.id,
677
+ sessionId,
678
+ ...userId !== void 0 ? { userId } : {},
679
+ agentId,
680
+ deps: options.deps ?? config.deps,
681
+ tracer,
682
+ signal,
683
+ usage: usageAcc,
684
+ stepNumber: 0,
685
+ messages,
686
+ state
687
+ });
688
+ let systemPrompt = instructionsText;
689
+ if (config.autoAssembleContext === true && memory !== void 0) {
690
+ const lastUser = lastUserText(seed);
691
+ systemPrompt = (await memory.contextEngine.assemble(memory, {
692
+ scope: {
693
+ userId: userId ?? agentId,
694
+ sessionId,
695
+ agentId
696
+ },
697
+ agentId,
698
+ sessionId,
699
+ runId: state.id,
700
+ ...instructionsText.length > 0 ? { agentInstructions: instructionsText } : {},
701
+ ...lastUser !== void 0 ? { lastUserMessage: lastUser } : {}
702
+ })).systemMessage.content;
703
+ }
704
+ if (systemPrompt.length > 0) messages.push({
705
+ role: "system",
706
+ content: systemPrompt
707
+ });
708
+ messages.push(...seed);
709
+ for (const m of messages) state.messages.push(m);
710
+ }
711
+ const finalSnapshot = { output: "" };
712
+ yield {
713
+ type: "agent.start",
714
+ runId: state.id,
715
+ agentId
716
+ };
717
+ const inputGuards = config.guardrails?.input;
718
+ if (!resumed && inputGuards !== void 0 && inputGuards.length > 0) for (let i = 0; i < messages.length; i++) {
719
+ const msg = messages[i];
720
+ if (msg === void 0 || msg.role !== "user" || typeof msg.content !== "string") continue;
721
+ const composed = await composeGuardrails(inputGuards, msg.content, {
722
+ stage: "input",
723
+ runId: state.id,
724
+ sessionId,
725
+ agentId
726
+ });
727
+ if (!composed.ok) {
728
+ yield {
729
+ type: "guardrail.tripped",
730
+ guardrailName: composed.name,
731
+ phase: "input",
732
+ reason: composed.message
733
+ };
734
+ const message = `input guardrail '${composed.name}' blocked the run: ${composed.message}`;
735
+ yield {
736
+ type: "agent.error",
737
+ error: {
738
+ message,
739
+ code: "guardrail-blocked"
740
+ }
741
+ };
742
+ state.status = "failed";
743
+ state.error = {
744
+ message,
745
+ code: "guardrail-blocked"
746
+ };
747
+ return yield* finishRun(state, finalSnapshot);
748
+ }
749
+ if (composed.value !== msg.content) {
750
+ const rewritten = {
751
+ ...msg,
752
+ content: composed.value
753
+ };
754
+ const stateIdx = state.messages.indexOf(msg);
755
+ messages[i] = rewritten;
756
+ if (stateIdx !== -1) state.messages[stateIdx] = rewritten;
757
+ }
758
+ }
759
+ const structuredInstruction = config.outputType?.kind === "structured" ? buildStructuredInstruction(config.outputType) : void 0;
760
+ const resumedApprovedCalls = [];
761
+ const journaledCallIds = /* @__PURE__ */ new Set();
762
+ for (const step of state.steps) for (const completed of step.toolCalls) journaledCallIds.add(completed.call.toolCallId);
763
+ if (resumed && options.directive?.approvals !== void 0 && state.pendingApprovals.length > 0) {
764
+ const decisions = new Map(options.directive.approvals.map((d) => [d.toolCallId, d]));
765
+ const stillPending = [];
766
+ for (const approval of state.pendingApprovals) {
767
+ const decision = decisions.get(approval.toolCallId);
768
+ if (decision === void 0) {
769
+ stillPending.push(approval);
770
+ continue;
771
+ }
772
+ if (decision.granted) {
773
+ yield {
774
+ type: "tool.approval.granted",
775
+ toolCallId: approval.toolCallId
776
+ };
777
+ if (journaledCallIds.has(approval.toolCallId) && messages.some((m) => m.role === "tool" && m.toolCallId === approval.toolCallId)) continue;
778
+ resumedApprovedCalls.push({
779
+ toolCallId: approval.toolCallId,
780
+ toolName: approval.toolName,
781
+ args: approval.args
782
+ });
783
+ } else {
784
+ yield {
785
+ type: "tool.approval.denied",
786
+ toolCallId: approval.toolCallId,
787
+ ...decision.reason !== void 0 ? { reason: decision.reason } : {}
788
+ };
789
+ messages.push({
790
+ role: "tool",
791
+ toolCallId: approval.toolCallId,
792
+ content: `Error: tool approval denied${decision.reason ? `: ${decision.reason}` : ""}`
793
+ });
794
+ state.messages.push({
795
+ role: "tool",
796
+ toolCallId: approval.toolCallId,
797
+ content: `Error: tool approval denied${decision.reason ? `: ${decision.reason}` : ""}`
798
+ });
799
+ }
800
+ }
801
+ state.pendingApprovals.splice(0, state.pendingApprovals.length, ...stillPending);
802
+ if (stillPending.length === 0) state.status = "running";
803
+ }
804
+ const systemPrefixLength = countLeadingSystemMessages(messages);
805
+ const runContextBase = {
806
+ runId: state.id,
807
+ sessionId,
808
+ ...userId !== void 0 ? { userId } : {},
809
+ agentId,
810
+ deps: options.deps ?? config.deps,
811
+ tracer,
812
+ signal,
813
+ usage: usageAcc,
814
+ stepNumber: 0,
815
+ messages,
816
+ state
817
+ };
818
+ if (resumed && (state.status === "awaiting_approval" || state.status === "failed")) return yield* finishRun(state, finalSnapshot);
819
+ if (resumed && resumedApprovedCalls.length > 0) {
820
+ state.steps.push({
821
+ stepNumber: 0,
822
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
823
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
824
+ usage: zeroUsage(),
825
+ toolCalls: [],
826
+ agentId: state.currentAgentId
827
+ });
828
+ yield* dispatchBatch(resumedApprovedCalls, toolExecutor, {
829
+ ...runContextBase,
830
+ stepNumber: 0,
831
+ messages
832
+ }, 0);
833
+ }
834
+ const promotedDeferred = /* @__PURE__ */ new Set();
835
+ if (resumed && state.promotedTools !== void 0) for (const name of state.promotedTools) promotedDeferred.add(name);
836
+ /**
837
+ * Dispatch a batch of (non-handoff) tool calls through the
838
+ * {@link ToolExecutor} and surface the results as `AgentEvent`s.
839
+ *
840
+ * The agent owns the `tool.execute.start` / `.end` / `.error`
841
+ * lifecycle (derived deterministically from the returned
842
+ * {@link CompletedToolCall} outcomes) so every outcome kind —
843
+ * success, unknown-tool, invalid-input, sanitization-blocked,
844
+ * execution error — yields a consistent event and tool message,
845
+ * preserving the pre-WI-03 stream shape (R10). The executor's
846
+ * genuinely-live streaming events (`tool.execute.progress` /
847
+ * `.partial`, emitted only by streaming-hint tools) are bridged
848
+ * through Adapter E while the batch runs and are purely additive.
849
+ *
850
+ * Parallelism (WI-04): the executor runs independent calls in this
851
+ * batch concurrently, bounded by `maxParallelTools`. `tool.execute.start`
852
+ * is emitted up-front in call order and `.end` / `.error` after the
853
+ * batch settles, also in call order — so result mapping and tool-message
854
+ * history are deterministic regardless of which call finishes first,
855
+ * while `.progress` / `.partial` events for concurrent calls interleave
856
+ * (keyed by `toolCallId`). Tools declaring `executionMode: 'sequential'`
857
+ * are serialised by the executor and never overlap.
858
+ */
859
+ async function* dispatchBatch(calls, executor, runContext, stepNum) {
860
+ if (calls.length === 0) return;
861
+ for (const call of calls) yield {
862
+ type: "tool.execute.start",
863
+ toolCallId: call.toolCallId
864
+ };
865
+ const bridge = createExecutorEventBridge();
866
+ activeExecutorBridge = bridge;
867
+ const resultsPromise = executor.executeBatch({
868
+ calls,
869
+ runContext,
870
+ stepNumber: stepNum
871
+ });
872
+ const closeOnSettle = resultsPromise.then(() => bridge.close(), () => bridge.close());
873
+ for await (const event of bridge.drain()) if (event.type === "tool.execute.progress" || event.type === "tool.execute.partial") yield event;
874
+ await closeOnSettle;
875
+ activeExecutorBridge = void 0;
876
+ const completed = await resultsPromise;
877
+ const byCallId = new Map(completed.map((c) => [c.outcome.toolCallId, c]));
878
+ const stepEntry = state.steps[state.steps.length - 1];
879
+ for (const call of calls) {
880
+ const result = byCallId.get(call.toolCallId);
881
+ if (result === void 0) continue;
882
+ if (stepEntry !== void 0) stepEntry.toolCalls.push(result);
883
+ const outcome = result.outcome;
884
+ if ("kind" in outcome) {
885
+ yield {
886
+ type: "tool.execute.error",
887
+ toolCallId: call.toolCallId,
888
+ error: outcome
889
+ };
890
+ const text = `Error: ${outcome.message}`;
891
+ messages.push({
892
+ role: "tool",
893
+ toolCallId: call.toolCallId,
894
+ content: text
895
+ });
896
+ state.messages.push({
897
+ role: "tool",
898
+ toolCallId: call.toolCallId,
899
+ content: text
900
+ });
901
+ causalityMonitor?.recordCall(`tool.error:${call.toolName}`);
902
+ } else {
903
+ const output = outcome.output;
904
+ yield {
905
+ type: "tool.execute.end",
906
+ toolCallId: call.toolCallId,
907
+ result: output,
908
+ durationMs: outcome.durationMs
909
+ };
910
+ const handle = outcome.resultHandle;
911
+ const text = handle !== void 0 ? `${handle.preview}\n\n[Full result${handle.bytes !== void 0 ? ` (${handle.bytes} bytes)` : ""} stored behind a handle. Call read_result with handle ${JSON.stringify(handle.uri)} to retrieve it — optionally narrow with offset/length (bytes) or startLine/endLine.]` : typeof output === "string" ? output : JSON.stringify(output);
912
+ messages.push({
913
+ role: "tool",
914
+ toolCallId: call.toolCallId,
915
+ content: text
916
+ });
917
+ state.messages.push({
918
+ role: "tool",
919
+ toolCallId: call.toolCallId,
920
+ content: text
921
+ });
922
+ causalityMonitor?.recordCall(`tool:${call.toolName}`);
923
+ if (call.toolName === TOOL_SEARCH_NAME) recordToolSearchPromotions(output, promotedDeferred);
924
+ }
925
+ }
926
+ }
927
+ /**
928
+ * Auto-compaction trigger (WI-09 / P1-1). Before assembling each
929
+ * provider request, ask the memory {@link ContextEngine} whether the
930
+ * in-flight buffer has crossed its per-provider threshold
931
+ * (`shouldCompact`); when it has, summarise the older turns
932
+ * (`compactNow`, `source: 'auto-trigger'`), splice the result back in
933
+ * — preserving the byte-stable system prefix and the most-recent
934
+ * turns verbatim — and emit `context.compacted`. The compaction is
935
+ * configured on the memory facade (`createMemory({ contextEngine })`,
936
+ * RB-46); there is no parallel agent-level knob.
937
+ *
938
+ * No-op when no memory is wired, when compaction is disabled or below
939
+ * threshold (the engine returns `false`), or for `secret`-tier runs
940
+ * (secret history is never shipped to the summarizer — a less-trusted
941
+ * external sink; per-result handle references land in WI-10). Best
942
+ * effort: a misconfigured engine (e.g. no summarizer) is swallowed and
943
+ * the run proceeds uncompacted rather than aborting mid-flight.
944
+ *
945
+ * Operator-requested compactions (`agent.compact()`, CE-3/AG-13) are
946
+ * serviced here too, FIRST — the queue carries the `compact()` promise
947
+ * resolvers, and manual requests bypass the trigger evaluation.
948
+ */
949
+ async function* maybeAutoCompact() {
950
+ while (pendingManualCompacts.length > 0) {
951
+ const pending = pendingManualCompacts.shift();
952
+ if (pending !== void 0) yield* serviceManualCompact(pending);
953
+ }
954
+ const mem = memory;
955
+ if (mem === void 0) return;
956
+ if (config.sensitivity === "secret") return;
957
+ const engine = mem.contextEngine;
958
+ if (!await engine.shouldCompact(messages).catch(() => false)) return;
959
+ const startedAt = Date.now();
960
+ const envelope = await engine.compactNow({
961
+ scope: {
962
+ userId: state.userId ?? agentId,
963
+ sessionId,
964
+ agentId
965
+ },
966
+ runId: state.id,
967
+ sessionId,
968
+ agentId,
969
+ source: "auto-trigger",
970
+ messages: messages.slice(systemPrefixLength),
971
+ memory: mem
972
+ }).catch(() => void 0);
973
+ if (envelope === void 0) return;
974
+ if (envelope.result.droppedMessageIndices.length === 0) return;
975
+ spliceCompacted(envelope);
976
+ yield {
977
+ type: "context.compacted",
978
+ runId: state.id,
979
+ sessionId,
980
+ agentId,
981
+ beforeTokens: envelope.result.beforeTokens,
982
+ afterTokens: envelope.result.afterTokens,
983
+ summaryTokens: envelope.result.summaryTokens,
984
+ durationMs: Date.now() - startedAt,
985
+ source: "auto-trigger",
986
+ hooksFiredCount: envelope.result.hooksFiredCount
987
+ };
988
+ }
989
+ /**
990
+ * Prefix-pinned splice shared by the auto + manual compaction paths
991
+ * (CE-3): stable system prefix + [summary, ...recent turns], with the
992
+ * post-compaction hooks' text Context Essentials re-anchored as a
993
+ * trailing system message so they survive the trim (RB-46). Mutates
994
+ * BOTH the live loop buffer and `state.messages`.
995
+ */
996
+ function spliceCompacted(envelope) {
997
+ const rebuilt = [...messages.slice(0, systemPrefixLength), ...envelope.result.trimmedMessages];
998
+ const essentials = envelope.extraContent.map((part) => typeof part === "object" && part !== null && "text" in part ? String(part.text) : "").filter((text) => text.length > 0).join("\n\n");
999
+ if (essentials.length > 0) rebuilt.push({
1000
+ role: "system",
1001
+ content: essentials
1002
+ });
1003
+ messages.splice(0, messages.length, ...rebuilt);
1004
+ state.messages.splice(0, state.messages.length, ...rebuilt);
1005
+ }
1006
+ /**
1007
+ * Service one `agent.compact()` request inside the loop (CE-3/AG-13):
1008
+ * same prefix-pinned splice as the auto path, `source: 'manual'` (or
1009
+ * the caller's `'pre-step'`), `preserveRecentTurns` forwarded as a
1010
+ * per-call strategy override. An engine failure rejects the caller's
1011
+ * promise but never aborts the live run; a summarize that trims
1012
+ * nothing resolves `applied: false` without an event.
1013
+ */
1014
+ async function* serviceManualCompact(pending) {
1015
+ const mem = memory;
1016
+ if (mem === void 0) {
1017
+ pending.resolve(noopCompactionResult("no-memory"));
1018
+ return;
1019
+ }
1020
+ const source = pending.options?.source ?? "manual";
1021
+ const startedAt = Date.now();
1022
+ let envelope;
1023
+ try {
1024
+ envelope = await mem.contextEngine.compactNow({
1025
+ scope: {
1026
+ userId: state.userId ?? agentId,
1027
+ sessionId,
1028
+ agentId
1029
+ },
1030
+ runId: state.id,
1031
+ sessionId,
1032
+ agentId,
1033
+ source,
1034
+ messages: messages.slice(systemPrefixLength),
1035
+ memory: mem,
1036
+ ...pending.options?.preserveRecentTurns !== void 0 ? { preserveRecentTurns: pending.options.preserveRecentTurns } : {}
1037
+ });
1038
+ } catch (cause) {
1039
+ pending.reject(cause);
1040
+ return;
1041
+ }
1042
+ const { result } = envelope;
1043
+ const applied = result.droppedMessageIndices.length > 0;
1044
+ if (applied) {
1045
+ spliceCompacted(envelope);
1046
+ yield {
1047
+ type: "context.compacted",
1048
+ runId: state.id,
1049
+ sessionId,
1050
+ agentId,
1051
+ beforeTokens: result.beforeTokens,
1052
+ afterTokens: result.afterTokens,
1053
+ summaryTokens: result.summaryTokens,
1054
+ durationMs: Date.now() - startedAt,
1055
+ source,
1056
+ hooksFiredCount: result.hooksFiredCount
1057
+ };
1058
+ }
1059
+ pending.resolve({
1060
+ beforeTokens: result.beforeTokens,
1061
+ afterTokens: result.afterTokens,
1062
+ summaryTokens: result.summaryTokens,
1063
+ durationMs: Date.now() - startedAt,
1064
+ hooksFiredCount: result.hooksFiredCount,
1065
+ summary: result.summary ?? "",
1066
+ applied,
1067
+ ...applied ? {} : { skippedReason: "nothing-to-trim" }
1068
+ });
1069
+ }
1070
+ const handoffNames = Array.from(handoffMap.keys());
1071
+ let stepNumber = 0;
1072
+ let lastStepCalledToolNames = [];
1073
+ async function* emitCancellation() {
1074
+ yield {
1075
+ type: "agent.cancelling",
1076
+ runId: state.id,
1077
+ drain: pendingAbort?.drain ?? false,
1078
+ onPendingApprovals: pendingAbort?.onPendingApprovals ?? "deny"
1079
+ };
1080
+ const policy = pendingAbort?.onPendingApprovals ?? "deny";
1081
+ if (policy === "deny") {
1082
+ const drained = state.pendingApprovals.splice(0, state.pendingApprovals.length);
1083
+ for (const approval of drained) yield {
1084
+ type: "tool.approval.denied",
1085
+ toolCallId: approval.toolCallId,
1086
+ reason: "auto-denied: agent.abort()"
1087
+ };
1088
+ } else if (policy === "fail") {
1089
+ state.status = "failed";
1090
+ state.error = {
1091
+ message: "aborted with pending approvals",
1092
+ code: "run-aborted"
1093
+ };
1094
+ yield {
1095
+ type: "agent.error",
1096
+ error: {
1097
+ message: "aborted with pending approvals",
1098
+ code: "run-aborted"
1099
+ }
1100
+ };
1101
+ return true;
1102
+ }
1103
+ state.status = "aborted";
1104
+ return false;
1105
+ }
1106
+ try {
1107
+ while (!stopWhen.check(state)) {
1108
+ while (externalEventQueue.length > 0) {
1109
+ const ev = externalEventQueue.shift();
1110
+ if (ev !== void 0) yield ev;
1111
+ }
1112
+ if (signal.aborted) {
1113
+ if (yield* emitCancellation()) return yield* finishRun(state, finalSnapshot);
1114
+ break;
1115
+ }
1116
+ stepNumber += 1;
1117
+ const stepStart = (/* @__PURE__ */ new Date()).toISOString();
1118
+ if (pendingSteer.length > 0) {
1119
+ for (const m of pendingSteer) {
1120
+ messages.push(m);
1121
+ state.messages.push(m);
1122
+ }
1123
+ pendingSteer = [];
1124
+ }
1125
+ yield {
1126
+ type: "step.start",
1127
+ stepNumber
1128
+ };
1129
+ yield* maybeAutoCompact();
1130
+ const stepCtx = {
1131
+ ...runContextBase,
1132
+ stepNumber,
1133
+ messages
1134
+ };
1135
+ const overrides = config.prepareStep ? await config.prepareStep(stepCtx) : {};
1136
+ const useOverrideRegistry = overrides.tools !== void 0 && !isCodeMode;
1137
+ const stepRegistry = useOverrideRegistry ? buildToolRegistry({ tools: overrides.tools }).registry : toolRegistry;
1138
+ if (useOverrideRegistry) {
1139
+ registerToolSearch(stepRegistry);
1140
+ registerReadResult(stepRegistry, resultReader);
1141
+ }
1142
+ const stepExecutor = useOverrideRegistry ? makeToolExecutor(stepRegistry) : toolExecutor;
1143
+ const handoffTools = handoffNames.map((n) => {
1144
+ const h = handoffMap.get(n);
1145
+ if (h === void 0) throw new ToolNotFoundError(n);
1146
+ return buildHandoffTool(h.agent);
1147
+ });
1148
+ let stepTools;
1149
+ if (isCodeMode) stepTools = [...codeModeAdvertised, ...handoffTools];
1150
+ else {
1151
+ const eagerTools = stepRegistry.listEager();
1152
+ const promotedTools = promotedDeferred.size === 0 ? [] : orderPromotedTools(promotedDeferred, stepRegistry.listDeferred());
1153
+ stepTools = [
1154
+ ...eagerTools,
1155
+ ...promotedTools,
1156
+ ...handoffTools
1157
+ ];
1158
+ }
1159
+ const calledLastStep = new Set(lastStepCalledToolNames);
1160
+ const toolPreferences = stepTools.filter((t) => calledLastStep.has(t.name)).map((t) => {
1161
+ return t.preferredModel;
1162
+ });
1163
+ const primary = resolvePreferredModel({
1164
+ ...overrides.provider !== void 0 ? { prepareStepProvider: overrides.provider } : {},
1165
+ toolPreferredModels: toolPreferences,
1166
+ ...config.preferredModel !== void 0 ? { agentPreferredModel: config.preferredModel } : {},
1167
+ agentDefaultProvider: config.provider,
1168
+ ...config.modelTierMap !== void 0 ? { modelTierMap: config.modelTierMap } : {}
1169
+ });
1170
+ const fallbackChain = primary.source === "prepare-step" ? [primary.resolvedProvider] : [primary.resolvedProvider, ...(config.fallbackModels ?? []).map(specToProvider)];
1171
+ const reasoningPolicy = effectiveReasoningRetention(config.reasoningRetention, primary.resolvedProvider);
1172
+ if (reasoningPolicy === "strip") {
1173
+ const { stripped } = stripReasoningFromMessages(messages);
1174
+ if (stripped > 0) stripReasoningFromMessages(state.messages);
1175
+ }
1176
+ const toolDefs = stepTools.map((t) => toolToDefinition(t));
1177
+ const baseRequest = {
1178
+ messages: structuredInstruction === void 0 ? messages : [...messages, {
1179
+ role: "system",
1180
+ content: structuredInstruction
1181
+ }],
1182
+ ...config.outputType !== void 0 ? { outputType: {
1183
+ kind: config.outputType.kind,
1184
+ ...config.outputType.description !== void 0 ? { description: config.outputType.description } : {},
1185
+ ...config.outputType.jsonSchema !== void 0 ? { jsonSchema: config.outputType.jsonSchema } : {}
1186
+ } } : {},
1187
+ tools: toolDefs,
1188
+ ...overrides.toolChoice !== void 0 ? { toolChoice: overrides.toolChoice } : config.toolChoice !== void 0 ? { toolChoice: config.toolChoice } : {},
1189
+ metadata: {
1190
+ sessionId,
1191
+ agentId,
1192
+ ...userId !== void 0 ? { userId } : {},
1193
+ runId: state.id,
1194
+ stepNumber
1195
+ },
1196
+ signal,
1197
+ ...overrides.temperature !== void 0 ? { temperature: overrides.temperature } : {},
1198
+ ...overrides.maxTokens !== void 0 ? { maxTokens: overrides.maxTokens } : {},
1199
+ reasoningRetention: reasoningPolicy
1200
+ };
1201
+ const stepUsage = zeroUsage();
1202
+ let attempt = 0;
1203
+ let textBuffer = "";
1204
+ let providerForStep = primary.resolvedProvider;
1205
+ let lastModelId = primary.resolvedModelId;
1206
+ let modelSucceeded = false;
1207
+ let lastError;
1208
+ let finalCalls = [];
1209
+ let stepReasoningParts = [];
1210
+ for (let chainIdx = 0; chainIdx < fallbackChain.length; chainIdx++) {
1211
+ const candidate = fallbackChain[chainIdx];
1212
+ if (candidate === void 0) continue;
1213
+ providerForStep = candidate;
1214
+ const providerModelId = providerForStep.modelId;
1215
+ if (chainIdx > 0) {
1216
+ attempt += 1;
1217
+ const reason = lastError ? isAgentFallbackEligible(lastError, fallbackPolicy).reason ?? "transient" : "transient";
1218
+ yield {
1219
+ type: "agent.model.fellback",
1220
+ runId: state.id,
1221
+ sessionId,
1222
+ agentId,
1223
+ from: lastModelId,
1224
+ to: providerModelId,
1225
+ reason,
1226
+ stepNumber,
1227
+ attempt
1228
+ };
1229
+ lastModelId = providerModelId;
1230
+ }
1231
+ const evState = {
1232
+ textBuffer: "",
1233
+ reasoningBuffer: "",
1234
+ reasoningParts: [],
1235
+ calls: /* @__PURE__ */ new Map(),
1236
+ finalCalls: []
1237
+ };
1238
+ let providerError;
1239
+ let providerCallCompleted = false;
1240
+ let providerStepUsage = zeroUsage();
1241
+ try {
1242
+ const stream$1 = providerForStep.stream(baseRequest);
1243
+ for await (const ev of stream$1) {
1244
+ if (signal.aborted && pendingAbort?.drain !== true) throw new AgentRuntimeError("run-aborted", "aborted");
1245
+ const out = handleProviderEvent(ev, evState);
1246
+ if (out.emit !== void 0) yield out.emit;
1247
+ if (out.providerError !== void 0) providerError = out.providerError;
1248
+ if (out.usage !== void 0) providerStepUsage = {
1249
+ promptTokens: providerStepUsage.promptTokens + out.usage.promptTokens,
1250
+ completionTokens: providerStepUsage.completionTokens + out.usage.completionTokens,
1251
+ totalTokens: providerStepUsage.totalTokens + out.usage.totalTokens,
1252
+ ...out.usage.reasoningTokens !== void 0 || providerStepUsage.reasoningTokens !== void 0 ? { reasoningTokens: (providerStepUsage.reasoningTokens ?? 0) + (out.usage.reasoningTokens ?? 0) } : {}
1253
+ };
1254
+ if (out.finished === true) providerCallCompleted = true;
1255
+ }
1256
+ } catch (cause) {
1257
+ if (signal.aborted || cause instanceof AgentRuntimeError && cause.code === "run-aborted") break;
1258
+ const message = cause instanceof Error ? cause.message : String(cause);
1259
+ providerError = {
1260
+ kind: classifyThrownProviderErrorKind(cause),
1261
+ message,
1262
+ cause
1263
+ };
1264
+ }
1265
+ if (providerError !== void 0) {
1266
+ lastError = providerError;
1267
+ if (!isAgentFallbackEligible(providerError, fallbackPolicy).eligible || chainIdx === fallbackChain.length - 1) {
1268
+ yield {
1269
+ type: "agent.error",
1270
+ error: {
1271
+ message: providerError.message,
1272
+ code: providerError.kind
1273
+ }
1274
+ };
1275
+ state.status = "failed";
1276
+ state.error = {
1277
+ message: providerError.message,
1278
+ code: providerError.kind
1279
+ };
1280
+ return yield* finishRun(state, finalSnapshot);
1281
+ }
1282
+ continue;
1283
+ }
1284
+ if (providerCallCompleted) {
1285
+ modelSucceeded = true;
1286
+ textBuffer = evState.textBuffer;
1287
+ finalCalls = evState.finalCalls;
1288
+ if (evState.reasoningBuffer.length > 0) stepReasoningParts = [{
1289
+ type: "reasoning",
1290
+ text: evState.reasoningBuffer
1291
+ }];
1292
+ stepUsage.promptTokens += providerStepUsage.promptTokens;
1293
+ stepUsage.completionTokens += providerStepUsage.completionTokens;
1294
+ stepUsage.totalTokens += providerStepUsage.totalTokens;
1295
+ if (providerStepUsage.reasoningTokens !== void 0) stepUsage.reasoningTokens = (stepUsage.reasoningTokens ?? 0) + providerStepUsage.reasoningTokens;
1296
+ break;
1297
+ }
1298
+ }
1299
+ if (signal.aborted && !modelSucceeded) {
1300
+ yield* emitCancellation();
1301
+ return yield* finishRun(state, finalSnapshot);
1302
+ }
1303
+ if (!modelSucceeded) {
1304
+ yield {
1305
+ type: "agent.error",
1306
+ error: {
1307
+ message: "all configured providers failed without finishing",
1308
+ code: "no-provider-completed"
1309
+ }
1310
+ };
1311
+ state.status = "failed";
1312
+ state.error = {
1313
+ message: "no provider completed",
1314
+ code: "no-provider-completed"
1315
+ };
1316
+ return yield* finishRun(state, finalSnapshot);
1317
+ }
1318
+ usageAcc.add(lastModelId, stepUsage);
1319
+ addModelUsage(state, lastModelId, stepUsage);
1320
+ state.usage.promptTokens += stepUsage.promptTokens;
1321
+ state.usage.completionTokens += stepUsage.completionTokens;
1322
+ state.usage.totalTokens += stepUsage.totalTokens;
1323
+ if (stepUsage.reasoningTokens !== void 0) state.usage.reasoningTokens = (state.usage.reasoningTokens ?? 0) + stepUsage.reasoningTokens;
1324
+ const leakCheck = causalityMonitor !== void 0 && textBuffer.length > 0 ? causalityMonitor.checkMessage(textBuffer) : void 0;
1325
+ const leakBlocked = leakCheck?.leakDetected === true && leakCheck.decision === "block";
1326
+ const assistant = buildAssistantMessage(leakBlocked ? LATERAL_LEAK_BLOCKED_NOTICE : textBuffer, stepReasoningParts, finalCalls, agentId, reasoningPolicy);
1327
+ messages.push(assistant);
1328
+ state.messages.push(assistant);
1329
+ if (leakCheck?.leakDetected === true) {
1330
+ const sha = sha256Hex(textBuffer);
1331
+ yield {
1332
+ type: "agent.lateral-leak.detected",
1333
+ runId: state.id,
1334
+ sessionId,
1335
+ agentId,
1336
+ vector: leakCheck.vector,
1337
+ severity: leakCheck.severity,
1338
+ causalityChain: leakCheck.causalityChain,
1339
+ messageContentSha256: sha,
1340
+ ...leakCheck.matchedPattern !== void 0 ? { matchedPattern: leakCheck.matchedPattern } : {},
1341
+ decision: leakCheck.decision,
1342
+ detectedAtIso: (/* @__PURE__ */ new Date()).toISOString()
1343
+ };
1344
+ }
1345
+ const handoffCalls = finalCalls.filter((c) => handoffMap.has(c.toolName));
1346
+ if (handoffCalls.length > 1) throw new MultipleHandoffsInStepError(handoffCalls.map((c) => c.toolName));
1347
+ const stepRecord = {
1348
+ stepNumber,
1349
+ startedAt: stepStart,
1350
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1351
+ usage: stepUsage,
1352
+ toolCalls: [],
1353
+ agentId: state.currentAgentId
1354
+ };
1355
+ state.steps.push(stepRecord);
1356
+ lastStepCalledToolNames = finalCalls.map((c) => c.toolName);
1357
+ if (textBuffer.length > 0 && !leakBlocked) {
1358
+ finalSnapshot.output = textBuffer;
1359
+ yield {
1360
+ type: "text.complete",
1361
+ text: textBuffer
1362
+ };
1363
+ }
1364
+ if (finalCalls.length > 0) {
1365
+ const execRunContext = {
1366
+ ...runContextBase,
1367
+ stepNumber,
1368
+ messages
1369
+ };
1370
+ let batch = [];
1371
+ for (const call of finalCalls) {
1372
+ const handoff = handoffMap.get(call.toolName);
1373
+ if (handoff !== void 0) {
1374
+ if (batch.length > 0) {
1375
+ yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
1376
+ batch = [];
1377
+ }
1378
+ yield {
1379
+ type: "tool.execute.start",
1380
+ toolCallId: call.toolCallId
1381
+ };
1382
+ const filter = handoff.filter ?? filters.defaultHandoffFilter();
1383
+ const filtered = filter(messages);
1384
+ const targetId = handoff.agent.id;
1385
+ const handoffRec = {
1386
+ fromAgentId: agentId,
1387
+ toAgentId: targetId,
1388
+ stepNumber,
1389
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1390
+ inputFilter: filter.descriptor,
1391
+ secretsInheritance: "inherit-allowlist",
1392
+ inheritedSecrets: []
1393
+ };
1394
+ state.handoffs.push(handoffRec);
1395
+ yield {
1396
+ type: "handoff",
1397
+ fromAgentId: agentId,
1398
+ toAgentId: targetId
1399
+ };
1400
+ state.currentAgentId = targetId;
1401
+ const subAgent = handoff.agent;
1402
+ const subStart = Date.now();
1403
+ const subOutputs = [];
1404
+ let subResult;
1405
+ const subStream = subAgent.stream(filtered, {
1406
+ signal,
1407
+ ...options.deps !== void 0 || config.deps !== void 0 ? { deps: options.deps ?? config.deps } : {},
1408
+ sessionId
1409
+ });
1410
+ for await (const subEv of subStream) if (subEv.type === "text.complete") subOutputs.push(subEv.text);
1411
+ else if (subEv.type === "agent.end") subResult = subEv.result;
1412
+ const subDurationMs = Date.now() - subStart;
1413
+ const stepEntry = state.steps[state.steps.length - 1];
1414
+ if (subResult !== void 0 && subResult.status !== "completed") {
1415
+ const toolError = {
1416
+ toolCallId: call.toolCallId,
1417
+ toolName: call.toolName,
1418
+ kind: subResult.status === "aborted" ? "aborted" : "execution_failed",
1419
+ message: `handoff to '${targetId}' ${subResult.status}${subResult.error !== void 0 ? `: ${subResult.error.message}` : ""}`
1420
+ };
1421
+ if (stepEntry !== void 0) stepEntry.toolCalls.push({
1422
+ call,
1423
+ outcome: toolError,
1424
+ stepNumber
1425
+ });
1426
+ yield {
1427
+ type: "tool.execute.error",
1428
+ toolCallId: call.toolCallId,
1429
+ error: toolError
1430
+ };
1431
+ const text = `Error: ${toolError.message}`;
1432
+ messages.push({
1433
+ role: "tool",
1434
+ toolCallId: call.toolCallId,
1435
+ content: text
1436
+ });
1437
+ state.messages.push({
1438
+ role: "tool",
1439
+ toolCallId: call.toolCallId,
1440
+ content: text
1441
+ });
1442
+ continue;
1443
+ }
1444
+ const result = subOutputs.join("");
1445
+ const completed = {
1446
+ call,
1447
+ outcome: {
1448
+ toolCallId: call.toolCallId,
1449
+ toolName: call.toolName,
1450
+ output: result,
1451
+ durationMs: subDurationMs
1452
+ },
1453
+ stepNumber
1454
+ };
1455
+ if (stepEntry !== void 0) stepEntry.toolCalls.push(completed);
1456
+ yield {
1457
+ type: "tool.execute.end",
1458
+ toolCallId: call.toolCallId,
1459
+ result,
1460
+ durationMs: subDurationMs
1461
+ };
1462
+ messages.push({
1463
+ role: "tool",
1464
+ toolCallId: call.toolCallId,
1465
+ content: result
1466
+ });
1467
+ state.messages.push({
1468
+ role: "tool",
1469
+ toolCallId: call.toolCallId,
1470
+ content: result
1471
+ });
1472
+ continue;
1473
+ }
1474
+ if (await invokeNeedsApproval(stepRegistry.get(call.toolName), call.args, execRunContext, signal)) {
1475
+ if (batch.length > 0) {
1476
+ yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
1477
+ batch = [];
1478
+ }
1479
+ yield {
1480
+ type: "tool.execute.start",
1481
+ toolCallId: call.toolCallId
1482
+ };
1483
+ const approval = {
1484
+ toolCallId: call.toolCallId,
1485
+ toolName: call.toolName,
1486
+ args: call.args,
1487
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString()
1488
+ };
1489
+ state.pendingApprovals.push(approval);
1490
+ state.status = "awaiting_approval";
1491
+ const taintSnap = toolDataFlowGuard?.snapshotLedger(state.id);
1492
+ if (taintSnap !== void 0) state.taintSummary = taintSnap;
1493
+ if (promotedDeferred.size > 0) state.promotedTools = [...promotedDeferred];
1494
+ yield {
1495
+ type: "tool.approval.requested",
1496
+ toolCallId: call.toolCallId
1497
+ };
1498
+ if (config.checkpointStore !== void 0) await config.checkpointStore.put(state.id, "agent", {
1499
+ id: state.id,
1500
+ threadId: state.id,
1501
+ namespace: "agent",
1502
+ state: serializeRunState(state, { stripTracingApiKey: true }),
1503
+ channelVersions: {},
1504
+ stepNumber,
1505
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1506
+ }, {
1507
+ source: "sync",
1508
+ status: "suspended",
1509
+ nodeName: "agent.run"
1510
+ });
1511
+ return yield* finishRun(state, finalSnapshot);
1512
+ }
1513
+ batch.push(call);
1514
+ }
1515
+ if (batch.length > 0) yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
1516
+ }
1517
+ yield {
1518
+ type: "step.end",
1519
+ stepNumber,
1520
+ usage: stepUsage
1521
+ };
1522
+ if (finalCalls.length === 0) {
1523
+ state.status = "completed";
1524
+ break;
1525
+ }
1526
+ }
1527
+ } catch (cause) {
1528
+ const message = cause instanceof Error ? cause.message : String(cause);
1529
+ const code = cause instanceof AgentRuntimeError ? cause.code : "unknown";
1530
+ yield {
1531
+ type: "agent.error",
1532
+ error: {
1533
+ message,
1534
+ code
1535
+ }
1536
+ };
1537
+ state.status = "failed";
1538
+ state.error = {
1539
+ message,
1540
+ code
1541
+ };
1542
+ return yield* finishRun(state, finalSnapshot);
1543
+ } finally {
1544
+ if (parentSignal !== void 0) parentSignal.removeEventListener("abort", onParentAbort);
1545
+ }
1546
+ if (state.status === "running") {
1547
+ const message = `run stopped by stop condition: ${stopWhen.description}`;
1548
+ state.status = "failed";
1549
+ state.error = {
1550
+ message,
1551
+ code: "stop-condition"
1552
+ };
1553
+ yield {
1554
+ type: "agent.error",
1555
+ error: {
1556
+ message,
1557
+ code: "stop-condition"
1558
+ }
1559
+ };
1560
+ }
1561
+ if (state.status === "completed" && config.outputType?.kind === "structured") {
1562
+ const raw = String(finalSnapshot.output ?? "");
1563
+ try {
1564
+ const parsed = JSON.parse(stripJsonFence(raw));
1565
+ finalSnapshot.output = config.outputType.schema !== void 0 ? config.outputType.schema.parse(parsed) : parsed;
1566
+ } catch (cause) {
1567
+ const message = `structured output validation failed: ${cause instanceof Error ? cause.message : String(cause)}`;
1568
+ yield {
1569
+ type: "agent.error",
1570
+ error: {
1571
+ message,
1572
+ code: "output-validation-failed"
1573
+ }
1574
+ };
1575
+ state.status = "failed";
1576
+ state.error = {
1577
+ message,
1578
+ code: "output-validation-failed"
1579
+ };
1580
+ }
1581
+ }
1582
+ const outputGuards = config.guardrails?.output;
1583
+ if (state.status === "completed" && outputGuards !== void 0 && outputGuards.length > 0) {
1584
+ const composed = await composeGuardrails(outputGuards, finalSnapshot.output, {
1585
+ stage: "output",
1586
+ runId: state.id,
1587
+ sessionId,
1588
+ agentId
1589
+ });
1590
+ if (!composed.ok) {
1591
+ yield {
1592
+ type: "guardrail.tripped",
1593
+ guardrailName: composed.name,
1594
+ phase: "output",
1595
+ reason: composed.message
1596
+ };
1597
+ const message = `output guardrail '${composed.name}' blocked the run: ${composed.message}`;
1598
+ yield {
1599
+ type: "agent.error",
1600
+ error: {
1601
+ message,
1602
+ code: "guardrail-blocked"
1603
+ }
1604
+ };
1605
+ state.status = "failed";
1606
+ state.error = {
1607
+ message,
1608
+ code: "guardrail-blocked"
1609
+ };
1610
+ } else if (composed.value !== finalSnapshot.output) finalSnapshot.output = composed.value;
1611
+ }
1612
+ activeRunState = void 0;
1613
+ return yield* finishRun(state, finalSnapshot);
1614
+ }
1615
+ /**
1616
+ * Terminal wrapper around {@link finalize}: every exit path of the run
1617
+ * loop — completed, failed, aborted, suspended — ends the stream with
1618
+ * an `agent.end` event carrying the final {@link AgentResult} (AG-20).
1619
+ */
1620
+ async function* finishRun(state, snapshot) {
1621
+ while (pendingManualCompacts.length > 0) pendingManualCompacts.shift()?.resolve(noopCompactionResult("no-active-run"));
1622
+ const result = finalize(state, snapshot);
1623
+ if (result.status === "completed" || result.status === "failed") await spillWriter.clear?.(result.state.id).catch(() => {});
1624
+ yield {
1625
+ type: "agent.end",
1626
+ runId: result.state.id,
1627
+ result
1628
+ };
1629
+ return result;
1630
+ }
1631
+ function finalize(state, snapshot) {
1632
+ state.finishedAt = state.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1633
+ return {
1634
+ output: snapshot.output,
1635
+ usage: state.usage,
1636
+ status: state.status,
1637
+ ...state.error !== void 0 ? { error: state.error } : {},
1638
+ state
1639
+ };
1640
+ }
1641
+ const stream = (input, options) => {
1642
+ const opts = options ?? {};
1643
+ return { [Symbol.asyncIterator]: () => {
1644
+ const gen = runLoop(input, opts);
1645
+ return {
1646
+ async next() {
1647
+ const r = await gen.next();
1648
+ if (r.done === true) return {
1649
+ done: true,
1650
+ value: void 0
1651
+ };
1652
+ return {
1653
+ done: false,
1654
+ value: r.value
1655
+ };
1656
+ },
1657
+ async return() {
1658
+ await gen.return(void 0);
1659
+ return {
1660
+ done: true,
1661
+ value: void 0
1662
+ };
1663
+ }
1664
+ };
1665
+ } };
1666
+ };
1667
+ const run = async (input, options) => {
1668
+ const gen = runLoop(input, options ?? {});
1669
+ let next = await gen.next();
1670
+ while (next.done !== true) next = await gen.next();
1671
+ const result = next.value;
1672
+ if (result === void 0) throw new Error("unreachable: agent run loop ended without a result");
1673
+ return result;
1674
+ };
1675
+ const steer = (message) => {
1676
+ const { seed } = asMessages(message);
1677
+ pendingSteer.push(...seed);
1678
+ if (activeRunState !== void 0) externalEventQueue.push({
1679
+ type: "agent.steered",
1680
+ runId: activeRunState.id
1681
+ });
1682
+ };
1683
+ const followUp = (message) => {
1684
+ const { seed } = asMessages(message);
1685
+ pendingFollowUp.push(...seed);
1686
+ if (activeRunState !== void 0) externalEventQueue.push({
1687
+ type: "agent.followup.queued",
1688
+ runId: activeRunState.id
1689
+ });
1690
+ };
1691
+ const abort = (options) => {
1692
+ pendingAbort = options ?? {};
1693
+ abortController?.abort();
1694
+ };
1695
+ const toTool = (options = {}) => {
1696
+ const exposeTurns = options.exposeTurns ?? "final";
1697
+ return {
1698
+ name: options.name ?? `subagent_${config.name}`,
1699
+ description: options.description ?? `Invoke sub-agent '${config.name}'.`,
1700
+ inputSchema: {
1701
+ parse: (v) => v,
1702
+ safeParse: (v) => ({
1703
+ success: true,
1704
+ data: v
1705
+ }),
1706
+ toJSON: () => ({
1707
+ type: "object",
1708
+ properties: { input: { type: "string" } },
1709
+ required: ["input"]
1710
+ })
1711
+ },
1712
+ sideEffectClass: "side-effecting",
1713
+ async execute(input, ctx) {
1714
+ const callOpts = {
1715
+ ...ctx?.signal !== void 0 ? { signal: ctx.signal } : {},
1716
+ ...ctx?.runContext.deps !== void 0 ? { deps: ctx.runContext.deps } : {},
1717
+ ...ctx?.runContext.sessionId !== void 0 ? { sessionId: ctx.runContext.sessionId } : {}
1718
+ };
1719
+ const seed = options.inputFilter !== void 0 && ctx !== void 0 ? [...options.inputFilter(ctx.runContext.messages), {
1720
+ role: "user",
1721
+ content: input.input
1722
+ }] : input.input;
1723
+ if (exposeTurns === "all") {
1724
+ const turns = [];
1725
+ let endResult;
1726
+ for await (const ev of stream(seed, callOpts)) if (ev.type === "text.complete") turns.push(ev.text);
1727
+ else if (ev.type === "agent.end") endResult = ev.result;
1728
+ if (endResult !== void 0 && endResult.status !== "completed") throw new Error(`sub-agent '${config.name}' ${endResult.status}${endResult.error !== void 0 ? `: ${endResult.error.message}` : ""}`);
1729
+ return turns.join("\n\n");
1730
+ }
1731
+ const result = await run(seed, callOpts);
1732
+ if (result.status !== "completed") throw new Error(`sub-agent '${config.name}' ${result.status}${result.error !== void 0 ? `: ${result.error.message}` : ""}`);
1733
+ if (exposeTurns === "none") return "";
1734
+ return result.output;
1735
+ }
1736
+ };
1737
+ };
1738
+ const compact = async (options) => {
1739
+ if (memory === void 0) return noopCompactionResult("no-memory");
1740
+ if (config.sensitivity === "secret") return noopCompactionResult("sensitivity-gated");
1741
+ if (activeRunState === void 0) return noopCompactionResult("no-active-run");
1742
+ return await new Promise((resolve, reject) => {
1743
+ pendingManualCompacts.push({
1744
+ options,
1745
+ resolve,
1746
+ reject
1747
+ });
1748
+ });
1749
+ };
1750
+ const fanOut = async (options) => {
1751
+ const runId = activeRunState?.id ?? `run_${newId()}`;
1752
+ const sessionId = activeRunState?.sessionId ?? `session_${newId()}`;
1753
+ return runFanOut({
1754
+ children: options.children,
1755
+ ...options.maxConcurrentChildren !== void 0 ? { maxConcurrentChildren: options.maxConcurrentChildren } : {},
1756
+ ...options.perBudget !== void 0 ? { perBudget: options.perBudget } : {},
1757
+ ...options.mergeStrategy !== void 0 ? { mergeStrategy: options.mergeStrategy } : {},
1758
+ ...options.signal !== void 0 ? { signal: options.signal } : {},
1759
+ emit: (event) => {
1760
+ externalEventQueue.push(event);
1761
+ },
1762
+ ...config.mergeGuard !== void 0 ? { mergeGuard: config.mergeGuard } : {},
1763
+ runId,
1764
+ sessionId,
1765
+ agentId
1766
+ });
1767
+ };
1768
+ const progressFallbackRunId = `run_${newId()}`;
1769
+ const progress = {
1770
+ write: async (content, opts) => {
1771
+ const runId = activeRunState?.id ?? progressFallbackRunId;
1772
+ const ref = await progressIO.write(runId, content, opts);
1773
+ externalEventQueue.push({
1774
+ type: "agent.progress.written",
1775
+ runId,
1776
+ sessionId: activeRunState?.sessionId ?? "",
1777
+ agentId,
1778
+ ref
1779
+ });
1780
+ return ref;
1781
+ },
1782
+ read: async (opts) => {
1783
+ const queriedRunId = opts?.runId ?? activeRunState?.id ?? progressFallbackRunId;
1784
+ const refs = await progressIO.read(queriedRunId, opts);
1785
+ externalEventQueue.push({
1786
+ type: "agent.progress.read",
1787
+ runId: activeRunState?.id ?? queriedRunId,
1788
+ sessionId: activeRunState?.sessionId ?? "",
1789
+ agentId,
1790
+ refs,
1791
+ queriedRunId,
1792
+ queriedRole: opts?.role
1793
+ });
1794
+ return refs;
1795
+ }
1796
+ };
1797
+ config.sensitivity;
1798
+ return {
1799
+ id: agentId,
1800
+ config,
1801
+ stream,
1802
+ run,
1803
+ steer,
1804
+ followUp,
1805
+ abort,
1806
+ toTool,
1807
+ compact,
1808
+ fanOut,
1809
+ progress,
1810
+ registry: toolRegistry
1811
+ };
1812
+ }
1813
+ /**
1814
+ * Pre-execution approval screen (Adapter G / durable HITL). Evaluates a
1815
+ * (registry-resolved) tool's `needsApproval` against the realized args.
1816
+ * Returns `true` when the run must suspend before the tool executes.
1817
+ *
1818
+ * Actual execution flows through the `@graphorin/tools` executor, whose
1819
+ * `ApprovalGate` auto-grants because only no-approval / pre-approved
1820
+ * calls ever reach it; this probe is what keeps the suspend in the
1821
+ * agent so the durable-HITL contract (persist `RunState`, resume via
1822
+ * directive) is preserved.
1823
+ */
1824
+ async function invokeNeedsApproval(tool, args, baseCtx, signal) {
1825
+ const predicate = tool?.needsApproval;
1826
+ if (predicate === void 0 || predicate === false) return false;
1827
+ if (predicate === true) return true;
1828
+ const probeCtx = {
1829
+ toolCallId: "probe",
1830
+ runContext: baseCtx,
1831
+ signal,
1832
+ tracer: baseCtx.tracer,
1833
+ logger: NOOP_LOGGER,
1834
+ secrets: probeSecretsAccessor(),
1835
+ reportProgress: () => {},
1836
+ streamContent: () => {}
1837
+ };
1838
+ return Boolean(await predicate(args, probeCtx));
1839
+ }
1840
+ /**
1841
+ * Rejecting secrets accessor used only by the {@link invokeNeedsApproval}
1842
+ * probe. Real tool execution resolves secrets through the executor's
1843
+ * ACL-scoped accessor; an approval predicate has no legitimate need to
1844
+ * read secret material, so every `require(...)` rejects.
1845
+ */
1846
+ function probeSecretsAccessor() {
1847
+ const rejector = (_key, _options) => Promise.reject(/* @__PURE__ */ new Error("secrets.require is unavailable inside a needsApproval predicate"));
1848
+ return { require: rejector };
1849
+ }
1850
+
1851
+ //#endregion
1852
+ export { createAgent };
1853
+ //# sourceMappingURL=factory.js.map