@axlsdk/axl 0.19.1 → 0.20.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.
package/dist/index.js CHANGED
@@ -1,130 +1,9 @@
1
1
  import {
2
2
  __require
3
- } from "./chunk-JBLQKU6X.js";
3
+ } from "./chunk-NVPA4RY6.js";
4
4
 
5
5
  // src/tool.ts
6
- var DEFAULT_MAX_STRING_LENGTH = 1e4;
7
- function validateStringLengths(value, maxLen, path = "") {
8
- if (typeof value === "string") {
9
- if (value.length > maxLen) {
10
- throw new Error(
11
- `String argument${path ? ` at "${path}"` : ""} exceeds maximum length (${value.length} > ${maxLen})`
12
- );
13
- }
14
- } else if (Array.isArray(value)) {
15
- for (let i = 0; i < value.length; i++) {
16
- validateStringLengths(value[i], maxLen, path ? `${path}[${i}]` : `[${i}]`);
17
- }
18
- } else if (value !== null && typeof value === "object") {
19
- for (const [key, val] of Object.entries(value)) {
20
- validateStringLengths(val, maxLen, path ? `${path}.${key}` : key);
21
- }
22
- }
23
- }
24
- function sleep(ms) {
25
- return new Promise((resolve2) => setTimeout(resolve2, ms));
26
- }
27
- function getBackoffMs(attempt, strategy) {
28
- switch (strategy) {
29
- case "none":
30
- return 0;
31
- case "linear":
32
- return attempt * 1e3;
33
- case "exponential":
34
- return Math.pow(2, attempt - 1) * 1e3;
35
- }
36
- }
37
- function tool(config) {
38
- const retryPolicy = {
39
- attempts: config.retry?.attempts ?? 1,
40
- backoff: config.retry?.backoff ?? "exponential",
41
- on: config.retry?.on
42
- };
43
- const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
44
- const execute = async (input, ctx) => {
45
- const parsed = config.input.parse(input);
46
- if (maxStringLen > 0) {
47
- validateStringLengths(parsed, maxStringLen);
48
- }
49
- const maxAttempts = retryPolicy.attempts ?? 1;
50
- let lastError;
51
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
52
- try {
53
- return await config.handler(parsed, ctx);
54
- } catch (err) {
55
- lastError = err instanceof Error ? err : new Error(String(err));
56
- if (attempt === maxAttempts) break;
57
- if (retryPolicy.on && !retryPolicy.on(lastError)) {
58
- break;
59
- }
60
- const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
61
- if (backoffMs > 0) {
62
- await sleep(backoffMs);
63
- }
64
- }
65
- }
66
- throw lastError;
67
- };
68
- return {
69
- name: config.name,
70
- description: config.description,
71
- inputSchema: config.input,
72
- sensitive: config.sensitive ?? false,
73
- retry: retryPolicy,
74
- requireApproval: config.requireApproval ?? false,
75
- hooks: config.hooks,
76
- ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
77
- async run(ctx, input) {
78
- const startTime = Date.now();
79
- try {
80
- let processedInput = input;
81
- if (config.hooks?.before) {
82
- processedInput = await config.hooks.before(processedInput, ctx);
83
- }
84
- let result = await execute(processedInput, ctx);
85
- if (config.hooks?.after) {
86
- result = await config.hooks.after(result, ctx);
87
- }
88
- ctx.log("tool_call_complete", {
89
- tool: config.name,
90
- duration: Date.now() - startTime
91
- });
92
- return result;
93
- } catch (err) {
94
- ctx.log("tool_call_error", {
95
- tool: config.name,
96
- error: err instanceof Error ? err.message : String(err),
97
- duration: Date.now() - startTime
98
- });
99
- throw err;
100
- }
101
- },
102
- _execute: execute
103
- };
104
- }
105
-
106
- // src/providers/types.ts
107
- function resolveThinkingOptions(options) {
108
- if (options.thinkingBudget !== void 0 && options.thinkingBudget < 0) {
109
- throw new Error(`thinkingBudget must be non-negative, got ${options.thinkingBudget}`);
110
- }
111
- const effort = options.effort;
112
- const thinkingBudget = options.thinkingBudget;
113
- const hasBudgetOverride = thinkingBudget !== void 0 && thinkingBudget > 0;
114
- return {
115
- effort,
116
- thinkingBudget,
117
- includeThoughts: options.includeThoughts ?? false,
118
- // Budget override wins: effort: 'none' + thinkingBudget: 5000 → thinking enabled
119
- thinkingDisabled: (effort === "none" || thinkingBudget === 0) && !hasBudgetOverride,
120
- activeEffort: effort && effort !== "none" ? effort : void 0,
121
- hasBudgetOverride
122
- };
123
- }
124
- async function resolveApiKey(source) {
125
- if (typeof source === "function") return await source() ?? "";
126
- return source ?? "";
127
- }
6
+ import { ZodError } from "zod";
128
7
 
129
8
  // src/errors.ts
130
9
  var AxlError = class extends Error {
@@ -135,6 +14,55 @@ var AxlError = class extends Error {
135
14
  this.code = code;
136
15
  }
137
16
  };
17
+ var EventStreamOverflowError = class extends Error {
18
+ maxQueued;
19
+ eventType;
20
+ constructor(maxQueued, eventType, cause) {
21
+ super(
22
+ `AxlEventBus queue exceeded maxQueued=${maxQueued} (event type: ${eventType}). Consumer is too slow or the producer is unbounded. Configure \`maxQueued\`/\`onOverflow\` on the runtime, or set maxQueued: Infinity to disable.`
23
+ );
24
+ this.name = "EventStreamOverflowError";
25
+ this.maxQueued = maxQueued;
26
+ this.eventType = eventType;
27
+ if (cause !== void 0) {
28
+ Object.defineProperty(this, "cause", {
29
+ configurable: true,
30
+ enumerable: false,
31
+ value: cause
32
+ });
33
+ }
34
+ }
35
+ };
36
+ function isEventStreamOverflowError(error) {
37
+ return error instanceof EventStreamOverflowError;
38
+ }
39
+ function rethrowEventStreamOverflow(error) {
40
+ if (isEventStreamOverflowError(error)) throw error;
41
+ }
42
+ function preserveErrorCause(error, cause) {
43
+ if (cause === void 0 || cause === error || "cause" in error) return error;
44
+ Object.defineProperty(error, "cause", {
45
+ configurable: true,
46
+ enumerable: false,
47
+ value: cause
48
+ });
49
+ return error;
50
+ }
51
+ var ToolFailure = class extends AxlError {
52
+ modelMessage;
53
+ constructor(options) {
54
+ super(options.code ?? "TOOL_FAILURE", options.message);
55
+ this.name = "ToolFailure";
56
+ this.modelMessage = options.modelMessage;
57
+ if (options.cause !== void 0) {
58
+ Object.defineProperty(this, "cause", {
59
+ configurable: true,
60
+ enumerable: false,
61
+ value: options.cause
62
+ });
63
+ }
64
+ }
65
+ };
138
66
  var ToolModelOutputError = class extends AxlError {
139
67
  toolName;
140
68
  cause;
@@ -240,19 +168,209 @@ var ValidationError = class extends AxlError {
240
168
  this.retries = retries;
241
169
  }
242
170
  };
243
- var ToolDenied = class extends AxlError {
244
- toolName;
245
- agentName;
246
- constructor(toolName, agentName) {
171
+
172
+ // src/tool.ts
173
+ var DEFAULT_MAX_STRING_LENGTH = 1e4;
174
+ var ToolStringLengthError = class extends Error {
175
+ constructor(path, actual, maximum) {
176
+ const renderedPath = path.reduce(
177
+ (current, part) => typeof part === "number" ? `${current}[${part}]` : current ? `${current}.${part}` : part,
178
+ ""
179
+ );
247
180
  super(
248
- "TOOL_DENIED",
249
- `Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
181
+ `String argument${renderedPath ? ` at "${renderedPath}"` : ""} exceeds maximum length (${actual} > ${maximum})`
250
182
  );
251
- this.name = "ToolDenied";
252
- this.toolName = toolName;
253
- this.agentName = agentName;
183
+ this.path = path;
184
+ this.actual = actual;
185
+ this.maximum = maximum;
186
+ this.name = "ToolStringLengthError";
254
187
  }
255
188
  };
189
+ function validateStringLengths(value, maxLen, path = []) {
190
+ if (typeof value === "string") {
191
+ if (value.length > maxLen) {
192
+ throw new ToolStringLengthError(path, value.length, maxLen);
193
+ }
194
+ } else if (Array.isArray(value)) {
195
+ for (let i = 0; i < value.length; i++) {
196
+ validateStringLengths(value[i], maxLen, [...path, i]);
197
+ }
198
+ } else if (value !== null && typeof value === "object") {
199
+ for (const [key, val] of Object.entries(value)) {
200
+ validateStringLengths(val, maxLen, [...path, key]);
201
+ }
202
+ }
203
+ }
204
+ function sleep(ms, signal) {
205
+ if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
206
+ signal.throwIfAborted();
207
+ return new Promise((resolve2, reject) => {
208
+ const timeout = setTimeout(() => {
209
+ signal.removeEventListener("abort", onAbort);
210
+ resolve2();
211
+ }, ms);
212
+ const onAbort = () => {
213
+ clearTimeout(timeout);
214
+ reject(signal.reason);
215
+ };
216
+ signal.addEventListener("abort", onAbort, { once: true });
217
+ });
218
+ }
219
+ function isAbortError(error) {
220
+ try {
221
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+ function toolArgumentIssues(error) {
227
+ if (error instanceof ZodError) {
228
+ return error.issues.map((issue) => ({
229
+ path: issue.path.map((part) => typeof part === "symbol" ? String(part) : part),
230
+ code: issue.code,
231
+ message: issue.message
232
+ }));
233
+ }
234
+ if (error instanceof ToolStringLengthError) {
235
+ return [
236
+ {
237
+ path: error.path,
238
+ code: "too_big",
239
+ message: error.message
240
+ }
241
+ ];
242
+ }
243
+ return [{ path: [], code: "invalid_arguments", message: "Tool arguments are invalid." }];
244
+ }
245
+ var TOOL_INTERNALS = /* @__PURE__ */ new WeakMap();
246
+ function getToolInternals(tool2) {
247
+ const internals = TOOL_INTERNALS.get(tool2);
248
+ if (!internals) throw new Error(`Tool "${tool2.name}" was not created by tool()`);
249
+ return internals;
250
+ }
251
+ function prepareToolInput(tool2, input) {
252
+ return getToolInternals(tool2).prepareInput(input);
253
+ }
254
+ function executePreparedTool(tool2, input, ctx, options) {
255
+ return getToolInternals(tool2).executePrepared(input, ctx, options);
256
+ }
257
+ function getBackoffMs(attempt, strategy) {
258
+ switch (strategy) {
259
+ case "none":
260
+ return 0;
261
+ case "linear":
262
+ return attempt * 1e3;
263
+ case "exponential":
264
+ return Math.pow(2, attempt - 1) * 1e3;
265
+ }
266
+ }
267
+ function tool(config) {
268
+ const retryPolicy = {
269
+ attempts: config.retry?.attempts ?? 1,
270
+ backoff: config.retry?.backoff ?? "exponential",
271
+ on: config.retry?.on
272
+ };
273
+ const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
274
+ const prepareInput = (input) => {
275
+ const parsed = config.input.parse(input);
276
+ if (maxStringLen > 0) {
277
+ validateStringLengths(parsed, maxStringLen);
278
+ }
279
+ return parsed;
280
+ };
281
+ const executePrepared = async (parsed, ctx, options) => {
282
+ const maxAttempts = retryPolicy.attempts ?? 1;
283
+ let lastError;
284
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
285
+ options?.signal?.throwIfAborted();
286
+ options?.onAttempt?.(attempt);
287
+ try {
288
+ const result = await config.handler(parsed, ctx);
289
+ if (options?.checkAfterHandlerAbort !== false) options?.signal?.throwIfAborted();
290
+ return result;
291
+ } catch (err) {
292
+ if (options?.signal?.aborted) options.signal.throwIfAborted();
293
+ if (isAbortError(err)) throw err;
294
+ rethrowEventStreamOverflow(err);
295
+ lastError = err instanceof Error ? err : new Error(String(err));
296
+ if (attempt === maxAttempts) break;
297
+ if (retryPolicy.on && !retryPolicy.on(lastError)) {
298
+ break;
299
+ }
300
+ const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
301
+ if (backoffMs > 0) {
302
+ await sleep(backoffMs, options?.signal);
303
+ }
304
+ }
305
+ }
306
+ throw lastError;
307
+ };
308
+ const execute = async (input, ctx) => executePrepared(prepareInput(input), ctx);
309
+ const instance = {
310
+ name: config.name,
311
+ description: config.description,
312
+ inputSchema: config.input,
313
+ sensitive: config.sensitive ?? false,
314
+ retry: retryPolicy,
315
+ requireApproval: config.requireApproval ?? false,
316
+ hooks: config.hooks,
317
+ ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
318
+ async run(ctx, input) {
319
+ const startTime = Date.now();
320
+ try {
321
+ let processedInput = input;
322
+ if (config.hooks?.before) {
323
+ processedInput = await config.hooks.before(processedInput, ctx);
324
+ }
325
+ let result = await execute(processedInput, ctx);
326
+ if (config.hooks?.after) {
327
+ result = await config.hooks.after(result, ctx);
328
+ }
329
+ ctx.log("tool_call_complete", {
330
+ tool: config.name,
331
+ duration: Date.now() - startTime
332
+ });
333
+ return result;
334
+ } catch (err) {
335
+ ctx.log("tool_call_error", {
336
+ tool: config.name,
337
+ error: err instanceof Error ? err.message : String(err),
338
+ duration: Date.now() - startTime
339
+ });
340
+ throw err;
341
+ }
342
+ },
343
+ _execute: execute
344
+ };
345
+ TOOL_INTERNALS.set(instance, {
346
+ prepareInput,
347
+ executePrepared
348
+ });
349
+ return instance;
350
+ }
351
+
352
+ // src/providers/types.ts
353
+ function resolveThinkingOptions(options) {
354
+ if (options.thinkingBudget !== void 0 && options.thinkingBudget < 0) {
355
+ throw new Error(`thinkingBudget must be non-negative, got ${options.thinkingBudget}`);
356
+ }
357
+ const effort = options.effort;
358
+ const thinkingBudget = options.thinkingBudget;
359
+ const hasBudgetOverride = thinkingBudget !== void 0 && thinkingBudget > 0;
360
+ return {
361
+ effort,
362
+ thinkingBudget,
363
+ includeThoughts: options.includeThoughts ?? false,
364
+ // Budget override wins: effort: 'none' + thinkingBudget: 5000 → thinking enabled
365
+ thinkingDisabled: (effort === "none" || thinkingBudget === 0) && !hasBudgetOverride,
366
+ activeEffort: effort && effort !== "none" ? effort : void 0,
367
+ hasBudgetOverride
368
+ };
369
+ }
370
+ async function resolveApiKey(source) {
371
+ if (typeof source === "function") return await source() ?? "";
372
+ return source ?? "";
373
+ }
256
374
 
257
375
  // src/providers/errors.ts
258
376
  var ProviderError = class extends AxlError {
@@ -361,7 +479,7 @@ function sleep2(ms, signal) {
361
479
  );
362
480
  });
363
481
  }
364
- function isAbortError(err, signal) {
482
+ function isAbortError2(err, signal) {
365
483
  if (signal?.aborted) return true;
366
484
  return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
367
485
  }
@@ -380,7 +498,7 @@ async function fetchWithRetry(input, init, opts) {
380
498
  try {
381
499
  res = await fetch(input, init);
382
500
  } catch (err) {
383
- if (isAbortError(err, init?.signal ?? void 0)) throw err;
501
+ if (isAbortError2(err, init?.signal ?? void 0)) throw err;
384
502
  if (attempt >= maxRetries) {
385
503
  throw buildProviderError({
386
504
  provider,
@@ -3062,7 +3180,7 @@ var defaultRegistry = new ProviderRegistry();
3062
3180
  // src/context.ts
3063
3181
  import { AsyncLocalStorage } from "async_hooks";
3064
3182
  import { randomUUID } from "crypto";
3065
- import { z, ZodError } from "zod";
3183
+ import { z, ZodError as ZodError2 } from "zod";
3066
3184
 
3067
3185
  // src/partial-json.ts
3068
3186
  var MAX_DEPTH = 256;
@@ -3681,9 +3799,9 @@ var AXL_EVENT_TYPES = [
3681
3799
  // Tool invocation lifecycle
3682
3800
  "tool_call_start",
3683
3801
  "tool_call_end",
3802
+ "tool_call_rejected",
3684
3803
  // Single-point tool events
3685
3804
  "tool_approval",
3686
- "tool_denied",
3687
3805
  // Delegation
3688
3806
  "delegate",
3689
3807
  // Handoff (spans two asks; not AskScoped).
@@ -3714,7 +3832,7 @@ var AXL_EVENT_TYPES = [
3714
3832
  "memory_remember",
3715
3833
  "memory_recall",
3716
3834
  "memory_forget",
3717
- // Durable execution checkpoints (`ctx.checkpoint`)
3835
+ // Scoped execution checkpoints (`ctx.checkpoint`)
3718
3836
  "checkpoint_save",
3719
3837
  "checkpoint_replay",
3720
3838
  // Human-in-the-loop (`ctx.awaitHuman`)
@@ -3729,6 +3847,12 @@ var AXL_EVENT_TYPES = [
3729
3847
  "done",
3730
3848
  "error"
3731
3849
  ];
3850
+ var AXL_EVENT_TYPES_V2 = AXL_EVENT_TYPES;
3851
+ var AXL_TOOL_LIFECYCLE_TYPES_V2 = [
3852
+ "tool_call_start",
3853
+ "tool_call_end",
3854
+ "tool_call_rejected"
3855
+ ];
3732
3856
 
3733
3857
  // src/event-stream.ts
3734
3858
  var STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
@@ -3744,8 +3868,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3744
3868
  "agent_call_end",
3745
3869
  "tool_call_start",
3746
3870
  "tool_call_end",
3871
+ "tool_call_rejected",
3747
3872
  "tool_approval",
3748
- "tool_denied",
3749
3873
  "handoff_start",
3750
3874
  "handoff_return",
3751
3875
  "delegate",
@@ -3759,18 +3883,6 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3759
3883
  "await_human",
3760
3884
  "await_human_resolved"
3761
3885
  ]);
3762
- var EventStreamOverflowError = class extends Error {
3763
- maxQueued;
3764
- eventType;
3765
- constructor(maxQueued, eventType) {
3766
- super(
3767
- `AxlEventBus queue exceeded maxQueued=${maxQueued} (event type: ${eventType}). Consumer is too slow or the producer is unbounded. Configure \`maxQueued\`/\`onOverflow\` on the runtime, or set maxQueued: Infinity to disable.`
3768
- );
3769
- this.name = "EventStreamOverflowError";
3770
- this.maxQueued = maxQueued;
3771
- this.eventType = eventType;
3772
- }
3773
- };
3774
3886
  var DEFAULT_MAX_QUEUED = 1e4;
3775
3887
  var DEFAULT_OVERFLOW = "drop-oldest-non-terminal";
3776
3888
  var AxlEventBus = class {
@@ -3781,6 +3893,7 @@ var AxlEventBus = class {
3781
3893
  maxQueued;
3782
3894
  onOverflow;
3783
3895
  overflowWarned = false;
3896
+ droppedEvents = 0;
3784
3897
  /** Callbacks fired exactly once when `_finish()` is called. Used by
3785
3898
  * curated views (e.g., `partialObjects`) that are listener-based and
3786
3899
  * need a termination signal independent of any synthetic `done` event. */
@@ -3826,6 +3939,10 @@ var AxlEventBus = class {
3826
3939
  * can be 4 KB+ each, hence the more aggressive cleanup here).
3827
3940
  */
3828
3941
  stringStreamByAsk = /* @__PURE__ */ new Map();
3942
+ /** Internal fan-out for `stringStream()` subscribers. Kept separate from
3943
+ * the public EventEmitter so strict curated-view overflow is a runtime
3944
+ * signal, not mistaken for (and swallowed as) a buggy user listener. */
3945
+ stringStreamSubscribers = /* @__PURE__ */ new Set();
3829
3946
  /**
3830
3947
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
3831
3948
  * is `await using`-disposed. Set via the second constructor argument.
@@ -3859,6 +3976,12 @@ var AxlEventBus = class {
3859
3976
  this.bus.on("error", () => {
3860
3977
  });
3861
3978
  }
3979
+ /** Machine-readable completeness across this bus's bounded iterable views.
3980
+ * EventEmitter listener delivery is independent and is not described by
3981
+ * this status. */
3982
+ get observationStatus() {
3983
+ return this.droppedEvents === 0 ? { complete: true } : { complete: false, reason: "queue_overflow", droppedEvents: this.droppedEvents };
3984
+ }
3862
3985
  unknownEventWarned = false;
3863
3986
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3864
3987
  on(event, handler) {
@@ -4171,7 +4294,7 @@ var AxlEventBus = class {
4171
4294
  }
4172
4295
  return {
4173
4296
  [Symbol.asyncIterator]() {
4174
- const pending = [];
4297
+ const pending = /* @__PURE__ */ new Map();
4175
4298
  let resolveWaiter = null;
4176
4299
  let finished = self.finished;
4177
4300
  let unsubscribed = false;
@@ -4185,10 +4308,39 @@ var AxlEventBus = class {
4185
4308
  if (filterPath !== void 0 && path !== filterPath) return false;
4186
4309
  return true;
4187
4310
  };
4311
+ const pendingKey = (askId, path) => JSON.stringify([askId, path]);
4312
+ const enqueue = (event) => {
4313
+ const key = pendingKey(event.askId, event.path);
4314
+ const existing = pending.get(key);
4315
+ if (existing) {
4316
+ pending.set(key, {
4317
+ ...event,
4318
+ delta: existing.delta + event.delta
4319
+ });
4320
+ wakeWaiter();
4321
+ return;
4322
+ }
4323
+ if (pending.size >= self.maxQueued) {
4324
+ if (self.onOverflow === "throw") {
4325
+ self.droppedEvents++;
4326
+ throw new EventStreamOverflowError(self.maxQueued, "string_delta");
4327
+ }
4328
+ const oldest = pending.keys().next().value;
4329
+ if (oldest !== void 0) {
4330
+ pending.delete(oldest);
4331
+ self.droppedEvents++;
4332
+ }
4333
+ self.warnOverflow(
4334
+ "dropping oldest pending string field (stringStream subscriber is slower than producer)"
4335
+ );
4336
+ }
4337
+ pending.set(key, event);
4338
+ wakeWaiter();
4339
+ };
4188
4340
  for (const [askId, paths] of self.stringStreamByAsk) {
4189
4341
  for (const [path, acc] of paths) {
4190
4342
  if (!matchesFilter(askId, path)) continue;
4191
- pending.push({
4343
+ enqueue({
4192
4344
  askId,
4193
4345
  agent: acc.agent,
4194
4346
  path,
@@ -4198,51 +4350,37 @@ var AxlEventBus = class {
4198
4350
  });
4199
4351
  }
4200
4352
  }
4201
- const handler = (event) => {
4202
- if (event.type !== "string_delta") return;
4203
- const askId = event.askId ?? "";
4204
- if (!matchesFilter(askId, event.data.path)) return;
4205
- const paths = self.stringStreamByAsk.get(askId);
4206
- const acc = paths?.get(event.data.path);
4207
- const accumulated = acc?.text ?? event.data.delta;
4208
- pending.push({
4209
- askId,
4210
- agent: event.agent,
4211
- path: event.data.path,
4212
- delta: event.data.delta,
4213
- accumulated,
4214
- attempt: event.attempt
4215
- });
4216
- wakeWaiter();
4217
- };
4218
- self.bus.on("string_delta", handler);
4219
- const pipelineHandler = (event) => {
4220
- if (event.type !== "pipeline") return;
4221
- if (event.status !== "failed") return;
4222
- const askId = event.askId ?? "";
4223
- for (let i = pending.length - 1; i >= 0; i--) {
4224
- if (pending[i].askId === askId) pending.splice(i, 1);
4353
+ const subscriber = {
4354
+ push(event) {
4355
+ if (matchesFilter(event.askId, event.path)) enqueue(event);
4356
+ },
4357
+ reset(askId) {
4358
+ for (const [key, event] of pending) {
4359
+ if (event.askId === askId) pending.delete(key);
4360
+ }
4225
4361
  }
4226
4362
  };
4227
- self.bus.on("pipeline", pipelineHandler);
4363
+ self.stringStreamSubscribers.add(subscriber);
4228
4364
  const unsubscribe = () => {
4229
4365
  if (unsubscribed) return;
4230
4366
  unsubscribed = true;
4231
- self.bus.off("string_delta", handler);
4232
- self.bus.off("pipeline", pipelineHandler);
4367
+ self.stringStreamSubscribers.delete(subscriber);
4233
4368
  unsubscribeFinish();
4234
4369
  };
4235
4370
  const unsubscribeFinish = self._onFinish(() => {
4236
4371
  finished = true;
4237
- self.bus.off("string_delta", handler);
4238
- self.bus.off("pipeline", pipelineHandler);
4372
+ self.stringStreamSubscribers.delete(subscriber);
4239
4373
  wakeWaiter();
4240
4374
  });
4241
4375
  return {
4242
4376
  async next() {
4243
4377
  while (true) {
4244
- if (pending.length > 0) {
4245
- return { value: pending.shift(), done: false };
4378
+ if (pending.size > 0) {
4379
+ const first = pending.entries().next().value;
4380
+ if (first) {
4381
+ pending.delete(first[0]);
4382
+ return { value: first[1], done: false };
4383
+ }
4246
4384
  }
4247
4385
  if (finished) {
4248
4386
  unsubscribe();
@@ -4300,6 +4438,7 @@ var AxlEventBus = class {
4300
4438
  */
4301
4439
  _push(event) {
4302
4440
  if (this.finished) return;
4441
+ let overflowError;
4303
4442
  if (event.type === "partial_object" && event.data?.object !== void 0) {
4304
4443
  const askId = event.askId ?? "";
4305
4444
  this.latestPartialByAsk.set(askId, {
@@ -4321,10 +4460,30 @@ var AxlEventBus = class {
4321
4460
  text: (existing?.text ?? "") + event.data.delta,
4322
4461
  attempt: event.attempt
4323
4462
  });
4463
+ const accumulated = paths.get(event.data.path).text;
4464
+ for (const subscriber of this.stringStreamSubscribers) {
4465
+ try {
4466
+ subscriber.push({
4467
+ askId,
4468
+ agent: event.agent,
4469
+ path: event.data.path,
4470
+ delta: event.data.delta,
4471
+ accumulated,
4472
+ attempt: event.attempt
4473
+ });
4474
+ } catch (error) {
4475
+ if (isEventStreamOverflowError(error)) {
4476
+ overflowError ??= error;
4477
+ } else {
4478
+ throw error;
4479
+ }
4480
+ }
4481
+ }
4324
4482
  } else if (event.type === "pipeline" && event.status === "failed") {
4325
4483
  const askId = event.askId ?? "";
4326
4484
  this.latestPartialByAsk.delete(askId);
4327
4485
  this.stringStreamByAsk.delete(askId);
4486
+ for (const subscriber of this.stringStreamSubscribers) subscriber.reset(askId);
4328
4487
  } else if (event.type === "ask_end") {
4329
4488
  this.stringStreamByAsk.delete(event.askId ?? "");
4330
4489
  }
@@ -4339,39 +4498,57 @@ var AxlEventBus = class {
4339
4498
  const waiter = this.waiters.shift();
4340
4499
  if (waiter) {
4341
4500
  waiter({ value: event, done: false });
4501
+ if (overflowError) throw overflowError;
4342
4502
  return;
4343
4503
  }
4344
4504
  if (this.eventQueue.length >= this.maxQueued && !TERMINAL_TYPES.has(event.type)) {
4345
- this.handleOverflow(event);
4505
+ try {
4506
+ this.handleOverflow(event);
4507
+ } catch (error) {
4508
+ if (isEventStreamOverflowError(error)) {
4509
+ overflowError ??= error;
4510
+ } else {
4511
+ throw error;
4512
+ }
4513
+ }
4514
+ if (overflowError) throw overflowError;
4346
4515
  return;
4347
4516
  }
4348
4517
  this.eventQueue.push(event);
4518
+ if (overflowError) throw overflowError;
4349
4519
  }
4350
4520
  /** Apply the overflow policy when the queue is at `maxQueued` and a
4351
4521
  * non-terminal event arrives. Terminal events bypass this path entirely
4352
4522
  * (checked by the caller). */
4353
4523
  handleOverflow(event) {
4354
4524
  if (this.onOverflow === "throw") {
4525
+ this.droppedEvents++;
4355
4526
  throw new EventStreamOverflowError(this.maxQueued, event.type);
4356
4527
  }
4357
4528
  let droppedIdx = -1;
4358
4529
  for (let i = 0; i < this.eventQueue.length; i++) {
4359
- if (!TERMINAL_TYPES.has(this.eventQueue[i].type)) {
4530
+ const candidate = this.eventQueue[i];
4531
+ if (!TERMINAL_TYPES.has(candidate.type)) {
4360
4532
  droppedIdx = i;
4361
4533
  break;
4362
4534
  }
4363
4535
  }
4364
- if (!this.overflowWarned) {
4365
- this.overflowWarned = true;
4366
- console.warn(
4367
- `[axl] AxlEventBus queue exceeded maxQueued=${this.maxQueued}` + (droppedIdx >= 0 ? `; dropping oldest non-terminal events (consumer is slower than producer).` : `; queue is all terminals (preserved past cap so no terminal is lost).`) + ` This warning fires once per bus instance.`
4368
- );
4369
- }
4536
+ this.warnOverflow(
4537
+ droppedIdx >= 0 ? "dropping oldest non-terminal events (consumer is slower than producer)" : "queue contains only protected terminal events; preserving them past the cap"
4538
+ );
4370
4539
  if (droppedIdx >= 0) {
4371
4540
  this.eventQueue.splice(droppedIdx, 1);
4541
+ this.droppedEvents++;
4372
4542
  }
4373
4543
  this.eventQueue.push(event);
4374
4544
  }
4545
+ warnOverflow(detail) {
4546
+ if (this.overflowWarned) return;
4547
+ this.overflowWarned = true;
4548
+ console.warn(
4549
+ `[axl] AxlEventBus queue exceeded maxQueued=${this.maxQueued}; ${detail}. This warning fires once per bus instance.`
4550
+ );
4551
+ }
4375
4552
  /**
4376
4553
  * Mark the bus as finished. Wakes any pending iterators with `done: true`
4377
4554
  * and drops further `_push` calls. Idempotent.
@@ -4438,6 +4615,43 @@ var AxlEventBus = class {
4438
4615
  }
4439
4616
  };
4440
4617
 
4618
+ // src/event-schema.ts
4619
+ var UnsupportedEventSchemaVersionError = class extends Error {
4620
+ constructor(version) {
4621
+ super(`Unsupported Axl event schema version: ${String(version)}`);
4622
+ this.version = version;
4623
+ this.name = "UnsupportedEventSchemaVersionError";
4624
+ }
4625
+ };
4626
+ function getEventSchemaVersion(event) {
4627
+ const version = event.schemaVersion === void 0 ? 1 : event.schemaVersion;
4628
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4629
+ return version;
4630
+ }
4631
+ function getExecutionEventSchemaVersion(execution) {
4632
+ const version = execution.eventSchemaVersion === void 0 ? 1 : execution.eventSchemaVersion;
4633
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4634
+ return version;
4635
+ }
4636
+ function normalizeStoredExecution(execution) {
4637
+ const version = getExecutionEventSchemaVersion(execution);
4638
+ if (!Array.isArray(execution.events)) {
4639
+ throw new TypeError(`Execution ${execution.executionId} has a non-array events field`);
4640
+ }
4641
+ for (const event of execution.events) {
4642
+ const eventVersion = getEventSchemaVersion(event);
4643
+ if (eventVersion !== version) {
4644
+ throw new Error(
4645
+ `Execution ${execution.executionId} declares event schema v${version} but contains a v${eventVersion} event`
4646
+ );
4647
+ }
4648
+ }
4649
+ if (version === 2) {
4650
+ return { ...execution, eventSchemaVersion: 2 };
4651
+ }
4652
+ return { ...execution, eventSchemaVersion: 1 };
4653
+ }
4654
+
4441
4655
  // src/redaction.ts
4442
4656
  var REDACTED = "[redacted]";
4443
4657
  function walkObjectOneLevel(obj, preserveKeys) {
@@ -4488,7 +4702,7 @@ function redactMemory(event) {
4488
4702
  };
4489
4703
  }
4490
4704
  var passthrough = (e) => e;
4491
- var REDACTION_RULES = {
4705
+ var LEGACY_REDACTION_RULES = {
4492
4706
  workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
4493
4707
  workflow_end: (e) => {
4494
4708
  const d = e.data;
@@ -4588,10 +4802,125 @@ var REDACTION_RULES = {
4588
4802
  done: (e) => ({ ...e, data: { result: REDACTED } }),
4589
4803
  error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
4590
4804
  };
4805
+ function redactLegacyEvent(event) {
4806
+ const rule = LEGACY_REDACTION_RULES[event.type];
4807
+ return rule(event);
4808
+ }
4809
+ function redactV2Outcome(outcome) {
4810
+ switch (outcome.status) {
4811
+ case "succeeded":
4812
+ return { ...outcome, result: REDACTED };
4813
+ case "denied":
4814
+ return outcome.reason === void 0 ? outcome : { ...outcome, reason: REDACTED };
4815
+ case "cancelled":
4816
+ return {
4817
+ ...outcome,
4818
+ cancellation: {
4819
+ ...outcome.cancellation,
4820
+ ..."result" in outcome.cancellation ? { result: REDACTED } : {},
4821
+ ...outcome.cancellation.reason !== void 0 ? { reason: REDACTED } : {}
4822
+ }
4823
+ };
4824
+ case "failed": {
4825
+ const failure = outcome.failure;
4826
+ return {
4827
+ ...outcome,
4828
+ failure: {
4829
+ ...failure,
4830
+ ..."result" in failure ? { result: REDACTED } : {},
4831
+ error: {
4832
+ ...failure.error,
4833
+ message: REDACTED,
4834
+ ...failure.error.cause !== void 0 ? { cause: REDACTED } : {}
4835
+ }
4836
+ }
4837
+ };
4838
+ }
4839
+ }
4840
+ }
4841
+ function redactV2Rejection(data) {
4842
+ switch (data.reason) {
4843
+ case "unavailable":
4844
+ return data;
4845
+ case "invalid_json":
4846
+ return { ...data, message: REDACTED };
4847
+ case "invalid_arguments":
4848
+ return {
4849
+ ...data,
4850
+ args: REDACTED,
4851
+ issues: data.issues.map((issue) => ({
4852
+ ...issue,
4853
+ ...issue.message !== void 0 ? { message: REDACTED } : {}
4854
+ }))
4855
+ };
4856
+ }
4857
+ }
4858
+ var V2_TOOL_REDACTION_RULES = {
4859
+ tool_call_start: (event) => ({
4860
+ ...event,
4861
+ data: { ...event.data, args: REDACTED }
4862
+ }),
4863
+ tool_call_end: (event) => ({
4864
+ ...event,
4865
+ data: {
4866
+ ...event.data,
4867
+ args: REDACTED,
4868
+ outcome: redactV2Outcome(event.data.outcome)
4869
+ }
4870
+ }),
4871
+ tool_call_rejected: (event) => ({
4872
+ ...event,
4873
+ data: redactV2Rejection(event.data)
4874
+ })
4875
+ };
4876
+ function reuseLegacyRule(type) {
4877
+ return LEGACY_REDACTION_RULES[type];
4878
+ }
4879
+ var COMMON_V2_REDACTION_RULES = {
4880
+ workflow_start: reuseLegacyRule("workflow_start"),
4881
+ workflow_end: reuseLegacyRule("workflow_end"),
4882
+ ask_start: reuseLegacyRule("ask_start"),
4883
+ ask_end: reuseLegacyRule("ask_end"),
4884
+ agent_call_start: reuseLegacyRule("agent_call_start"),
4885
+ agent_call_end: reuseLegacyRule("agent_call_end"),
4886
+ token: reuseLegacyRule("token"),
4887
+ tool_approval: reuseLegacyRule("tool_approval"),
4888
+ delegate: reuseLegacyRule("delegate"),
4889
+ handoff_start: reuseLegacyRule("handoff_start"),
4890
+ handoff_return: reuseLegacyRule("handoff_return"),
4891
+ pipeline: reuseLegacyRule("pipeline"),
4892
+ partial_object: reuseLegacyRule("partial_object"),
4893
+ string_delta: reuseLegacyRule("string_delta"),
4894
+ verify: reuseLegacyRule("verify"),
4895
+ schema_diagnostic: reuseLegacyRule("schema_diagnostic"),
4896
+ log: reuseLegacyRule("log"),
4897
+ memory_remember: reuseLegacyRule("memory_remember"),
4898
+ memory_recall: reuseLegacyRule("memory_recall"),
4899
+ memory_forget: reuseLegacyRule("memory_forget"),
4900
+ checkpoint_save: reuseLegacyRule("checkpoint_save"),
4901
+ checkpoint_replay: reuseLegacyRule("checkpoint_replay"),
4902
+ await_human: reuseLegacyRule("await_human"),
4903
+ await_human_resolved: reuseLegacyRule("await_human_resolved"),
4904
+ guardrail: reuseLegacyRule("guardrail"),
4905
+ schema_check: reuseLegacyRule("schema_check"),
4906
+ validate: reuseLegacyRule("validate"),
4907
+ done: reuseLegacyRule("done"),
4908
+ error: reuseLegacyRule("error")
4909
+ };
4910
+ var REDACTION_RULES = {
4911
+ ...COMMON_V2_REDACTION_RULES,
4912
+ ...V2_TOOL_REDACTION_RULES
4913
+ };
4591
4914
  function redactEvent(event) {
4592
4915
  const rule = REDACTION_RULES[event.type];
4593
4916
  return rule(event);
4594
4917
  }
4918
+ function redactHistoricalEvent(event) {
4919
+ if (getEventSchemaVersion(event) === 1) {
4920
+ return redactLegacyEvent(event);
4921
+ }
4922
+ return redactEvent(event);
4923
+ }
4595
4924
 
4596
4925
  // src/schema-diagnostics.ts
4597
4926
  var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
@@ -4791,8 +5120,8 @@ function normalize(value, path, stack) {
4791
5120
  "accessor properties are not supported"
4792
5121
  );
4793
5122
  }
4794
- if (typeof key === "symbol" && descriptor?.enumerable) {
4795
- throw invalid(`${path}[symbol]`, "enumerable symbol keys are not supported");
5123
+ if (typeof key === "symbol") {
5124
+ throw invalid(`${path}[symbol]`, "symbol keys are not supported");
4796
5125
  }
4797
5126
  }
4798
5127
  if (typeof descriptors.toJSON?.value === "function") {
@@ -4869,63 +5198,541 @@ function serializeToolModelOutput(toolName, output) {
4869
5198
  }
4870
5199
  }
4871
5200
 
4872
- // src/context.ts
4873
- var signalStorage = new AsyncLocalStorage();
4874
- function isErrorShaped(value) {
4875
- return value !== null && typeof value === "object" && "error" in value;
5201
+ // src/tool-invocation.ts
5202
+ var INVALID_JSON_MODEL_MESSAGE = "Error: Invalid JSON in tool arguments. Please provide valid JSON.";
5203
+ var INVALID_ARGUMENTS_MODEL_MESSAGE = "Error: Tool arguments are invalid. Correct the arguments and try again.";
5204
+ var SENSITIVE_SUCCESS_MESSAGE = "[REDACTED - sensitive tool output]";
5205
+ var SENSITIVE_FAILURE_MESSAGE = "[REDACTED - sensitive tool failure]";
5206
+ var MAX_MCP_MODEL_ERROR_LENGTH = 2e3;
5207
+ function cloneToolArguments(value) {
5208
+ try {
5209
+ return structuredClone(value);
5210
+ } catch {
5211
+ throw new Error("Prepared tool arguments must be structured-cloneable");
5212
+ }
5213
+ }
5214
+ function parseToolInvocation(options) {
5215
+ const { toolCall, configuredTool, override, mcpCall, mcpTraceName, availableTools } = options;
5216
+ const requestedTool = toolCall.function.name;
5217
+ if (!override && !configuredTool && !mcpCall) {
5218
+ return {
5219
+ kind: "rejected",
5220
+ requestedTool,
5221
+ toolName: requestedTool,
5222
+ callId: toolCall.id,
5223
+ data: { reason: "unavailable", requestedTool, availableTools },
5224
+ modelMessage: `Tool "${requestedTool}" is not available. Available tools: ${availableTools.length > 0 ? availableTools.join(", ") : "none"}`
5225
+ };
5226
+ }
5227
+ let parsedArguments;
5228
+ try {
5229
+ parsedArguments = JSON.parse(toolCall.function.arguments);
5230
+ } catch {
5231
+ return {
5232
+ kind: "rejected",
5233
+ requestedTool,
5234
+ toolName: mcpTraceName ?? requestedTool,
5235
+ callId: toolCall.id,
5236
+ data: {
5237
+ reason: "invalid_json",
5238
+ requestedTool,
5239
+ message: "Tool arguments were not valid JSON."
5240
+ },
5241
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5242
+ };
5243
+ }
5244
+ if (typeof parsedArguments !== "object" || parsedArguments === null || Array.isArray(parsedArguments)) {
5245
+ return {
5246
+ kind: "rejected",
5247
+ requestedTool,
5248
+ toolName: mcpTraceName ?? requestedTool,
5249
+ callId: toolCall.id,
5250
+ data: {
5251
+ reason: "invalid_json",
5252
+ requestedTool,
5253
+ message: "Tool arguments must decode to a JSON object."
5254
+ },
5255
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5256
+ };
5257
+ }
5258
+ const source = override ? { kind: "override", execute: override } : configuredTool ? { kind: "local", tool: configuredTool } : { kind: "mcp", call: mcpCall };
5259
+ let preparedArguments = parsedArguments;
5260
+ if (source.kind === "local") {
5261
+ try {
5262
+ preparedArguments = prepareToolInput(source.tool, parsedArguments);
5263
+ } catch (error) {
5264
+ return {
5265
+ kind: "rejected",
5266
+ requestedTool,
5267
+ toolName: requestedTool,
5268
+ callId: toolCall.id,
5269
+ data: {
5270
+ reason: "invalid_arguments",
5271
+ requestedTool,
5272
+ args: parsedArguments,
5273
+ issues: toolArgumentIssues(error)
5274
+ },
5275
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5276
+ };
5277
+ }
5278
+ }
5279
+ try {
5280
+ preparedArguments = cloneToolArguments(preparedArguments);
5281
+ } catch (error) {
5282
+ return {
5283
+ kind: "rejected",
5284
+ requestedTool,
5285
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5286
+ callId: toolCall.id,
5287
+ data: {
5288
+ reason: "invalid_arguments",
5289
+ requestedTool,
5290
+ args: parsedArguments,
5291
+ issues: toolArgumentIssues(error)
5292
+ },
5293
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5294
+ };
5295
+ }
5296
+ return {
5297
+ requestedTool,
5298
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5299
+ callId: toolCall.id,
5300
+ args: preparedArguments,
5301
+ configuredTool,
5302
+ source
5303
+ };
4876
5304
  }
4877
- function isErrorShapedSafely(value) {
5305
+ function isAbortError3(error) {
4878
5306
  try {
4879
- return isErrorShaped(value);
5307
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
4880
5308
  } catch {
4881
5309
  return false;
4882
5310
  }
4883
5311
  }
4884
- function recordToolSpanResult(span, value, suppressInspectionErrors) {
5312
+ function normalizeAbortError(reason) {
5313
+ if (isAbortError3(reason)) return reason;
5314
+ const error = new Error("The operation was aborted");
5315
+ error.name = "AbortError";
5316
+ if (reason !== void 0) {
5317
+ Object.defineProperty(error, "cause", { value: reason, enumerable: false });
5318
+ }
5319
+ return error;
5320
+ }
5321
+ function cancellationError(signal, error) {
5322
+ if (signal?.aborted) return normalizeAbortError(signal.reason);
5323
+ return isAbortError3(error) ? normalizeAbortError(error) : void 0;
5324
+ }
5325
+ function eventError(error) {
4885
5326
  let isError = false;
4886
- let errorMessage;
4887
- const inspect = () => {
4888
- if (isErrorShaped(value)) {
4889
- isError = true;
4890
- errorMessage = value.error;
4891
- } else {
4892
- isError = false;
4893
- }
5327
+ try {
5328
+ isError = error instanceof Error;
5329
+ } catch {
5330
+ }
5331
+ if (isError) {
5332
+ const read = (key) => {
5333
+ try {
5334
+ return Reflect.get(error, key);
5335
+ } catch {
5336
+ return void 0;
5337
+ }
5338
+ };
5339
+ const name = read("name");
5340
+ const message = read("message");
5341
+ const code = read("code");
5342
+ const cause = read("cause");
5343
+ return {
5344
+ name: typeof name === "string" ? name : "Error",
5345
+ message: typeof message === "string" ? message : "Unknown error",
5346
+ ...typeof code === "string" ? { code } : {},
5347
+ ...cause !== void 0 ? { cause } : {}
5348
+ };
5349
+ }
5350
+ try {
5351
+ return { name: "Error", message: String(error) };
5352
+ } catch {
5353
+ return { name: "Error", message: "Unknown thrown value" };
5354
+ }
5355
+ }
5356
+ function isToolFailure(error) {
5357
+ try {
5358
+ return error instanceof ToolFailure;
5359
+ } catch {
5360
+ return false;
5361
+ }
5362
+ }
5363
+ function cancellation(phase, error, result) {
5364
+ let reason;
5365
+ try {
5366
+ reason = error instanceof Error && typeof error.message === "string" ? error.message : void 0;
5367
+ } catch {
5368
+ }
5369
+ const cancellation2 = phase === "after_handler" || phase === "after_hook" || phase === "projection" || phase === "serialization" ? { phase, result, ...reason ? { reason } : {} } : { phase, ...reason ? { reason } : {} };
5370
+ return {
5371
+ kind: "cancelled",
5372
+ cancellation: cancellation2,
5373
+ error: normalizeAbortError(error)
4894
5374
  };
4895
- if (suppressInspectionErrors) {
5375
+ }
5376
+ function failed(phase, error, options = {}) {
5377
+ let modelMessage;
5378
+ if (isToolFailure(error)) {
4896
5379
  try {
4897
- inspect();
5380
+ modelMessage = typeof error.modelMessage === "string" ? error.modelMessage : void 0;
4898
5381
  } catch {
4899
- isError = false;
4900
- errorMessage = void 0;
4901
5382
  }
4902
- } else {
4903
- inspect();
4904
5383
  }
4905
- span.setAttribute("axl.tool.success", !isError);
4906
- if (isError) span.setStatus("error", errorMessage);
5384
+ const toolFailure = modelMessage !== void 0;
5385
+ const common = {
5386
+ phase,
5387
+ kind: toolFailure ? "tool_failure" : "unexpected",
5388
+ disposition: toolFailure ? "continue" : "abort",
5389
+ error: eventError(error)
5390
+ };
5391
+ const failure = phase === "handler" ? { ...common, attempts: options.attempts ?? 1 } : phase === "after_hook" ? { ...common, result: options.result } : common;
5392
+ return {
5393
+ kind: "failed",
5394
+ failure,
5395
+ error,
5396
+ ...modelMessage !== void 0 ? { modelMessage } : {}
5397
+ };
5398
+ }
5399
+ function mcpContent(result) {
5400
+ return result.content.map((content) => content.type === "text" ? content.text : `[${content.type}]`).join("\n");
5401
+ }
5402
+ function safeMcpModelError(content) {
5403
+ let normalized = "";
5404
+ for (const character of content) {
5405
+ const code = character.charCodeAt(0);
5406
+ normalized += code <= 31 && code !== 9 && code !== 10 && code !== 13 || code === 127 ? " " : character;
5407
+ }
5408
+ return `MCP tool error: ${normalized.slice(0, MAX_MCP_MODEL_ERROR_LENGTH)}`;
4907
5409
  }
4908
- function prepareToolModelOutput(toolName, project) {
4909
- let output;
5410
+ async function executeAcceptedTool(options) {
5411
+ const { invocation, context, signal, requestApproval, createChildContext } = options;
5412
+ let effectiveArgs = cloneToolArguments(invocation.args);
5413
+ if (invocation.source.kind === "local" && invocation.source.tool.requireApproval) {
5414
+ try {
5415
+ signal?.throwIfAborted();
5416
+ const approval = await requestApproval();
5417
+ signal?.throwIfAborted();
5418
+ if (!approval.approved) return { kind: "denied", reason: approval.reason };
5419
+ } catch (error) {
5420
+ rethrowEventStreamOverflow(error);
5421
+ const abort = cancellationError(signal, error);
5422
+ if (abort !== void 0) return cancellation("approval", abort);
5423
+ return {
5424
+ kind: "failed",
5425
+ failure: {
5426
+ phase: "approval",
5427
+ kind: "infrastructure",
5428
+ disposition: "abort",
5429
+ error: eventError(error)
5430
+ },
5431
+ error
5432
+ };
5433
+ }
5434
+ }
5435
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.before) {
5436
+ try {
5437
+ signal?.throwIfAborted();
5438
+ effectiveArgs = await invocation.source.tool.hooks.before(effectiveArgs, context);
5439
+ signal?.throwIfAborted();
5440
+ } catch (error) {
5441
+ rethrowEventStreamOverflow(error);
5442
+ const abort = cancellationError(signal, error);
5443
+ if (abort !== void 0) return cancellation("before_hook", abort);
5444
+ return failed("before_hook", error);
5445
+ }
5446
+ }
5447
+ let result;
5448
+ let attempts = 1;
4910
5449
  try {
4911
- output = project();
4912
- } catch (cause) {
4913
- throw new ToolModelOutputError(toolName, cause);
5450
+ signal?.throwIfAborted();
5451
+ switch (invocation.source.kind) {
5452
+ case "override": {
5453
+ const executeOverride = invocation.source.execute;
5454
+ result = await executeOverride(effectiveArgs);
5455
+ break;
5456
+ }
5457
+ case "local":
5458
+ result = await executePreparedTool(
5459
+ invocation.source.tool,
5460
+ effectiveArgs,
5461
+ createChildContext(),
5462
+ {
5463
+ signal,
5464
+ onAttempt: (attempt) => attempts = attempt,
5465
+ checkAfterHandlerAbort: false
5466
+ }
5467
+ );
5468
+ break;
5469
+ case "mcp": {
5470
+ const mcpResult = await invocation.source.call(effectiveArgs);
5471
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, mcpResult);
5472
+ if (mcpResult.isError) {
5473
+ const content = mcpContent(mcpResult);
5474
+ const error = new Error(content || "MCP tool returned an error");
5475
+ return {
5476
+ kind: "failed",
5477
+ failure: {
5478
+ phase: "handler",
5479
+ kind: "mcp_error",
5480
+ disposition: "continue",
5481
+ error: { name: "McpToolError", message: error.message, code: "MCP_TOOL_ERROR" },
5482
+ attempts: 1
5483
+ },
5484
+ error,
5485
+ modelMessage: safeMcpModelError(content)
5486
+ };
5487
+ }
5488
+ result = mcpResult;
5489
+ break;
5490
+ }
5491
+ }
5492
+ } catch (error) {
5493
+ rethrowEventStreamOverflow(error);
5494
+ const abort = cancellationError(signal, error);
5495
+ if (abort !== void 0) return cancellation("handler", abort);
5496
+ return failed("handler", error, { attempts });
5497
+ }
5498
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, result);
5499
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.after) {
5500
+ const rawResult = result;
5501
+ try {
5502
+ result = await invocation.source.tool.hooks.after(result, context);
5503
+ signal?.throwIfAborted();
5504
+ } catch (error) {
5505
+ rethrowEventStreamOverflow(error);
5506
+ const abort = cancellationError(signal, error);
5507
+ if (abort !== void 0) return cancellation("after_hook", abort, rawResult);
5508
+ return failed("after_hook", error, { result: rawResult });
5509
+ }
5510
+ }
5511
+ return { kind: "succeeded", result };
5512
+ }
5513
+ function prepareToolMessage(invocation, outcome) {
5514
+ const sensitive = invocation.configuredTool?.sensitive === true;
5515
+ if (outcome.kind === "denied") {
5516
+ return {
5517
+ ok: true,
5518
+ content: JSON.stringify({ error: "Tool request was denied by human approval." })
5519
+ };
5520
+ }
5521
+ if (outcome.kind === "failed") {
5522
+ if (outcome.failure.disposition !== "continue" || outcome.modelMessage === void 0) {
5523
+ throw new Error("Aborting tool failures cannot produce provider messages");
5524
+ }
5525
+ return { ok: true, content: sensitive ? SENSITIVE_FAILURE_MESSAGE : outcome.modelMessage };
5526
+ }
5527
+ if (outcome.kind !== "succeeded") {
5528
+ throw new Error("Cancelled tool calls cannot produce provider messages");
5529
+ }
5530
+ if (sensitive) return { ok: true, content: SENSITIVE_SUCCESS_MESSAGE };
5531
+ if (invocation.source.kind === "mcp") {
5532
+ try {
5533
+ return { ok: true, content: mcpContent(outcome.result) };
5534
+ } catch (error) {
5535
+ rethrowEventStreamOverflow(error);
5536
+ return { ok: false, phase: "serialization", error };
5537
+ }
5538
+ }
5539
+ if (invocation.configuredTool?.toModelOutput) {
5540
+ try {
5541
+ const project = invocation.configuredTool.toModelOutput;
5542
+ return {
5543
+ ok: true,
5544
+ content: serializeToolModelOutput(
5545
+ invocation.requestedTool,
5546
+ project(outcome.result)
5547
+ )
5548
+ };
5549
+ } catch (error) {
5550
+ rethrowEventStreamOverflow(error);
5551
+ if (error instanceof ToolModelOutputError && isEventStreamOverflowError(error.cause)) {
5552
+ throw error.cause;
5553
+ }
5554
+ return {
5555
+ ok: false,
5556
+ phase: "projection",
5557
+ error: error instanceof ToolModelOutputError && error.toolName === invocation.requestedTool ? error : new ToolModelOutputError(invocation.requestedTool, error)
5558
+ };
5559
+ }
5560
+ }
5561
+ try {
5562
+ const content = JSON.stringify(outcome.result);
5563
+ if (content === void 0) throw new Error("Tool result is not JSON serializable");
5564
+ return { ok: true, content };
5565
+ } catch (error) {
5566
+ rethrowEventStreamOverflow(error);
5567
+ return { ok: false, phase: "serialization", error };
5568
+ }
5569
+ }
5570
+ async function settleAcceptedTool(options) {
5571
+ const internal = await executeAcceptedTool(options);
5572
+ if (internal.kind === "cancelled") {
5573
+ return {
5574
+ outcome: { status: "cancelled", cancellation: internal.cancellation },
5575
+ abortError: internal.error
5576
+ };
5577
+ }
5578
+ if (internal.kind === "failed" && internal.failure.disposition === "abort") {
5579
+ return {
5580
+ outcome: { status: "failed", failure: internal.failure },
5581
+ abortError: internal.error
5582
+ };
5583
+ }
5584
+ const result = internal.kind === "succeeded" ? internal.result : void 0;
5585
+ const outputPhase = internal.kind === "succeeded" && !options.invocation.configuredTool?.sensitive && options.invocation.source.kind !== "mcp" && options.invocation.configuredTool?.toModelOutput ? "projection" : "serialization";
5586
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5587
+ return {
5588
+ outcome: {
5589
+ status: "cancelled",
5590
+ cancellation: { phase: outputPhase, result }
5591
+ },
5592
+ abortError: normalizeAbortError(options.signal.reason)
5593
+ };
5594
+ }
5595
+ const prepared = prepareToolMessage(options.invocation, internal);
5596
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5597
+ return {
5598
+ outcome: {
5599
+ status: "cancelled",
5600
+ cancellation: { phase: outputPhase, result }
5601
+ },
5602
+ abortError: normalizeAbortError(options.signal.reason)
5603
+ };
5604
+ }
5605
+ if (!prepared.ok) {
5606
+ const outputAbort = cancellationError(options.signal, prepared.error) ?? (prepared.error instanceof ToolModelOutputError ? cancellationError(options.signal, prepared.error.cause) : void 0);
5607
+ if (outputAbort !== void 0) {
5608
+ return {
5609
+ outcome: {
5610
+ status: "cancelled",
5611
+ cancellation: { phase: prepared.phase, result }
5612
+ },
5613
+ abortError: outputAbort
5614
+ };
5615
+ }
5616
+ const failure = {
5617
+ phase: prepared.phase,
5618
+ kind: "output",
5619
+ disposition: "abort",
5620
+ error: eventError(prepared.error),
5621
+ result
5622
+ };
5623
+ return {
5624
+ outcome: { status: "failed", failure },
5625
+ abortError: prepared.error
5626
+ };
5627
+ }
5628
+ if (internal.kind === "succeeded") {
5629
+ return {
5630
+ outcome: { status: "succeeded", result: internal.result },
5631
+ providerContent: prepared.content
5632
+ };
5633
+ }
5634
+ if (internal.kind === "denied") {
5635
+ return {
5636
+ outcome: { status: "denied", ...internal.reason ? { reason: internal.reason } : {} },
5637
+ providerContent: prepared.content
5638
+ };
5639
+ }
5640
+ return {
5641
+ outcome: { status: "failed", failure: internal.failure },
5642
+ providerContent: prepared.content
5643
+ };
5644
+ }
5645
+ function recordToolSpanOutcome(span, outcome) {
5646
+ span.setAttribute("axl.tool.outcome", outcome.status);
5647
+ span.setAttribute("axl.tool.success", outcome.status === "succeeded");
5648
+ switch (outcome.status) {
5649
+ case "succeeded":
5650
+ span.setStatus("ok");
5651
+ return;
5652
+ case "denied":
5653
+ span.setStatus("unset");
5654
+ return;
5655
+ case "failed":
5656
+ span.setAttribute("axl.tool.phase", outcome.failure.phase);
5657
+ span.setStatus("error");
5658
+ return;
5659
+ case "cancelled":
5660
+ span.setAttribute("axl.tool.phase", outcome.cancellation.phase);
5661
+ span.setStatus("error");
5662
+ }
5663
+ }
5664
+
5665
+ // src/context.ts
5666
+ var signalStorage = new AsyncLocalStorage();
5667
+ var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
5668
+ function resolveBranchDrainTimeoutMs(value) {
5669
+ const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
5670
+ if (typeof resolved !== "number" || Number.isNaN(resolved) || !Number.isFinite(resolved) || resolved < 0) {
5671
+ throw new AxlError(
5672
+ "INVALID_CONFIG",
5673
+ `branchDrainTimeoutMs must be a non-negative finite number (got ${String(resolved)})`
5674
+ );
4914
5675
  }
4915
- return serializeToolModelOutput(toolName, output);
5676
+ return resolved;
4916
5677
  }
4917
- function finalizeToolResult(options) {
4918
- const { toolName, configuredTool, outcome, legacyContent, emitEnd, beforeProjection } = options;
4919
- const projectionEligible = configuredTool !== void 0 && !configuredTool.sensitive && outcome.kind === "returned" && configuredTool.toModelOutput !== void 0;
4920
- if (projectionEligible) {
4921
- emitEnd();
4922
- beforeProjection();
4923
- const project = configuredTool.toModelOutput;
4924
- return prepareToolModelOutput(toolName, () => project(outcome.value));
5678
+ function parseHumanDecision(value) {
5679
+ const invalid2 = () => {
5680
+ throw new AxlError(
5681
+ "INVALID_HUMAN_DECISION",
5682
+ "Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
5683
+ );
5684
+ };
5685
+ if (typeof value !== "object" || value === null || Array.isArray(value)) invalid2();
5686
+ let prototype;
5687
+ try {
5688
+ prototype = Object.getPrototypeOf(value);
5689
+ } catch {
5690
+ return invalid2();
5691
+ }
5692
+ if (prototype !== Object.prototype && prototype !== null) invalid2();
5693
+ let ownKeys;
5694
+ try {
5695
+ ownKeys = Reflect.ownKeys(value);
5696
+ } catch {
5697
+ return invalid2();
5698
+ }
5699
+ if (ownKeys.some(
5700
+ (key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
5701
+ )) {
5702
+ invalid2();
5703
+ }
5704
+ const readDataProperty = (key) => {
5705
+ let descriptor;
5706
+ try {
5707
+ descriptor = Object.getOwnPropertyDescriptor(value, key);
5708
+ } catch {
5709
+ return invalid2();
5710
+ }
5711
+ if (!descriptor) {
5712
+ if (ownKeys.includes(key)) invalid2();
5713
+ return { present: false, value: void 0 };
5714
+ }
5715
+ if (!ownKeys.includes(key)) return invalid2();
5716
+ if (!("value" in descriptor)) return invalid2();
5717
+ return { present: true, value: descriptor.value };
5718
+ };
5719
+ const approved = readDataProperty("approved");
5720
+ const data = readDataProperty("data");
5721
+ const reason = readDataProperty("reason");
5722
+ if (approved.present && approved.value === true && !reason.present) {
5723
+ if (data.value === void 0 || typeof data.value === "string") {
5724
+ return { approved: true, ...data.value === void 0 ? {} : { data: data.value } };
5725
+ }
5726
+ }
5727
+ if (approved.present && approved.value === false && !data.present) {
5728
+ if (reason.value === void 0 || typeof reason.value === "string") {
5729
+ return {
5730
+ approved: false,
5731
+ ...reason.value === void 0 ? {} : { reason: reason.value }
5732
+ };
5733
+ }
4925
5734
  }
4926
- const content = legacyContent();
4927
- emitEnd();
4928
- return content;
5735
+ return invalid2();
4929
5736
  }
4930
5737
  var askStorage = new AsyncLocalStorage();
4931
5738
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
@@ -5060,9 +5867,8 @@ var WorkflowContext = class _WorkflowContext {
5060
5867
  providerRegistry;
5061
5868
  sessionHistory;
5062
5869
  onTrace;
5063
- onToken;
5064
- onToolCall;
5065
5870
  pendingDecisions;
5871
+ awaitHumanClaims;
5066
5872
  budgetContext;
5067
5873
  stateStore;
5068
5874
  /** Root step counter for this execution. Inherited by every ctx.ask()
@@ -5083,27 +5889,27 @@ var WorkflowContext = class _WorkflowContext {
5083
5889
  * the caller-supplied key.
5084
5890
  */
5085
5891
  autoCheckpointCounters;
5086
- /** Idempotency guards for `workflow_start` / `workflow_end` emission.
5892
+ branchDrainState;
5893
+ /** Root-shared idempotency guards for workflow lifecycle emission.
5087
5894
  * Both `runtime.execute()` and `runtime.stream()` have paths where a
5088
5895
  * post-emit side-effect (checkpoint deletion, state-store persistence)
5089
5896
  * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
5090
5897
  * fired — the outer catch would then fire `_emitWorkflowEnd({status:
5091
5898
  * 'failed'})` for a second time. These flags make both emitters
5092
5899
  * single-fire so consumers never see paired-then-conflicting terminal
5093
- * events for one execution. */
5094
- _workflowStartEmitted = false;
5095
- _workflowEndEmitted = false;
5900
+ * events for one execution. Sharing the state with child contexts also
5901
+ * suppresses late branch events after bounded finalization. */
5902
+ workflowLifecycleState;
5096
5903
  signal;
5097
5904
  summaryCache;
5098
5905
  workflowName;
5099
5906
  mcpManager;
5100
5907
  spanManager;
5101
5908
  memoryManager;
5102
- resumeMode;
5103
5909
  toolOverrides;
5104
5910
  awaitHumanHandler;
5105
- onAgentStart;
5106
5911
  onAgentCallComplete;
5912
+ _onDecisionCleanupFailed;
5107
5913
  /** Shared mutable slot for the lazy `AxlEventBus`. The slot is shared
5108
5914
  * by reference across parent/child contexts so a late allocation
5109
5915
  * (consumer subscribes after a child context exists) is visible
@@ -5112,6 +5918,7 @@ var WorkflowContext = class _WorkflowContext {
5112
5918
  * contexts that never observe). */
5113
5919
  _busRef;
5114
5920
  _eventStreamOptions;
5921
+ _forceStreaming;
5115
5922
  /** Removes the constructor-registered `'abort'` listener from
5116
5923
  * `this.signal` so a long-lived signal (e.g., a request-scoped or
5117
5924
  * process-wide signal reused across many `runtime.createContext()` /
@@ -5131,8 +5938,7 @@ var WorkflowContext = class _WorkflowContext {
5131
5938
  * *between* asks. `ctx.events.partialObjects` is the coalescing view
5132
5939
  * designed for streaming-structured-output UIs.
5133
5940
  * - **Ad-hoc contexts** from `runtime.createContext()`: the same
5134
- * iterable replaces the legacy `onToken` / `onToolCall` /
5135
- * `onAgentStart` callbacks.
5941
+ * iterable provides typed per-context observation.
5136
5942
  * - **Cross-execution**: prefer `runtime.on('trace', …)` instead — that
5137
5943
  * fans out across all executions, while `ctx.events` is per-context.
5138
5944
  *
@@ -5147,9 +5953,9 @@ var WorkflowContext = class _WorkflowContext {
5147
5953
  *
5148
5954
  * **Subscribe before the first `ctx.ask()`.** The streaming code
5149
5955
  * path inside `ctx.ask()` activates only when an observer is
5150
- * present at the time the ask starts (`_streamingEnabled` — either
5151
- * the legacy `onToken` callback is set or `ctx.events` has been
5152
- * allocated). If you allocate `ctx.events` AFTER a `ctx.ask()` has
5956
+ * present at the time the ask starts (`_streamingEnabled` — an explicit
5957
+ * runtime streaming mode or allocated `ctx.events`). If you allocate
5958
+ * `ctx.events` AFTER a `ctx.ask()` has
5153
5959
  * begun, that in-flight ask will not stream `token` /
5154
5960
  * `partial_object` events at all (the agent loop went through
5155
5961
  * `provider.chat` instead of `provider.stream`). Subsequent asks
@@ -5188,18 +5994,16 @@ var WorkflowContext = class _WorkflowContext {
5188
5994
  this._busRef.current?._finish();
5189
5995
  this.abortListenerCleanup?.();
5190
5996
  }
5191
- /** True if any observer wants per-token streaming for asks started
5192
- * from this context: either the legacy `onToken` callback is set
5193
- * (e.g., `runtime.stream()` plumbs a sentinel) OR `ctx.events` has
5194
- * been allocated (a workflow-handler consumer subscribed before
5195
- * the ask). `ctx.ask` reads this to decide whether to enter the
5997
+ /** True when asks should use provider streaming: `runtime.stream()` set an
5998
+ * explicit internal mode or `ctx.events` has been allocated. `ctx.ask`
5999
+ * reads this to decide whether to enter the
5196
6000
  * streaming code path; the gate is re-checked per ask, so a
5197
6001
  * consumer subscribing AFTER the first ask started still gets
5198
6002
  * streaming on the next one. The check `_busRef.current !== undefined`
5199
6003
  * is a one-way flag — `_finish` does not unset the reference, so
5200
6004
  * once an observer was present, every subsequent ask streams. */
5201
6005
  get _streamingEnabled() {
5202
- return this.onToken !== void 0 || this._busRef.current !== void 0;
6006
+ return this._forceStreaming || this._busRef.current !== void 0;
5203
6007
  }
5204
6008
  constructor(init) {
5205
6009
  this.input = init.input;
@@ -5209,9 +6013,8 @@ var WorkflowContext = class _WorkflowContext {
5209
6013
  this.providerRegistry = init.providerRegistry;
5210
6014
  this.sessionHistory = init.sessionHistory ?? [];
5211
6015
  this.onTrace = init.onTrace;
5212
- this.onToken = init.onToken;
5213
- this.onToolCall = init.onToolCall;
5214
6016
  this.pendingDecisions = init.pendingDecisions;
6017
+ this.awaitHumanClaims = init.awaitHumanClaims ?? /* @__PURE__ */ new Map();
5215
6018
  this.budgetContext = init.budgetContext;
5216
6019
  this.stateStore = init.stateStore;
5217
6020
  this.signal = init.signal;
@@ -5219,13 +6022,13 @@ var WorkflowContext = class _WorkflowContext {
5219
6022
  this.mcpManager = init.mcpManager;
5220
6023
  this.spanManager = init.spanManager;
5221
6024
  this.memoryManager = init.memoryManager;
5222
- this.resumeMode = init.resumeMode ?? false;
5223
6025
  this.toolOverrides = init.toolOverrides;
5224
6026
  this.awaitHumanHandler = init.awaitHumanHandler;
5225
- this.onAgentStart = init.onAgentStart;
5226
6027
  this.onAgentCallComplete = init.onAgentCallComplete;
6028
+ this._onDecisionCleanupFailed = init._onDecisionCleanupFailed;
5227
6029
  this._busRef = init._busRef ?? { current: void 0 };
5228
6030
  this._eventStreamOptions = init.eventStreamOptions;
6031
+ this._forceStreaming = init._forceStreaming ?? false;
5229
6032
  if (init._busRef === void 0 && this.signal && !this.signal.aborted) {
5230
6033
  const signal = this.signal;
5231
6034
  const onAbort = () => {
@@ -5241,6 +6044,15 @@ var WorkflowContext = class _WorkflowContext {
5241
6044
  byAgent: /* @__PURE__ */ new Map(),
5242
6045
  root: 0
5243
6046
  };
6047
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
6048
+ this.branchDrainState = init.branchDrainState ?? {
6049
+ pending: /* @__PURE__ */ new Set(),
6050
+ timeoutMs: branchDrainTimeoutMs
6051
+ };
6052
+ this.workflowLifecycleState = init.workflowLifecycleState ?? {
6053
+ startEmitted: false,
6054
+ endEmitted: false
6055
+ };
5244
6056
  if (init.metadata?.summaryCache) {
5245
6057
  this.summaryCache = init.metadata.summaryCache;
5246
6058
  }
@@ -5249,16 +6061,9 @@ var WorkflowContext = class _WorkflowContext {
5249
6061
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
5250
6062
  * Shares: budget tracking, abort signals, trace emission, provider registry,
5251
6063
  * state store, span manager, memory manager, MCP manager, config,
5252
- * awaitHuman handler, pending decisions, tool overrides, AND the
5253
- * streaming callbacks (onToken / onAgentStart / onToolCall).
6064
+ * awaitHuman handler, pending decisions, tool overrides, and event bus.
5254
6065
  * Isolates: session history.
5255
6066
  *
5256
- * Streaming callbacks now propagate into nested asks because every callback
5257
- * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
5258
- * consumers that want root-only behavior can filter on `meta.depth === 0`
5259
- * instead of relying on the runtime to drop nested events. This is the
5260
- * "nested ask visibility" fix from spec §3.2.
5261
- *
5262
6067
  * The shared step counter lives in `askStorage` (ALS), so there is no
5263
6068
  * per-context counter to pass through anymore. Nested events are
5264
6069
  * correlated to their parent via `parentAskId` (set on every nested ask
@@ -5279,12 +6084,11 @@ var WorkflowContext = class _WorkflowContext {
5279
6084
  spanManager: this.spanManager,
5280
6085
  memoryManager: this.memoryManager,
5281
6086
  onTrace: this.onTrace,
5282
- onToken: this.onToken,
5283
- onToolCall: this.onToolCall,
5284
- onAgentStart: this.onAgentStart,
5285
6087
  onAgentCallComplete: this.onAgentCallComplete,
6088
+ _onDecisionCleanupFailed: this._onDecisionCleanupFailed,
5286
6089
  awaitHumanHandler: this.awaitHumanHandler,
5287
6090
  pendingDecisions: this.pendingDecisions,
6091
+ awaitHumanClaims: this.awaitHumanClaims,
5288
6092
  toolOverrides: this.toolOverrides,
5289
6093
  signal: this.signal,
5290
6094
  workflowName: this.workflowName,
@@ -5292,12 +6096,15 @@ var WorkflowContext = class _WorkflowContext {
5292
6096
  // emitted from `ctx.ask` / spawn / race / parallel / map in this
5293
6097
  // child context don't collide with the parent's in the state store.
5294
6098
  autoCheckpointCounters: this.autoCheckpointCounters,
6099
+ branchDrainState: this.branchDrainState,
6100
+ workflowLifecycleState: this.workflowLifecycleState,
5295
6101
  // Share the parent's bus slot (mutable ref). Late allocation in
5296
6102
  // either parent or child propagates because both read the same
5297
6103
  // slot. Nested-ask events (agent-as-tool pattern) bubble up to
5298
6104
  // the parent's iterator with `parentAskId`/`depth` correlation
5299
6105
  // intact.
5300
6106
  _busRef: this._busRef,
6107
+ _forceStreaming: this._forceStreaming,
5301
6108
  eventStreamOptions: this._eventStreamOptions
5302
6109
  // Isolated: sessionHistory (empty)
5303
6110
  });
@@ -5310,28 +6117,194 @@ var WorkflowContext = class _WorkflowContext {
5310
6117
  get currentSignal() {
5311
6118
  return signalStorage.getStore() ?? this.signal;
5312
6119
  }
5313
- /**
5314
- * Build a `CallbackMeta` for the current ask frame. Used at every
5315
- * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
5316
- * route or filter by ask correlation (`meta.depth === 0` for root
5317
- * chat UIs, etc.).
5318
- *
5319
- * If the call is somehow outside an ask frame (e.g., a programmatic
5320
- * `tool.run()` test harness), falls back to a synthetic meta keyed off
5321
- * `executionId` so the type contract holds — consumers see `depth: 0`
5322
- * and can ignore those events.
5323
- */
5324
- currentCallbackMeta(agentName) {
5325
- const frame = askStorage.getStore();
5326
- if (frame) {
5327
- return {
5328
- askId: frame.askId,
5329
- ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
5330
- depth: frame.depth,
5331
- agent: agentName
6120
+ _registerBranchDrain(promise) {
6121
+ const state = this.branchDrainState;
6122
+ state.pending.add(promise);
6123
+ void promise.then(
6124
+ () => state.pending.delete(promise),
6125
+ (error) => {
6126
+ state.pending.delete(promise);
6127
+ if (!state.overflow && isEventStreamOverflowError(error)) {
6128
+ state.overflow = error;
6129
+ }
6130
+ }
6131
+ );
6132
+ }
6133
+ /** Wait for race/quorum losers (including nested branch work) to settle.
6134
+ * Primitive calls still resolve as soon as their winner/quorum is known;
6135
+ * this is only the runtime's terminal/persistence barrier. */
6136
+ async _drainBranchWork() {
6137
+ const state = this.branchDrainState;
6138
+ state.deadline ??= Date.now() + state.timeoutMs;
6139
+ while (state.pending.size > 0) {
6140
+ const overflow2 = state.overflow;
6141
+ state.overflow = void 0;
6142
+ if (overflow2) throw overflow2;
6143
+ const remaining = state.deadline - Date.now();
6144
+ if (remaining <= 0) {
6145
+ return {
6146
+ complete: false,
6147
+ reason: "branch_drain_timeout",
6148
+ pendingContinuations: state.pending.size,
6149
+ timeoutMs: state.timeoutMs
6150
+ };
6151
+ }
6152
+ let timer;
6153
+ const settled = await Promise.race([
6154
+ Promise.allSettled([...state.pending]).then(() => true),
6155
+ new Promise((resolve2) => {
6156
+ timer = setTimeout(() => resolve2(false), remaining);
6157
+ })
6158
+ ]);
6159
+ if (timer) clearTimeout(timer);
6160
+ if (!settled) {
6161
+ return {
6162
+ complete: false,
6163
+ reason: "branch_drain_timeout",
6164
+ pendingContinuations: state.pending.size,
6165
+ timeoutMs: state.timeoutMs
6166
+ };
6167
+ }
6168
+ }
6169
+ const overflow = state.overflow;
6170
+ state.overflow = void 0;
6171
+ if (overflow) throw overflow;
6172
+ state.deadline = void 0;
6173
+ return { complete: true };
6174
+ }
6175
+ /** Resolve, execute, and observe one non-handoff tool request. Rejections
6176
+ * never start. Every accepted request emits one terminal event before a
6177
+ * continuation message or abort escapes this coordinator. */
6178
+ async observeToolInvocation(agent2, toolCall, currentMessages, availableTools) {
6179
+ const signal = this.currentSignal;
6180
+ signal?.throwIfAborted();
6181
+ const requestedTool = toolCall.function.name;
6182
+ const configuredTool = agent2._config.tools?.find(
6183
+ (candidate) => candidate.name === requestedTool
6184
+ );
6185
+ const override = this.toolOverrides?.get(requestedTool);
6186
+ const mcpManager = this.mcpManager;
6187
+ const mcpMatch = !configuredTool ? mcpManager?.getToolsForAgent(agent2._config.mcp, agent2._config.mcpTools).find(({ tool: tool2 }) => tool2.name === requestedTool) : void 0;
6188
+ const parsed = parseToolInvocation({
6189
+ toolCall,
6190
+ configuredTool,
6191
+ override,
6192
+ ...mcpMatch && mcpManager ? {
6193
+ mcpCall: (args) => mcpManager.callTool(requestedTool, args, mcpMatch.server),
6194
+ mcpTraceName: `${mcpMatch.server}:${requestedTool}`
6195
+ } : {},
6196
+ availableTools
6197
+ });
6198
+ signal?.throwIfAborted();
6199
+ if ("kind" in parsed) {
6200
+ this.emitEvent({
6201
+ type: "tool_call_rejected",
6202
+ agent: agent2._name,
6203
+ tool: parsed.toolName,
6204
+ callId: parsed.callId,
6205
+ data: parsed.data
6206
+ });
6207
+ currentMessages.push({
6208
+ role: "tool",
6209
+ content: parsed.modelMessage,
6210
+ tool_call_id: parsed.callId
6211
+ });
6212
+ return;
6213
+ }
6214
+ const startArgs = cloneToolArguments(parsed.args);
6215
+ this.emitEvent({
6216
+ type: "tool_call_start",
6217
+ agent: agent2._name,
6218
+ tool: parsed.toolName,
6219
+ callId: parsed.callId,
6220
+ data: {
6221
+ args: startArgs,
6222
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {}
6223
+ }
6224
+ });
6225
+ const startedAt = Date.now();
6226
+ const requestApproval = async () => {
6227
+ const approve = async () => {
6228
+ const approvalArgs = cloneToolArguments(parsed.args);
6229
+ const decision = await this.awaitHuman({
6230
+ channel: "tool_approval",
6231
+ prompt: `Tool "${parsed.toolName}" wants to execute with args: ${JSON.stringify(parsed.args)}`,
6232
+ metadata: { toolName: parsed.toolName, args: approvalArgs, agent: agent2._name }
6233
+ });
6234
+ return decision.approved ? { approved: true } : { approved: false, reason: decision.reason ?? "Denied by human" };
5332
6235
  };
6236
+ const approval = this.spanManager ? await this.spanManager.withSpanAsync(
6237
+ "axl.tool.approval",
6238
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6239
+ async (span) => {
6240
+ const value = await approve();
6241
+ span.setAttribute("axl.tool.approval.approved", value.approved);
6242
+ return value;
6243
+ }
6244
+ ) : await approve();
6245
+ this.emitEvent({
6246
+ type: "tool_approval",
6247
+ agent: agent2._name,
6248
+ tool: parsed.toolName,
6249
+ callId: parsed.callId,
6250
+ data: {
6251
+ approved: approval.approved,
6252
+ args: cloneToolArguments(parsed.args),
6253
+ ...approval.reason ? { reason: approval.reason } : {}
6254
+ }
6255
+ });
6256
+ return approval;
6257
+ };
6258
+ let terminalEmitted = false;
6259
+ const executeAndObserve = async (span) => {
6260
+ const settlement2 = await settleAcceptedTool({
6261
+ invocation: parsed,
6262
+ context: this,
6263
+ signal,
6264
+ requestApproval,
6265
+ createChildContext: () => this.createChildContext()
6266
+ });
6267
+ const duration = Date.now() - startedAt;
6268
+ span?.setAttribute("axl.tool.duration", duration);
6269
+ if (span) recordToolSpanOutcome(span, settlement2.outcome);
6270
+ if (terminalEmitted) {
6271
+ throw new Error(`Tool invocation ${parsed.callId} emitted more than one terminal event`);
6272
+ }
6273
+ terminalEmitted = true;
6274
+ try {
6275
+ this.emitEvent({
6276
+ type: "tool_call_end",
6277
+ agent: agent2._name,
6278
+ tool: parsed.toolName,
6279
+ callId: parsed.callId,
6280
+ duration,
6281
+ data: {
6282
+ args: cloneToolArguments(parsed.args),
6283
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {},
6284
+ outcome: settlement2.outcome
6285
+ }
6286
+ });
6287
+ } catch (error) {
6288
+ if (error instanceof Error && settlement2.abortError !== void 0) {
6289
+ throw preserveErrorCause(error, settlement2.abortError);
6290
+ }
6291
+ throw error;
6292
+ }
6293
+ return settlement2;
6294
+ };
6295
+ const settlement = this.spanManager ? await this.spanManager.withSpanAsync(
6296
+ "axl.tool.call",
6297
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6298
+ executeAndObserve
6299
+ ) : await executeAndObserve();
6300
+ if (settlement.providerContent !== void 0) {
6301
+ currentMessages.push({
6302
+ role: "tool",
6303
+ content: settlement.providerContent,
6304
+ tool_call_id: parsed.callId
6305
+ });
5333
6306
  }
5334
- return { askId: this.executionId, depth: 0, agent: agentName };
6307
+ if (settlement.abortError !== void 0) throw settlement.abortError;
5335
6308
  }
5336
6309
  // ── ctx.ask() ─────────────────────────────────────────────────────────
5337
6310
  async ask(agent2, prompt, options) {
@@ -5360,18 +6333,21 @@ var WorkflowContext = class _WorkflowContext {
5360
6333
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
5361
6334
  const usageCapture = {};
5362
6335
  const doCall = async () => {
5363
- const result = await this.executeAgentCall(
6336
+ const result2 = await this.executeAgentCall(
5364
6337
  agent2,
5365
6338
  prompt,
5366
6339
  options,
5367
6340
  void 0,
5368
6341
  usageCapture
5369
6342
  );
5370
- return result;
6343
+ return result2;
5371
6344
  };
5372
6345
  let outcome;
6346
+ let askFailure;
6347
+ let askFailed = false;
6348
+ let result;
5373
6349
  try {
5374
- const result = this.spanManager ? await this.spanManager.withSpanAsync(
6350
+ result = this.spanManager ? await this.spanManager.withSpanAsync(
5375
6351
  "axl.agent.ask",
5376
6352
  {
5377
6353
  "axl.agent.name": agent2._name,
@@ -5430,36 +6406,34 @@ var WorkflowContext = class _WorkflowContext {
5430
6406
  );
5431
6407
  }
5432
6408
  }
5433
- return result;
5434
6409
  } catch (err) {
6410
+ askFailed = true;
6411
+ askFailure = err;
5435
6412
  outcome = {
5436
6413
  ok: false,
5437
6414
  error: err instanceof Error ? err.message : String(err)
5438
6415
  };
5439
- throw err;
5440
- } finally {
5441
- try {
5442
- this.emitEvent({
5443
- type: "ask_end",
5444
- outcome: outcome ?? {
5445
- ok: false,
5446
- error: "ask_end emitted without outcome \u2014 internal bug"
5447
- },
5448
- cost: frame.askCost.value,
5449
- ...frame.askUnpriced ? { unpriced: true } : {},
5450
- duration: Date.now() - askStart
5451
- });
5452
- } catch (emitErr) {
5453
- if (emitErr instanceof EventStreamOverflowError && outcome && !outcome.ok) {
5454
- console.error(
5455
- "[axl] ask_end emit overflowed during error path; preserving original error:",
5456
- emitErr.message
5457
- );
5458
- } else {
5459
- throw emitErr;
5460
- }
5461
- }
5462
6416
  }
6417
+ let terminalFailure;
6418
+ let terminalFailed = false;
6419
+ try {
6420
+ this.emitEvent({
6421
+ type: "ask_end",
6422
+ outcome: outcome ?? {
6423
+ ok: false,
6424
+ error: "ask_end emitted without outcome \u2014 internal bug"
6425
+ },
6426
+ cost: frame.askCost.value,
6427
+ ...frame.askUnpriced ? { unpriced: true } : {},
6428
+ duration: Date.now() - askStart
6429
+ });
6430
+ } catch (error) {
6431
+ terminalFailed = true;
6432
+ terminalFailure = isEventStreamOverflowError(error) ? preserveErrorCause(error, askFailure) : error;
6433
+ }
6434
+ if (terminalFailed) throw terminalFailure;
6435
+ if (askFailed) throw askFailure;
6436
+ return result;
5463
6437
  });
5464
6438
  },
5465
6439
  { agent: agentName }
@@ -5681,7 +6655,6 @@ ${appendedSchemaText}`;
5681
6655
  }
5682
6656
  } : { type: "json_object" };
5683
6657
  }
5684
- const callbackMeta = this.currentCallbackMeta(agent2._name);
5685
6658
  const callParams = {
5686
6659
  ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
5687
6660
  maxTokens: chatOptions.maxTokens,
@@ -5720,7 +6693,6 @@ ${appendedSchemaText}`;
5720
6693
  ...messagesSnapshot ? { messages: messagesSnapshot } : {}
5721
6694
  }
5722
6695
  });
5723
- this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
5724
6696
  let response;
5725
6697
  try {
5726
6698
  this.currentSignal?.throwIfAborted();
@@ -5746,7 +6718,6 @@ ${appendedSchemaText}`;
5746
6718
  if (chunk.type === "text_delta") {
5747
6719
  content2 += chunk.content;
5748
6720
  this.emitEvent({ type: "token", data: chunk.content });
5749
- this.onToken?.(chunk.content, callbackMeta);
5750
6721
  if (walker) {
5751
6722
  walker.processChunk(chunk.content);
5752
6723
  if (walker.consumeBoundary()) {
@@ -5856,6 +6827,7 @@ ${appendedSchemaText}`;
5856
6827
  ...retryReason ? { retryReason } : {}
5857
6828
  }
5858
6829
  });
6830
+ this.currentSignal?.throwIfAborted();
5859
6831
  if (response.tool_calls && response.tool_calls.length > 0) {
5860
6832
  currentMessages.push({
5861
6833
  role: "assistant",
@@ -6025,263 +6997,12 @@ ${appendedSchemaText}`;
6025
6997
  return await handoffFn();
6026
6998
  }
6027
6999
  }
6028
- const tool2 = agent2._config.tools?.find((candidate) => candidate.name === toolName);
6029
- const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
6030
- const toolOverride = this.toolOverrides?.get(toolName);
6031
- if (toolOverride) {
6032
- let toolArgs2;
6033
- try {
6034
- toolArgs2 = JSON.parse(toolCall.function.arguments);
6035
- } catch {
6036
- currentMessages.push({
6037
- role: "tool",
6038
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6039
- tool_call_id: toolCall.id
6040
- });
6041
- continue;
6042
- }
6043
- this.emitEvent({
6044
- type: "tool_call_start",
6045
- tool: toolName,
6046
- callId: toolCall.id,
6047
- data: { args: toolArgs2 }
6048
- });
6049
- this.onToolCall?.(
6050
- { name: toolName, args: toolArgs2, callId: toolCall.id },
6051
- callbackMeta
6052
- );
6053
- const toolStart2 = Date.now();
6054
- const suppressErrorShapeInspection2 = tool2?.sensitive === true || tool2?.toModelOutput !== void 0;
6055
- const executeOverride = async () => {
6056
- try {
6057
- return { kind: "returned", value: await toolOverride(toolArgs2) };
6058
- } catch (err) {
6059
- return {
6060
- kind: "thrown",
6061
- value: { error: err instanceof Error ? err.message : String(err) }
6062
- };
6063
- }
6064
- };
6065
- const outcome = this.spanManager ? await this.spanManager.withSpanAsync(
6066
- "axl.tool.call",
6067
- {
6068
- "axl.tool.name": toolName,
6069
- "axl.agent.name": agent2._name
6070
- },
6071
- async (span) => {
6072
- const result = await executeOverride();
6073
- span.setAttribute("axl.tool.duration", Date.now() - toolStart2);
6074
- recordToolSpanResult(span, result.value, suppressErrorShapeInspection2);
6075
- return result;
6076
- }
6077
- ) : await executeOverride();
6078
- const toolResult2 = outcome.value;
6079
- const resultContent2 = finalizeToolResult({
6080
- toolName,
6081
- configuredTool: tool2,
6082
- outcome,
6083
- legacyContent: () => tool2?.sensitive ? "[REDACTED - sensitive tool output]" : JSON.stringify(toolResult2),
6084
- emitEnd: () => {
6085
- const toolDuration2 = Date.now() - toolStart2;
6086
- this.emitEvent({
6087
- type: "tool_call_end",
6088
- agent: agent2._name,
6089
- tool: toolName,
6090
- duration: toolDuration2,
6091
- data: { args: toolArgs2, result: toolResult2, callId: toolCall.id }
6092
- });
6093
- },
6094
- beforeProjection: () => this.currentSignal?.throwIfAborted()
6095
- });
6096
- currentMessages.push({
6097
- role: "tool",
6098
- content: resultContent2,
6099
- tool_call_id: toolCall.id
6100
- });
6101
- continue;
6102
- }
6103
- if (!tool2 && !isMcpTool) {
6104
- this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
6105
- currentMessages.push({
6106
- role: "tool",
6107
- content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
6108
- tool_call_id: toolCall.id
6109
- });
6110
- continue;
6111
- }
6112
- let toolArgs;
6113
- try {
6114
- toolArgs = JSON.parse(toolCall.function.arguments);
6115
- } catch {
6116
- currentMessages.push({
6117
- role: "tool",
6118
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6119
- tool_call_id: toolCall.id
6120
- });
6121
- continue;
6122
- }
6123
- this.emitEvent({
6124
- type: "tool_call_start",
6125
- tool: toolName,
6126
- callId: toolCall.id,
6127
- data: { args: toolArgs }
6128
- });
6129
- this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
6130
- const toolStart = Date.now();
6131
- const suppressErrorShapeInspection = tool2?.sensitive === true || tool2?.toModelOutput !== void 0;
6132
- if (tool2 && tool2.requireApproval) {
6133
- const approvalFn = async () => {
6134
- const decision = await this.awaitHuman({
6135
- channel: "tool_approval",
6136
- prompt: `Tool "${toolName}" wants to execute with args: ${JSON.stringify(toolArgs)}`,
6137
- metadata: { toolName, args: toolArgs, agent: agent2._name }
6138
- });
6139
- if (!decision.approved) {
6140
- const reason = decision.reason ?? "Denied by human";
6141
- currentMessages.push({
6142
- role: "tool",
6143
- content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
6144
- tool_call_id: toolCall.id
6145
- });
6146
- return { approved: false, reason };
6147
- }
6148
- return { approved: true };
6149
- };
6150
- let approvalOutcome;
6151
- if (this.spanManager) {
6152
- approvalOutcome = await this.spanManager.withSpanAsync(
6153
- "axl.tool.approval",
6154
- {
6155
- "axl.tool.name": toolName,
6156
- "axl.agent.name": agent2._name
6157
- },
6158
- async (span) => {
6159
- const result = await approvalFn();
6160
- span.setAttribute("axl.tool.approval.approved", result.approved);
6161
- return result;
6162
- }
6163
- );
6164
- } else {
6165
- approvalOutcome = await approvalFn();
6166
- }
6167
- this.emitEvent({
6168
- type: "tool_approval",
6169
- agent: agent2._name,
6170
- tool: toolName,
6171
- data: {
6172
- approved: approvalOutcome.approved,
6173
- args: toolArgs,
6174
- ...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
6175
- }
6176
- });
6177
- if (!approvalOutcome.approved) continue;
6178
- }
6179
- if (tool2 && tool2.hooks?.before) {
6180
- try {
6181
- toolArgs = await tool2.hooks.before(toolArgs, this);
6182
- } catch (err) {
6183
- const errorMsg = err instanceof Error ? err.message : String(err);
6184
- currentMessages.push({
6185
- role: "tool",
6186
- content: JSON.stringify({ error: `Before hook error: ${errorMsg}` }),
6187
- tool_call_id: toolCall.id
6188
- });
6189
- continue;
6190
- }
6191
- }
6192
- const executeTool = async () => {
6193
- let outcome;
6194
- let resultContent2;
6195
- if (isMcpTool && this.mcpManager) {
6196
- try {
6197
- const mcpResult = await this.mcpManager.callTool(toolName, toolArgs);
6198
- outcome = { kind: "returned", value: mcpResult };
6199
- resultContent2 = mcpResult.content.map(
6200
- (c) => c.type === "text" ? c.text : `[${c.type}]`
6201
- ).join("\n");
6202
- if (mcpResult.isError) {
6203
- resultContent2 = `Error: ${resultContent2}`;
6204
- }
6205
- } catch (err) {
6206
- outcome = {
6207
- kind: "thrown",
6208
- value: { error: err instanceof Error ? err.message : String(err) }
6209
- };
6210
- resultContent2 = JSON.stringify(outcome.value);
6211
- }
6212
- } else if (tool2) {
6213
- const childCtx = this.createChildContext();
6214
- try {
6215
- outcome = { kind: "returned", value: await tool2._execute(toolArgs, childCtx) };
6216
- } catch (err) {
6217
- outcome = {
6218
- kind: "thrown",
6219
- value: { error: err instanceof Error ? err.message : String(err) }
6220
- };
6221
- }
6222
- if (outcome.kind === "returned" && tool2.hooks?.after && !(suppressErrorShapeInspection ? isErrorShapedSafely(outcome.value) : isErrorShaped(outcome.value))) {
6223
- try {
6224
- outcome = {
6225
- kind: "returned",
6226
- value: await tool2.hooks.after(outcome.value, this)
6227
- };
6228
- } catch (err) {
6229
- outcome = {
6230
- kind: "thrown",
6231
- value: {
6232
- error: `After hook error: ${err instanceof Error ? err.message : String(err)}`
6233
- }
6234
- };
6235
- }
6236
- }
6237
- if (tool2.sensitive) {
6238
- resultContent2 = "[REDACTED - sensitive tool output]";
6239
- } else if (outcome.kind === "thrown" || tool2.toModelOutput === void 0) {
6240
- resultContent2 = JSON.stringify(outcome.value);
6241
- }
6242
- } else {
6243
- outcome = { kind: "returned", value: void 0 };
6244
- resultContent2 = "Tool execution error";
6245
- }
6246
- return { outcome, resultContent: resultContent2 };
6247
- };
6248
- const traceName = isMcpTool && this.mcpManager ? this.mcpManager.getQualifiedName(toolName) ?? toolName : toolName;
6249
- const execution = this.spanManager ? await this.spanManager.withSpanAsync(
6250
- "axl.tool.call",
6251
- {
6252
- "axl.tool.name": traceName,
6253
- "axl.agent.name": agent2._name
6254
- },
6255
- async (span) => {
6256
- const r = await executeTool();
6257
- span.setAttribute("axl.tool.duration", Date.now() - toolStart);
6258
- recordToolSpanResult(span, r.outcome.value, suppressErrorShapeInspection);
6259
- return r;
6260
- }
6261
- ) : await executeTool();
6262
- const toolResult = execution.outcome.value;
6263
- const toolDuration = Date.now() - toolStart;
6264
- const resultContent = finalizeToolResult({
6265
- toolName,
6266
- configuredTool: tool2,
6267
- outcome: execution.outcome,
6268
- legacyContent: () => execution.resultContent,
6269
- emitEnd: () => {
6270
- this.emitEvent({
6271
- type: "tool_call_end",
6272
- agent: agent2._name,
6273
- tool: traceName,
6274
- duration: toolDuration,
6275
- data: { args: toolArgs, result: toolResult, callId: toolCall.id }
6276
- });
6277
- },
6278
- beforeProjection: () => this.currentSignal?.throwIfAborted()
6279
- });
6280
- currentMessages.push({
6281
- role: "tool",
6282
- content: resultContent,
6283
- tool_call_id: toolCall.id
6284
- });
7000
+ await this.observeToolInvocation(
7001
+ agent2,
7002
+ toolCall,
7003
+ currentMessages,
7004
+ toolDefs.map((tool2) => tool2.function.name)
7005
+ );
6285
7006
  }
6286
7007
  continue;
6287
7008
  }
@@ -6401,7 +7122,7 @@ ${appendedSchemaText}`;
6401
7122
  pendingRetryReason = "schema";
6402
7123
  continue;
6403
7124
  }
6404
- const zodErr = schemaErr instanceof ZodError ? schemaErr : new ZodError([
7125
+ const zodErr = schemaErr instanceof ZodError2 ? schemaErr : new ZodError2([
6405
7126
  {
6406
7127
  code: "custom",
6407
7128
  path: [],
@@ -6418,6 +7139,7 @@ ${appendedSchemaText}`;
6418
7139
  metadata: this.metadata
6419
7140
  });
6420
7141
  } catch (err) {
7142
+ rethrowEventStreamOverflow(err);
6421
7143
  const reason = err instanceof Error ? err.message : String(err);
6422
7144
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
6423
7145
  }
@@ -6754,12 +7476,12 @@ ${summaryResponse.content}`
6754
7476
  * Execute a function with checkpoint-replay semantics.
6755
7477
  *
6756
7478
  * On first execution, runs `fn()`, saves the result under `name`, and
6757
- * returns it. On replay (resume after restart), returns the saved result
6758
- * without re-executing. This prevents duplicate side effects (double API
6759
- * calls, double refunds, etc.).
7479
+ * returns it. A later evaluation in the same explicit execution scope
7480
+ * returns the saved result without re-executing. Axl does not restart or
7481
+ * resume workflow continuations across processes.
6760
7482
  *
6761
7483
  * `name` must be a stable, caller-supplied identifier — the same call
6762
- * site MUST pass the same name across runs of the same execution for
7484
+ * site MUST pass the same name across evaluations of the same execution for
6763
7485
  * replay to work. Names are scoped to a single execution and must be
6764
7486
  * unique within it; reusing a name (e.g. inside a loop) gives
6765
7487
  * last-write-wins behavior. For loops, compose names from the iterator:
@@ -6882,6 +7604,7 @@ ${summaryResponse.content}`
6882
7604
  for (let i = 0; i < n; i++) {
6883
7605
  const index = i;
6884
7606
  const p = signalStorage.run(composedSignal, () => fn(index));
7607
+ this._registerBranchDrain(p);
6885
7608
  p.then((value) => {
6886
7609
  if (settled) return;
6887
7610
  results[index] = { ok: true, value };
@@ -6896,6 +7619,14 @@ ${summaryResponse.content}`
6896
7619
  reject(new QuorumNotMet(quorum, successCount, results));
6897
7620
  }
6898
7621
  }).catch((err) => {
7622
+ if (isEventStreamOverflowError(err)) {
7623
+ if (!settled) {
7624
+ settled = true;
7625
+ controller.abort();
7626
+ reject(err);
7627
+ }
7628
+ return;
7629
+ }
6899
7630
  if (settled) return;
6900
7631
  const isAbort = err instanceof DOMException && err.name === "AbortError";
6901
7632
  if (isAbort) {
@@ -6918,14 +7649,16 @@ ${summaryResponse.content}`
6918
7649
  }
6919
7650
  const parentSignal = this.currentSignal;
6920
7651
  const promises = Array.from({ length: n }, (_, i) => {
6921
- const run = () => fn(i).then((value) => ({ ok: true, value })).catch(
6922
- (err) => ({
7652
+ const run = () => fn(i).then((value) => ({ ok: true, value })).catch((err) => {
7653
+ rethrowEventStreamOverflow(err);
7654
+ return {
6923
7655
  ok: false,
6924
7656
  error: err instanceof Error ? err.message : String(err)
6925
- })
6926
- );
7657
+ };
7658
+ });
6927
7659
  return parentSignal ? signalStorage.run(parentSignal, run) : run();
6928
7660
  });
7661
+ for (const promise of promises) this._registerBranchDrain(promise);
6929
7662
  return Promise.all(promises);
6930
7663
  }
6931
7664
  // ── ctx.vote() ────────────────────────────────────────────────────────
@@ -7063,6 +7796,7 @@ ${summaryResponse.content}`
7063
7796
  try {
7064
7797
  validateResult = await options.validate(parsed, { metadata: this.metadata });
7065
7798
  } catch (err) {
7799
+ rethrowEventStreamOverflow(err);
7066
7800
  const reason = err instanceof Error ? err.message : String(err);
7067
7801
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
7068
7802
  }
@@ -7080,6 +7814,7 @@ ${summaryResponse.content}`
7080
7814
  emitVerifyOutcome(true, attempt + 1);
7081
7815
  return parsed;
7082
7816
  } catch (err) {
7817
+ rethrowEventStreamOverflow(err);
7083
7818
  if (err instanceof ValidationError) {
7084
7819
  lastRetry = {
7085
7820
  error: err.reason,
@@ -7102,12 +7837,12 @@ ${summaryResponse.content}`
7102
7837
  }
7103
7838
  continue;
7104
7839
  }
7105
- const errorMsg = err instanceof ZodError ? err.message : err instanceof Error ? err.message : String(err);
7840
+ const errorMsg = err instanceof ZodError2 ? err.message : err instanceof Error ? err.message : String(err);
7106
7841
  lastRetry = { error: errorMsg, output: rawOutput };
7107
7842
  if (attempt === maxRetries) {
7108
7843
  emitVerifyOutcome(false, attempt + 1, errorMsg);
7109
7844
  if (options?.fallback !== void 0) return options.fallback;
7110
- const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: errorMsg }]);
7845
+ const zodErr = err instanceof ZodError2 ? err : new ZodError2([{ code: "custom", path: [], message: errorMsg }]);
7111
7846
  throw new VerifyError(rawOutput, zodErr, maxRetries);
7112
7847
  }
7113
7848
  }
@@ -7116,7 +7851,7 @@ ${summaryResponse.content}`
7116
7851
  if (options?.fallback !== void 0) return options.fallback;
7117
7852
  throw new VerifyError(
7118
7853
  lastRetry?.output,
7119
- new ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
7854
+ new ZodError2([{ code: "custom", path: [], message: "Verify failed" }]),
7120
7855
  maxRetries
7121
7856
  );
7122
7857
  }
@@ -7146,6 +7881,7 @@ ${summaryResponse.content}`
7146
7881
  const unpriced = this.budgetContext.unpriced;
7147
7882
  return { value, budgetExceeded: exceeded, totalCost, unpriced };
7148
7883
  } catch (err) {
7884
+ rethrowEventStreamOverflow(err);
7149
7885
  if (this.budgetContext.exceeded) {
7150
7886
  return {
7151
7887
  value: null,
@@ -7246,7 +7982,8 @@ ${summaryResponse.content}`
7246
7982
  let remaining = fns.length;
7247
7983
  for (const fn of fns) {
7248
7984
  const p = signalStorage.run(composedSignal, fn);
7249
- p.then(async (value) => {
7985
+ this._registerBranchDrain(p);
7986
+ const continuation = p.then(async (value) => {
7250
7987
  if (settled) return;
7251
7988
  if (schema) {
7252
7989
  const parsed = schema.safeParse(value);
@@ -7276,6 +8013,7 @@ ${summaryResponse.content}`
7276
8013
  return;
7277
8014
  }
7278
8015
  } catch (err) {
8016
+ rethrowEventStreamOverflow(err);
7279
8017
  remaining--;
7280
8018
  lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
7281
8019
  if (remaining === 0 && !settled) {
@@ -7295,6 +8033,14 @@ ${summaryResponse.content}`
7295
8033
  controller.abort();
7296
8034
  resolve2(value);
7297
8035
  }).catch((err) => {
8036
+ if (isEventStreamOverflowError(err)) {
8037
+ if (!settled) {
8038
+ settled = true;
8039
+ controller.abort();
8040
+ reject(err);
8041
+ }
8042
+ throw err;
8043
+ }
7298
8044
  if (settled) return;
7299
8045
  if (err instanceof DOMException && err.name === "AbortError") {
7300
8046
  remaining--;
@@ -7311,6 +8057,7 @@ ${summaryResponse.content}`
7311
8057
  reject(lastError);
7312
8058
  }
7313
8059
  });
8060
+ this._registerBranchDrain(continuation);
7314
8061
  }
7315
8062
  });
7316
8063
  }
@@ -7349,9 +8096,19 @@ ${summaryResponse.content}`
7349
8096
  const idx = nextIndex++;
7350
8097
  try {
7351
8098
  const value = mapSignal ? await signalStorage.run(mapSignal, () => fn(items[idx], idx)) : await fn(items[idx], idx);
8099
+ if (settled) return;
7352
8100
  results[idx] = { ok: true, value };
7353
8101
  successCount++;
7354
8102
  } catch (err) {
8103
+ if (isEventStreamOverflowError(err)) {
8104
+ settled = true;
8105
+ controller?.abort();
8106
+ reject(err);
8107
+ throw err;
8108
+ }
8109
+ if (settled) {
8110
+ return;
8111
+ }
7355
8112
  if (err instanceof DOMException && err.name === "AbortError" && controller?.signal.aborted) {
7356
8113
  completedCount++;
7357
8114
  return;
@@ -7377,7 +8134,9 @@ ${summaryResponse.content}`
7377
8134
  };
7378
8135
  const workers = Math.min(concurrency, items.length);
7379
8136
  for (let i = 0; i < workers; i++) {
7380
- runNext().catch((err) => {
8137
+ const worker = runNext();
8138
+ this._registerBranchDrain(worker);
8139
+ worker.catch((err) => {
7381
8140
  if (!settled) reject(err);
7382
8141
  });
7383
8142
  }
@@ -7403,12 +8162,44 @@ ${summaryResponse.content}`
7403
8162
  return this._awaitHumanImpl(options);
7404
8163
  }
7405
8164
  async _awaitHumanImpl(options) {
8165
+ const branchSignal = this.currentSignal;
8166
+ const makeAbortError = (reason) => {
8167
+ if (typeof DOMException !== "undefined") {
8168
+ return new DOMException(
8169
+ typeof reason === "string" ? reason : "awaitHuman aborted",
8170
+ "AbortError"
8171
+ );
8172
+ }
8173
+ const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
8174
+ err.name = "AbortError";
8175
+ return err;
8176
+ };
8177
+ const awaitWithAbort = async (promise, signal2 = branchSignal) => {
8178
+ if (!signal2) return promise;
8179
+ if (signal2.aborted) throw makeAbortError(signal2.reason);
8180
+ let onAbort;
8181
+ try {
8182
+ return await Promise.race([
8183
+ promise,
8184
+ new Promise((_, reject) => {
8185
+ onAbort = () => reject(makeAbortError(signal2.reason));
8186
+ signal2.addEventListener("abort", onAbort, { once: true });
8187
+ })
8188
+ ]);
8189
+ } finally {
8190
+ if (onAbort) signal2.removeEventListener("abort", onAbort);
8191
+ }
8192
+ };
7406
8193
  if (this.awaitHumanHandler) {
8194
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
7407
8195
  this.emitEvent({
7408
8196
  type: "await_human",
7409
8197
  data: { channel: options.channel, prompt: options.prompt }
7410
8198
  });
7411
- const decision2 = await this.awaitHumanHandler(options);
8199
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
8200
+ const decision2 = parseHumanDecision(
8201
+ await awaitWithAbort(Promise.resolve(this.awaitHumanHandler(options)))
8202
+ );
7412
8203
  this.emitEvent({
7413
8204
  type: "await_human_resolved",
7414
8205
  data: { channel: options.channel, decision: decision2 }
@@ -7420,68 +8211,126 @@ ${summaryResponse.content}`
7420
8211
  "Tool requires approval but no approval handler is configured. Provide awaitHumanHandler to createContext() or use runtime.execute() with workflow infrastructure."
7421
8212
  );
7422
8213
  }
7423
- if (this.stateStore) {
7424
- await this.stateStore.savePendingDecision(this.executionId, {
7425
- executionId: this.executionId,
7426
- channel: options.channel,
7427
- prompt: options.prompt,
7428
- metadata: options.metadata,
7429
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7430
- });
7431
- await this.stateStore.saveExecutionState(this.executionId, {
7432
- workflow: this.workflowName ?? "unknown",
7433
- input: this.input,
7434
- step: this.stepRefRoot.value,
7435
- status: "waiting",
7436
- metadata: {
7437
- ...this.metadata,
7438
- awaitHumanChannel: options.channel,
7439
- awaitHumanPrompt: options.prompt
7440
- }
7441
- });
7442
- }
7443
- this.emitEvent({
7444
- type: "await_human",
7445
- data: { channel: options.channel, prompt: options.prompt }
8214
+ const existingClaim = this.awaitHumanClaims.get(this.executionId);
8215
+ if (existingClaim) {
8216
+ const error = new AxlError(
8217
+ "CONCURRENT_HUMAN_DECISION_UNSUPPORTED",
8218
+ `Execution "${this.executionId}" already has an active human decision. Concurrent awaitHuman calls require request-scoped decision identifiers.`
8219
+ );
8220
+ existingClaim.abort(error);
8221
+ throw error;
8222
+ }
8223
+ const claimController = new AbortController();
8224
+ this.awaitHumanClaims.set(this.executionId, claimController);
8225
+ const signal = branchSignal ? AbortSignal.any([branchSignal, claimController.signal]) : claimController.signal;
8226
+ let saveAttempted = false;
8227
+ let markReady;
8228
+ const ready = new Promise((resolve2) => {
8229
+ markReady = resolve2;
7446
8230
  });
7447
- const makeAbortError = (reason) => {
7448
- if (typeof DOMException !== "undefined") {
7449
- return new DOMException(
7450
- typeof reason === "string" ? reason : "awaitHuman aborted",
7451
- "AbortError"
7452
- );
7453
- }
7454
- const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
7455
- err.name = "AbortError";
7456
- return err;
8231
+ let markCleanupFinished;
8232
+ const cleanupFinished = new Promise((resolve2) => {
8233
+ markCleanupFinished = resolve2;
8234
+ });
8235
+ let resolveDecision;
8236
+ let rejectDecision;
8237
+ const decisionPromise = new Promise((resolve2, reject) => {
8238
+ resolveDecision = resolve2;
8239
+ rejectDecision = reject;
8240
+ });
8241
+ void decisionPromise.catch(() => {
8242
+ });
8243
+ const registeredResolver = {
8244
+ resolve: resolveDecision,
8245
+ ready,
8246
+ state: "preparing",
8247
+ cancelled: false,
8248
+ cleanupFinished
7457
8249
  };
7458
- const decision = await new Promise((resolve2, reject) => {
7459
- if (this.signal?.aborted) {
7460
- reject(makeAbortError(this.signal.reason));
7461
- return;
8250
+ this.pendingDecisions.set(this.executionId, registeredResolver);
8251
+ const abortListener = () => {
8252
+ registeredResolver.cancelled = true;
8253
+ markReady();
8254
+ rejectDecision(makeAbortError(signal.reason));
8255
+ };
8256
+ signal.addEventListener("abort", abortListener, { once: true });
8257
+ let decision;
8258
+ try {
8259
+ if (signal.aborted) throw makeAbortError(signal.reason);
8260
+ if (this.stateStore) {
8261
+ saveAttempted = true;
8262
+ await this.stateStore.savePendingDecision(this.executionId, {
8263
+ executionId: this.executionId,
8264
+ channel: options.channel,
8265
+ prompt: options.prompt,
8266
+ metadata: options.metadata,
8267
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8268
+ });
7462
8269
  }
7463
- this.pendingDecisions.set(this.executionId, resolve2);
7464
- const onAbort = () => {
7465
- const current = this.pendingDecisions.get(this.executionId);
7466
- if (current === resolve2) {
7467
- this.pendingDecisions.delete(this.executionId);
8270
+ if (signal.aborted) throw makeAbortError(signal.reason);
8271
+ this.emitEvent({
8272
+ type: "await_human",
8273
+ data: { channel: options.channel, prompt: options.prompt }
8274
+ });
8275
+ registeredResolver.state = "pending";
8276
+ markReady();
8277
+ decision = await decisionPromise;
8278
+ } catch (error) {
8279
+ if (registeredResolver.state === "preparing") {
8280
+ registeredResolver.state = "failed";
8281
+ markReady();
8282
+ }
8283
+ let needsCompensation = saveAttempted;
8284
+ const inFlightResolution = registeredResolver.resolution;
8285
+ if (registeredResolver.state === "resolving" && inFlightResolution) {
8286
+ needsCompensation = !await inFlightResolution;
8287
+ } else if (registeredResolver.state === "resolved") {
8288
+ needsCompensation = false;
8289
+ }
8290
+ if (this.stateStore && needsCompensation) {
8291
+ try {
8292
+ await this.stateStore.resolveDecision(this.executionId, {
8293
+ approved: false,
8294
+ reason: "Execution aborted while awaiting approval"
8295
+ });
8296
+ } catch (cleanupError) {
8297
+ if (error instanceof Error && Object.isExtensible(error)) {
8298
+ Object.defineProperty(error, "cleanupError", {
8299
+ configurable: true,
8300
+ enumerable: false,
8301
+ value: cleanupError
8302
+ });
8303
+ }
8304
+ try {
8305
+ this._onDecisionCleanupFailed?.({
8306
+ executionId: this.executionId,
8307
+ ...this.workflowName ? { workflow: this.workflowName } : {},
8308
+ operation: "resolveDecision_compensation",
8309
+ error: cleanupError
8310
+ });
8311
+ } catch (observerError) {
8312
+ console.error(
8313
+ "[axl] decision_cleanup_failed listener threw; workflow outcome unchanged:",
8314
+ observerError instanceof Error ? observerError.message : String(observerError)
8315
+ );
8316
+ }
7468
8317
  }
7469
- reject(makeAbortError(this.signal?.reason));
7470
- };
7471
- this.signal?.addEventListener("abort", onAbort, { once: true });
7472
- });
8318
+ }
8319
+ throw error;
8320
+ } finally {
8321
+ signal.removeEventListener("abort", abortListener);
8322
+ if (this.pendingDecisions.get(this.executionId) === registeredResolver) {
8323
+ this.pendingDecisions.delete(this.executionId);
8324
+ }
8325
+ if (this.awaitHumanClaims.get(this.executionId) === claimController) {
8326
+ this.awaitHumanClaims.delete(this.executionId);
8327
+ }
8328
+ markCleanupFinished();
8329
+ }
7473
8330
  this.emitEvent({
7474
8331
  type: "await_human_resolved",
7475
8332
  data: { channel: options.channel, decision }
7476
8333
  });
7477
- if (this.stateStore) {
7478
- await this.stateStore.saveExecutionState(this.executionId, {
7479
- workflow: this.workflowName ?? "unknown",
7480
- input: this.input,
7481
- step: this.stepRefRoot.value,
7482
- status: "running"
7483
- });
7484
- }
7485
8334
  return decision;
7486
8335
  }
7487
8336
  /**
@@ -7533,8 +8382,8 @@ ${summaryResponse.content}`
7533
8382
  // Internal: the runtime is the only caller, user workflows never call these.
7534
8383
  /** @internal — idempotent; no-ops on second+ call within one ctx. */
7535
8384
  _emitWorkflowStart(input) {
7536
- if (this._workflowStartEmitted) return;
7537
- this._workflowStartEmitted = true;
8385
+ if (this.workflowLifecycleState.startEmitted) return;
8386
+ this.workflowLifecycleState.startEmitted = true;
7538
8387
  this.emitEvent({
7539
8388
  type: "workflow_start",
7540
8389
  workflow: this.workflowName,
@@ -7550,8 +8399,8 @@ ${summaryResponse.content}`
7550
8399
  * status. First-wins semantics: the completed event stands, the
7551
8400
  * inner side-effect failure propagates as a normal thrown error. */
7552
8401
  _emitWorkflowEnd(info) {
7553
- if (this._workflowEndEmitted) return;
7554
- this._workflowEndEmitted = true;
8402
+ if (this.workflowLifecycleState.endEmitted) return;
8403
+ this.workflowLifecycleState.endEmitted = true;
7555
8404
  this.emitEvent({
7556
8405
  type: "workflow_end",
7557
8406
  workflow: this.workflowName,
@@ -7561,7 +8410,8 @@ ${summaryResponse.content}`
7561
8410
  duration: info.duration,
7562
8411
  ...info.result !== void 0 ? { result: info.result } : {},
7563
8412
  ...info.error !== void 0 ? { error: info.error } : {},
7564
- ...info.aborted ? { aborted: true } : {}
8413
+ ...info.aborted ? { aborted: true } : {},
8414
+ ...info.observation ? { observation: info.observation } : {}
7565
8415
  }
7566
8416
  });
7567
8417
  }
@@ -7850,6 +8700,7 @@ ${summaryResponse.content}`
7850
8700
  * consumer sites via the `type` discriminator.
7851
8701
  */
7852
8702
  emitEvent(partial) {
8703
+ if (this.workflowLifecycleState.endEmitted && partial.type !== "workflow_end") return;
7853
8704
  const data = partial.data;
7854
8705
  const frame = askStorage.getStore();
7855
8706
  const step = (frame?.stepRef ?? this.stepRefRoot).value++;
@@ -7875,6 +8726,7 @@ ${summaryResponse.content}`
7875
8726
  }
7876
8727
  }
7877
8728
  const event = {
8729
+ schemaVersion: 2,
7878
8730
  executionId: this.executionId,
7879
8731
  step,
7880
8732
  timestamp: Date.now(),
@@ -7895,13 +8747,13 @@ ${summaryResponse.content}`
7895
8747
  ...partial,
7896
8748
  data
7897
8749
  };
7898
- const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
8750
+ const finalEvent = this.config.trace?.redact ? redactHistoricalEvent(event) : event;
7899
8751
  let overflowErr;
7900
8752
  if (this.onTrace) {
7901
8753
  try {
7902
8754
  this.onTrace(finalEvent);
7903
8755
  } catch (err) {
7904
- if (err instanceof EventStreamOverflowError) {
8756
+ if (isEventStreamOverflowError(err)) {
7905
8757
  overflowErr = err;
7906
8758
  } else {
7907
8759
  console.error(
@@ -7916,7 +8768,7 @@ ${summaryResponse.content}`
7916
8768
  try {
7917
8769
  bus._push(finalEvent);
7918
8770
  } catch (err) {
7919
- if (err instanceof EventStreamOverflowError) {
8771
+ if (isEventStreamOverflowError(err)) {
7920
8772
  if (!overflowErr) {
7921
8773
  overflowErr = err;
7922
8774
  } else {
@@ -8103,26 +8955,31 @@ var MemoryStore = class {
8103
8955
  }
8104
8956
  // ── Execution History ──────────────────────────────────────────────
8105
8957
  async saveExecution(execution) {
8106
- this.executionHistory.set(execution.executionId, structuredClone(execution));
8958
+ const normalized = normalizeStoredExecution({
8959
+ ...execution,
8960
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
8961
+ });
8962
+ this.executionHistory.set(execution.executionId, structuredClone(normalized));
8107
8963
  }
8108
8964
  async getExecution(executionId) {
8109
8965
  const exec = this.executionHistory.get(executionId);
8110
- return exec ? structuredClone(exec) : null;
8966
+ return exec ? normalizeStoredExecution(structuredClone(exec)) : null;
8111
8967
  }
8112
8968
  async listExecutions(limit) {
8113
8969
  const sorted = [...this.executionHistory.values()].sort((a, b) => b.startedAt - a.startedAt);
8114
8970
  const result = limit ? sorted.slice(0, limit) : sorted;
8115
- return result.map((e) => structuredClone(e));
8971
+ return result.map((e) => normalizeStoredExecution(structuredClone(e)));
8116
8972
  }
8117
8973
  async deleteExecution(executionId) {
8118
- const removed = this.executionHistory.delete(executionId);
8119
- this.checkpoints.delete(executionId);
8120
- this.executionStates.delete(executionId);
8121
- this.streamingEvents.delete(executionId);
8122
- if (this.decisions.delete(executionId)) {
8974
+ const removedHistory = this.executionHistory.delete(executionId);
8975
+ const removedCheckpoints = this.checkpoints.delete(executionId);
8976
+ const removedState = this.executionStates.delete(executionId);
8977
+ const removedStreaming = this.streamingEvents.delete(executionId);
8978
+ const removedDecision = this.decisions.delete(executionId);
8979
+ if (removedDecision) {
8123
8980
  this.persistAwaitHumanState();
8124
8981
  }
8125
- return removed;
8982
+ return removedHistory || removedCheckpoints || removedState || removedStreaming || removedDecision;
8126
8983
  }
8127
8984
  // ── Eval History ──────────────────────────────────────────────────
8128
8985
  async saveEvalResult(entry) {
@@ -8228,16 +9085,23 @@ function rowToExecutionInfo(row) {
8228
9085
  startedAt: row.started_at,
8229
9086
  completedAt: row.completed_at ?? void 0,
8230
9087
  duration: row.duration,
8231
- error: row.error ?? void 0,
8232
- events: safeJsonParse(row.events) ?? []
9088
+ events: safeJsonParse(row.events) ?? [],
9089
+ ...row.event_schema_version != null ? { eventSchemaVersion: row.event_schema_version } : {}
8233
9090
  };
9091
+ if (row.error != null) info.error = row.error;
8234
9092
  if (row.metadata != null) {
8235
9093
  const parsed = safeJsonParse(row.metadata);
8236
9094
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8237
9095
  info.metadata = parsed;
8238
9096
  }
8239
9097
  }
8240
- return info;
9098
+ if (row.observation != null) {
9099
+ const parsed = safeJsonParse(row.observation);
9100
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9101
+ info.observation = parsed;
9102
+ }
9103
+ }
9104
+ return normalizeStoredExecution(info);
8241
9105
  }
8242
9106
  var SQLiteStore = class {
8243
9107
  db;
@@ -8277,6 +9141,10 @@ var SQLiteStore = class {
8277
9141
  * column (TEXT, nullable, JSON-serialized) so caller-supplied
8278
9142
  * `ExecuteOptions.metadata` round-trips through history. Existing
8279
9143
  * rows get `NULL`, deserialized as `metadata: undefined`.
9144
+ * - v4 (event schema): add `execution_history.event_schema_version`.
9145
+ * Existing rows remain `NULL`, the documented v1 sentinel.
9146
+ * - v5 (observation completeness): add `execution_history.observation`.
9147
+ * Existing rows remain `NULL`, meaning they predate the contract.
8280
9148
  *
8281
9149
  * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
8282
9150
  * IF NOT EXISTS` runs against the post-migration column name. Fresh
@@ -8294,7 +9162,7 @@ var SQLiteStore = class {
8294
9162
  * transactional re-read costs nothing.
8295
9163
  */
8296
9164
  migrate() {
8297
- const TARGET_VERSION = 3;
9165
+ const TARGET_VERSION = 5;
8298
9166
  const current = this.db.pragma("user_version", { simple: true });
8299
9167
  if (current >= TARGET_VERSION) return;
8300
9168
  this.db.exec("BEGIN IMMEDIATE");
@@ -8322,6 +9190,18 @@ var SQLiteStore = class {
8322
9190
  this.db.exec("ALTER TABLE execution_history ADD COLUMN metadata TEXT");
8323
9191
  }
8324
9192
  }
9193
+ if (committed < 4) {
9194
+ const cols = this.db.pragma("table_info(execution_history)");
9195
+ if (cols.length > 0 && !cols.some((c) => c.name === "event_schema_version")) {
9196
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN event_schema_version INTEGER");
9197
+ }
9198
+ }
9199
+ if (committed < 5) {
9200
+ const cols = this.db.pragma("table_info(execution_history)");
9201
+ if (cols.length > 0 && !cols.some((c) => c.name === "observation")) {
9202
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN observation TEXT");
9203
+ }
9204
+ }
8325
9205
  this.db.pragma(`user_version = ${TARGET_VERSION}`);
8326
9206
  this.db.exec("COMMIT");
8327
9207
  } catch (err) {
@@ -8385,7 +9265,9 @@ var SQLiteStore = class {
8385
9265
  duration INTEGER NOT NULL DEFAULT 0,
8386
9266
  error TEXT,
8387
9267
  events TEXT NOT NULL,
8388
- metadata TEXT
9268
+ metadata TEXT,
9269
+ event_schema_version INTEGER,
9270
+ observation TEXT
8389
9271
  );
8390
9272
 
8391
9273
  CREATE TABLE IF NOT EXISTS eval_history (
@@ -8470,7 +9352,7 @@ var SQLiteStore = class {
8470
9352
  const stmt = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?");
8471
9353
  stmt.run(executionId);
8472
9354
  }
8473
- // ── Execution State (for awaitHuman suspend/resume) ────────────────────
9355
+ // ── Legacy application-managed execution state ────────────────────────
8474
9356
  async saveExecutionState(executionId, state) {
8475
9357
  const stmt = this.db.prepare(
8476
9358
  "INSERT OR REPLACE INTO execution_state (execution_id, workflow, input, step, status, metadata) VALUES (?, ?, ?, ?, ?, ?)"
@@ -8505,19 +9387,25 @@ var SQLiteStore = class {
8505
9387
  }
8506
9388
  // ── Execution History ────────────────────────────────────────────────────
8507
9389
  async saveExecution(execution) {
9390
+ const normalized = normalizeStoredExecution({
9391
+ ...execution,
9392
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
9393
+ });
8508
9394
  this.db.prepare(
8509
- "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
9395
+ "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events, metadata, event_schema_version, observation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8510
9396
  ).run(
8511
- execution.executionId,
8512
- execution.workflow,
8513
- execution.status,
8514
- execution.totalCost,
8515
- execution.startedAt,
8516
- execution.completedAt ?? null,
8517
- execution.duration,
8518
- execution.error ?? null,
8519
- JSON.stringify(execution.events),
8520
- execution.metadata !== void 0 ? JSON.stringify(execution.metadata) : null
9397
+ normalized.executionId,
9398
+ normalized.workflow,
9399
+ normalized.status,
9400
+ normalized.totalCost,
9401
+ normalized.startedAt,
9402
+ normalized.completedAt ?? null,
9403
+ normalized.duration,
9404
+ normalized.error ?? null,
9405
+ JSON.stringify(normalized.events),
9406
+ normalized.metadata !== void 0 ? JSON.stringify(normalized.metadata) : null,
9407
+ normalized.eventSchemaVersion,
9408
+ normalized.observation !== void 0 ? JSON.stringify(normalized.observation) : null
8521
9409
  );
8522
9410
  }
8523
9411
  async getExecution(executionId) {
@@ -8531,11 +9419,11 @@ var SQLiteStore = class {
8531
9419
  }
8532
9420
  async deleteExecution(executionId) {
8533
9421
  const tx = this.db.transaction(() => {
8534
- const result = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
8535
- this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
8536
- this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
8537
- this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
8538
- return result.changes > 0;
9422
+ const historyResult = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
9423
+ const checkpointsResult = this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
9424
+ const stateResult = this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
9425
+ const decisionsResult = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
9426
+ return historyResult.changes + checkpointsResult.changes + stateResult.changes + decisionsResult.changes > 0;
8539
9427
  });
8540
9428
  return tx();
8541
9429
  }
@@ -8861,6 +9749,11 @@ var AxlStream = class extends Readable {
8861
9749
  get lifecycle() {
8862
9750
  return this.events.lifecycle;
8863
9751
  }
9752
+ /** Completeness of this stream's iterator queue. Check after consumption
9753
+ * before treating an unmatched lifecycle event as a producer defect. */
9754
+ get observationStatus() {
9755
+ return this.events.observationStatus;
9756
+ }
8864
9757
  /** Coalescing iterator over `partial_object` events — yields the latest
8865
9758
  * object payload per `askId`. Designed for streaming-structured-output
8866
9759
  * UIs: when the producer outpaces the consumer, intermediate snapshots
@@ -8959,6 +9852,7 @@ var AxlStream = class extends Readable {
8959
9852
  if (this.events.isFinished) return;
8960
9853
  this.result = result;
8961
9854
  const doneEvent = {
9855
+ schemaVersion: 2,
8962
9856
  type: "done",
8963
9857
  executionId,
8964
9858
  step: Number.MAX_SAFE_INTEGER,
@@ -8977,6 +9871,7 @@ var AxlStream = class extends Readable {
8977
9871
  _error(error, executionId) {
8978
9872
  if (this.events.isFinished) return;
8979
9873
  const errorEvent = {
9874
+ schemaVersion: 2,
8980
9875
  type: "error",
8981
9876
  executionId,
8982
9877
  step: Number.MAX_SAFE_INTEGER,
@@ -9555,7 +10450,7 @@ async function createSpanManager(config) {
9555
10450
  if (!config?.enabled) {
9556
10451
  return new NoopSpanManager();
9557
10452
  }
9558
- const { OTelSpanManager } = await import("./span-manager-3IKXXUTZ.js");
10453
+ const { OTelSpanManager } = await import("./span-manager-74ZCGELO.js");
9559
10454
  return OTelSpanManager.create(config);
9560
10455
  }
9561
10456
 
@@ -9577,11 +10472,7 @@ var STREAMING_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
9577
10472
  "partial_object",
9578
10473
  "string_delta"
9579
10474
  ]);
9580
- var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set([
9581
- "sessionId",
9582
- "sessionHistory",
9583
- "resumeMode"
9584
- ]);
10475
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["sessionId", "sessionHistory"]);
9585
10476
  function liftPersistedMetadata(metadata) {
9586
10477
  if (!metadata) return void 0;
9587
10478
  const filtered = {};
@@ -9699,7 +10590,7 @@ var StreamingFlusher = class {
9699
10590
  await Promise.allSettled(pendings);
9700
10591
  }
9701
10592
  };
9702
- function isAbortError2(err) {
10593
+ function isAbortError4(err) {
9703
10594
  return typeof err === "object" && err !== null && err.name === "AbortError";
9704
10595
  }
9705
10596
  function pushEventBounded(execInfo, event, cap) {
@@ -9711,7 +10602,18 @@ function pushEventBounded(execInfo, event, cap) {
9711
10602
  const last = execInfo.events[execInfo.events.length - 1];
9712
10603
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9713
10604
  if (!alreadyTruncated) {
10605
+ if (execInfo.observation?.complete !== false) {
10606
+ execInfo.observation = {
10607
+ complete: false,
10608
+ reason: "persistence_truncated",
10609
+ maxEvents: cap
10610
+ };
10611
+ }
10612
+ if (event.type === "workflow_end") {
10613
+ event.data.observation = execInfo.observation;
10614
+ }
9714
10615
  execInfo.events[execInfo.events.length - 1] = {
10616
+ schemaVersion: 2,
9715
10617
  type: "log",
9716
10618
  executionId: event.executionId,
9717
10619
  step: event.step,
@@ -9740,7 +10642,9 @@ function boundRecoveredEvents(events, cap) {
9740
10642
  const last = truncated[truncated.length - 1];
9741
10643
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9742
10644
  if (!alreadyTruncated) {
10645
+ const version = getEventSchemaVersion(events[0] ?? {});
9743
10646
  truncated[truncated.length - 1] = {
10647
+ ...version === 2 ? { schemaVersion: 2 } : {},
9744
10648
  type: "log",
9745
10649
  executionId: events[0]?.executionId ?? "",
9746
10650
  step: last?.step ?? 0,
@@ -9754,6 +10658,16 @@ function boundRecoveredEvents(events, cap) {
9754
10658
  }
9755
10659
  return truncated;
9756
10660
  }
10661
+ var REMOVED_OBSERVATION_CALLBACKS = ["onToken", "onToolCall", "onAgentStart"];
10662
+ function assertNoRemovedObservationCallbacks(options) {
10663
+ if (options === null || typeof options !== "object") return;
10664
+ const removed = REMOVED_OBSERVATION_CALLBACKS.filter((key) => Object.hasOwn(options, key));
10665
+ if (removed.length === 0) return;
10666
+ throw new AxlError(
10667
+ "INVALID_CONFIG",
10668
+ `runtime.createContext() no longer accepts ${removed.join(", ")}. Observe ctx.events or use runtime.stream() instead. See docs/migration/stream-first-observation.md.`
10669
+ );
10670
+ }
9757
10671
  var costScopeStorage = new AsyncLocalStorage2();
9758
10672
  function forwardAbortSignal(external, internal) {
9759
10673
  if (!external) return () => {
@@ -9915,9 +10829,7 @@ var AxlRuntime = class extends EventEmitter2 {
9915
10829
  * → parse output → emit `workflow_end` → delete checkpoints → persist.
9916
10830
  *
9917
10831
  * Shared between `execute()` and `stream()`. Centralizes the
9918
- * "start iff end" pairing invariant so a future emit path
9919
- * (e.g., `resumeExecution`) can inherit correct behavior by routing
9920
- * through this helper. Also factors out the AbortError detection.
10832
+ * "start iff end" pairing invariant and AbortError detection.
9921
10833
  *
9922
10834
  * Side effects on `execInfo`: sets `status` / `completedAt` /
9923
10835
  * `duration` / `result` (or `error`) before emitting `workflow_end`.
@@ -9941,18 +10853,23 @@ var AxlRuntime = class extends EventEmitter2 {
9941
10853
  */
9942
10854
  async runWorkflowBody(args) {
9943
10855
  const { workflow: workflow2, ctx, execInfo, validated, span, cleanupAbortForwarder } = args;
10856
+ let observation = execInfo.observation ?? { complete: true };
9944
10857
  try {
9945
10858
  ctx._emitWorkflowStart(validated);
9946
10859
  const raw = await workflow2.handler(ctx);
10860
+ const drainedObservation = await ctx._drainBranchWork();
10861
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
9947
10862
  const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
9948
10863
  execInfo.status = "completed";
9949
10864
  execInfo.completedAt = Date.now();
9950
10865
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9951
10866
  execInfo.result = result;
10867
+ execInfo.observation = observation;
9952
10868
  ctx._emitWorkflowEnd({
9953
10869
  status: "completed",
9954
10870
  duration: execInfo.duration,
9955
- result
10871
+ result,
10872
+ observation
9956
10873
  });
9957
10874
  if (this.stateStore.deleteCheckpoints) {
9958
10875
  await this.stateStore.deleteCheckpoints(execInfo.executionId);
@@ -9962,19 +10879,28 @@ var AxlRuntime = class extends EventEmitter2 {
9962
10879
  this.persistExecution(execInfo);
9963
10880
  return result;
9964
10881
  } catch (err) {
9965
- const aborted = isAbortError2(err);
10882
+ let terminalError = err;
10883
+ try {
10884
+ const drainedObservation = await ctx._drainBranchWork();
10885
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
10886
+ } catch (drainErr) {
10887
+ terminalError = drainErr instanceof Error ? preserveErrorCause(drainErr, terminalError) : drainErr;
10888
+ }
10889
+ const aborted = isAbortError4(terminalError);
9966
10890
  execInfo.status = "failed";
9967
10891
  execInfo.completedAt = Date.now();
9968
10892
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9969
- execInfo.error = err instanceof Error ? err.message : String(err);
10893
+ execInfo.error = terminalError instanceof Error ? terminalError.message : String(terminalError);
10894
+ execInfo.observation = observation;
9970
10895
  ctx._emitWorkflowEnd({
9971
10896
  status: "failed",
9972
10897
  duration: execInfo.duration,
9973
10898
  error: execInfo.error,
9974
- ...aborted ? { aborted: true } : {}
10899
+ ...aborted ? { aborted: true } : {},
10900
+ observation
9975
10901
  });
9976
10902
  this.persistExecution(execInfo);
9977
- throw err;
10903
+ throw terminalError;
9978
10904
  } finally {
9979
10905
  this.abortControllers.delete(execInfo.executionId);
9980
10906
  cleanupAbortForwarder?.();
@@ -10162,13 +11088,13 @@ var AxlRuntime = class extends EventEmitter2 {
10162
11088
  return result;
10163
11089
  }
10164
11090
  /** Get all execution info (running + completed + historical). */
10165
- /** Normalize an `ExecutionInfo` loaded from a `StateStore` so the
10166
- * `events: AxlEvent[]` type contract holds for downstream consumers
11091
+ /** Normalize an execution loaded from a `StateStore` so the
11092
+ * historical event union holds for downstream consumers
10167
11093
  * (Studio aggregators, REST routes, redaction). Custom or legacy stores
10168
11094
  * may persist rows without `events` (or with a non-array value) — without
10169
11095
  * this guard, a single malformed row crashes every iterator over
10170
- * `exec.events` and takes down dependent features. Mutates the row
10171
- * in place; logs at most one warning per `executionId`. */
11096
+ * `exec.events` and takes down dependent features. Returns a normalized
11097
+ * copy; logs at most one warning per `executionId`. */
10172
11098
  normalizeStoredExecution(exec) {
10173
11099
  if (!Array.isArray(exec.events)) {
10174
11100
  if (!this.warnedMalformedExecutions.has(exec.executionId)) {
@@ -10177,9 +11103,9 @@ var AxlRuntime = class extends EventEmitter2 {
10177
11103
  `[axl] StateStore returned execution ${exec.executionId} with non-array events (got ${exec.events === null ? "null" : typeof exec.events}); coercing to []`
10178
11104
  );
10179
11105
  }
10180
- exec.events = [];
11106
+ return normalizeStoredExecution({ ...exec, events: [] });
10181
11107
  }
10182
- return exec;
11108
+ return normalizeStoredExecution(exec);
10183
11109
  }
10184
11110
  async getExecutions() {
10185
11111
  if (!this.historicalExecutionsLoadPromise && this.stateStore.listExecutions) {
@@ -10189,8 +11115,9 @@ var AxlRuntime = class extends EventEmitter2 {
10189
11115
  this.historicalExecutions.set(exec.executionId, this.normalizeStoredExecution(exec));
10190
11116
  }
10191
11117
  }
10192
- }).catch(() => {
11118
+ }).catch((error) => {
10193
11119
  this.historicalExecutionsLoadPromise = null;
11120
+ throw error;
10194
11121
  });
10195
11122
  }
10196
11123
  if (this.historicalExecutionsLoadPromise) {
@@ -10208,6 +11135,7 @@ var AxlRuntime = class extends EventEmitter2 {
10208
11135
  * and automatically emits trace events and tracks cost.
10209
11136
  */
10210
11137
  createContext(options) {
11138
+ assertNoRemovedObservationCallbacks(options);
10211
11139
  const executionId = randomUUID3();
10212
11140
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
10213
11141
  this.registerWithCostScope(executionId);
@@ -10223,10 +11151,8 @@ var AxlRuntime = class extends EventEmitter2 {
10223
11151
  memoryManager: this.memoryManager,
10224
11152
  sessionHistory: options?.sessionHistory,
10225
11153
  signal: options?.signal,
10226
- onToken: options?.onToken,
10227
- onToolCall: options?.onToolCall,
10228
- onAgentStart: options?.onAgentStart,
10229
11154
  awaitHumanHandler: options?.awaitHumanHandler,
11155
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10230
11156
  eventStreamOptions: options?.events,
10231
11157
  onTrace: (event) => {
10232
11158
  this.emit("trace", event);
@@ -10260,6 +11186,7 @@ var AxlRuntime = class extends EventEmitter2 {
10260
11186
  );
10261
11187
  }
10262
11188
  const validated = workflow2.inputSchema.parse(input);
11189
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10263
11190
  const executionId = randomUUID3();
10264
11191
  const controller = new AbortController();
10265
11192
  this.abortControllers.set(executionId, controller);
@@ -10272,11 +11199,13 @@ var AxlRuntime = class extends EventEmitter2 {
10272
11199
  executionId,
10273
11200
  workflow: name,
10274
11201
  status: "running",
11202
+ eventSchemaVersion: 2,
10275
11203
  events: [],
10276
11204
  totalCost: 0,
10277
11205
  unpriced: false,
10278
11206
  startedAt: Date.now(),
10279
11207
  duration: 0,
11208
+ observation: { complete: true },
10280
11209
  metadata: liftPersistedMetadata(options?.metadata)
10281
11210
  };
10282
11211
  this.executions.set(executionId, execInfo);
@@ -10290,6 +11219,8 @@ var AxlRuntime = class extends EventEmitter2 {
10290
11219
  sessionHistory,
10291
11220
  signal: controller.signal,
10292
11221
  eventStreamOptions: options?.events,
11222
+ branchDrainTimeoutMs,
11223
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10293
11224
  onTrace: (event) => {
10294
11225
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10295
11226
  execInfo.totalCost += eventCostContribution(event);
@@ -10334,7 +11265,6 @@ var AxlRuntime = class extends EventEmitter2 {
10334
11265
  workflowName: name,
10335
11266
  mcpManager: this.mcpManager,
10336
11267
  memoryManager: this.memoryManager,
10337
- resumeMode: !!options?.metadata?.resumeMode,
10338
11268
  spanManager: this.spanManager,
10339
11269
  budgetContext: {
10340
11270
  totalCost: 0,
@@ -10365,6 +11295,7 @@ var AxlRuntime = class extends EventEmitter2 {
10365
11295
  }
10366
11296
  /** Execute a workflow and return a stream. */
10367
11297
  stream(name, input, options) {
11298
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10368
11299
  const axlStream = new AxlStream(options?.events);
10369
11300
  const controller = new AbortController();
10370
11301
  axlStream.on("close", () => controller.abort());
@@ -10385,11 +11316,13 @@ var AxlRuntime = class extends EventEmitter2 {
10385
11316
  executionId,
10386
11317
  workflow: name,
10387
11318
  status: "running",
11319
+ eventSchemaVersion: 2,
10388
11320
  events: [],
10389
11321
  totalCost: 0,
10390
11322
  unpriced: false,
10391
11323
  startedAt: Date.now(),
10392
11324
  duration: 0,
11325
+ observation: { complete: true },
10393
11326
  metadata: liftPersistedMetadata(options?.metadata)
10394
11327
  };
10395
11328
  this.executions.set(executionId, execInfo);
@@ -10402,12 +11335,11 @@ var AxlRuntime = class extends EventEmitter2 {
10402
11335
  sessionHistory,
10403
11336
  signal: controller.signal,
10404
11337
  eventStreamOptions: options?.events,
10405
- // Sentinel `onToken` so the streaming code path in WorkflowContext
10406
- // is entered (it's gated on `this.onToken` being defined). The
10407
- // actual `token` AxlEvents are emitted via `emitEvent` and reach
10408
- // the wire through `onTrace` — this callback is just the gate.
10409
- onToken: () => {
10410
- },
11338
+ branchDrainTimeoutMs,
11339
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
11340
+ // Transport mode is explicit internal state. It is inherited by child
11341
+ // contexts and does not allocate an unconsumed ctx.events queue.
11342
+ _forceStreaming: true,
10411
11343
  onTrace: (event) => {
10412
11344
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10413
11345
  execInfo.totalCost += eventCostContribution(event);
@@ -10453,7 +11385,6 @@ var AxlRuntime = class extends EventEmitter2 {
10453
11385
  workflowName: name,
10454
11386
  mcpManager: this.mcpManager,
10455
11387
  memoryManager: this.memoryManager,
10456
- resumeMode: !!options?.metadata?.resumeMode,
10457
11388
  spanManager: this.spanManager,
10458
11389
  budgetContext: {
10459
11390
  totalCost: 0,
@@ -10617,6 +11548,12 @@ var AxlRuntime = class extends EventEmitter2 {
10617
11548
  await this.stateStore.finalizeStreamingEvents?.(id);
10618
11549
  continue;
10619
11550
  }
11551
+ const eventSchemaVersion = getEventSchemaVersion(events[0]);
11552
+ for (const event of events) {
11553
+ if (getEventSchemaVersion(event) !== eventSchemaVersion) {
11554
+ throw new Error(`Streaming buffer ${id} contains mixed event schema versions`);
11555
+ }
11556
+ }
10620
11557
  const boundedEvents = boundRecoveredEvents(events, this.maxEventsPerExecution);
10621
11558
  const workflowStartEvent = boundedEvents.find((e) => e.type === "workflow_start");
10622
11559
  const lastEvent = boundedEvents[boundedEvents.length - 1];
@@ -10625,7 +11562,7 @@ var AxlRuntime = class extends EventEmitter2 {
10625
11562
  const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
10626
11563
  const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
10627
11564
  const unpriced = boundedEvents.some(isUnpricedLeaf);
10628
- const synthesized = {
11565
+ const executionBase = {
10629
11566
  executionId: id,
10630
11567
  workflow: workflowName,
10631
11568
  status: "failed",
@@ -10637,6 +11574,16 @@ var AxlRuntime = class extends EventEmitter2 {
10637
11574
  duration: completedAt - startedAt,
10638
11575
  error: "process terminated (recovered from streaming buffer)"
10639
11576
  };
11577
+ const synthesized = eventSchemaVersion === 2 ? {
11578
+ ...executionBase,
11579
+ eventSchemaVersion: 2,
11580
+ events: boundedEvents,
11581
+ observation: { complete: false, reason: "process_interrupted" }
11582
+ } : {
11583
+ ...executionBase,
11584
+ eventSchemaVersion: 1,
11585
+ events: boundedEvents
11586
+ };
10640
11587
  if (this.stateStore.saveExecution) {
10641
11588
  try {
10642
11589
  await this.stateStore.saveExecution(synthesized);
@@ -10717,7 +11664,8 @@ var AxlRuntime = class extends EventEmitter2 {
10717
11664
  */
10718
11665
  async deleteExecution(id) {
10719
11666
  const wasActive = this.executions.has(id);
10720
- const hadPendingDecision = this.pendingDecisionResolvers.has(id);
11667
+ const pendingDecisionResolver = this.pendingDecisionResolvers.get(id);
11668
+ const hadPendingDecision = pendingDecisionResolver !== void 0;
10721
11669
  if (wasActive) {
10722
11670
  this.pendingDeletedExecutions.add(id);
10723
11671
  this.streamableExecutionIds.delete(id);
@@ -10726,8 +11674,12 @@ var AxlRuntime = class extends EventEmitter2 {
10726
11674
  controller.abort();
10727
11675
  }
10728
11676
  }
10729
- if (hadPendingDecision) {
10730
- this.pendingDecisionResolvers.delete(id);
11677
+ if (pendingDecisionResolver) pendingDecisionResolver.cancelled = true;
11678
+ if (pendingDecisionResolver) {
11679
+ await pendingDecisionResolver.cleanupFinished;
11680
+ if (this.pendingDecisionResolvers.get(id) === pendingDecisionResolver) {
11681
+ this.pendingDecisionResolvers.delete(id);
11682
+ }
10731
11683
  }
10732
11684
  const existing = await this.getExecution(id);
10733
11685
  const workflow2 = existing?.workflow;
@@ -10805,60 +11757,68 @@ var AxlRuntime = class extends EventEmitter2 {
10805
11757
  }
10806
11758
  /** Resolve a pending human decision. */
10807
11759
  async resolveDecision(executionId, decision) {
10808
- const resolver = this.pendingDecisionResolvers.get(executionId);
11760
+ decision = parseHumanDecision(decision);
11761
+ let resolver = this.pendingDecisionResolvers.get(executionId);
10809
11762
  if (resolver) {
10810
- resolver(decision);
10811
- this.pendingDecisionResolvers.delete(executionId);
11763
+ if (resolver.cancelled) {
11764
+ throw new AxlError(
11765
+ "PENDING_DECISION_NOT_FOUND",
11766
+ `The pending human decision for execution "${executionId}" is no longer active.`
11767
+ );
11768
+ }
11769
+ await resolver.ready;
11770
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11771
+ throw new AxlError(
11772
+ "PENDING_DECISION_NOT_FOUND",
11773
+ `The pending human decision for execution "${executionId}" is no longer active.`
11774
+ );
11775
+ }
10812
11776
  }
10813
- await this.stateStore.resolveDecision(executionId, decision);
10814
11777
  if (!resolver) {
10815
- const state = await this.stateStore.getExecutionState(executionId);
10816
- if (state && state.status === "waiting") {
10817
- await this.resumeExecution(executionId);
11778
+ const pending = await this.stateStore.getPendingDecisions();
11779
+ resolver = this.pendingDecisionResolvers.get(executionId);
11780
+ if (resolver) {
11781
+ if (resolver.cancelled) {
11782
+ throw new AxlError(
11783
+ "PENDING_DECISION_NOT_FOUND",
11784
+ `The pending human decision for execution "${executionId}" is no longer active.`
11785
+ );
11786
+ }
11787
+ await resolver.ready;
11788
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11789
+ throw new AxlError(
11790
+ "PENDING_DECISION_NOT_FOUND",
11791
+ `The pending human decision for execution "${executionId}" is no longer active.`
11792
+ );
11793
+ }
11794
+ } else if (!pending.some((request) => request.executionId === executionId)) {
11795
+ throw new AxlError(
11796
+ "PENDING_DECISION_NOT_FOUND",
11797
+ `No pending human decision exists for execution "${executionId}".`
11798
+ );
11799
+ } else {
11800
+ throw new AxlError(
11801
+ "CROSS_PROCESS_RESUME_UNSUPPORTED",
11802
+ `Execution "${executionId}" is not awaiting a decision in this runtime. Cross-process approval replay requires a durable decision/lease lineage and is not yet supported; any persisted pending request was preserved.`
11803
+ );
10818
11804
  }
10819
11805
  }
10820
- }
10821
- /**
10822
- * Resume a specific execution that was waiting for a human decision.
10823
- * Re-runs the workflow from scratch; the workflow should use checkpoint-replay
10824
- * to skip already-completed steps and pick up from the awaitHuman point.
10825
- */
10826
- async resumeExecution(executionId) {
10827
- const state = await this.stateStore.getExecutionState(executionId);
10828
- if (!state) {
10829
- throw new Error(`No execution state found for "${executionId}"`);
10830
- }
10831
- const workflow2 = this.workflows.get(state.workflow);
10832
- if (!workflow2) {
10833
- throw new Error(
10834
- `Workflow "${state.workflow}" not registered. Cannot resume execution "${executionId}".`
10835
- );
10836
- }
10837
- return this.execute(state.workflow, state.input, {
10838
- metadata: { ...state.metadata, resumedFrom: executionId, resumeMode: true }
11806
+ let finishResolution;
11807
+ const resolutionFinished = new Promise((resolve2) => {
11808
+ finishResolution = resolve2;
10839
11809
  });
10840
- }
10841
- /**
10842
- * Resume all pending executions that were waiting for human decisions.
10843
- * Call this on startup to resume workflows that were interrupted by a restart.
10844
- * Returns the execution IDs that were resumed.
10845
- */
10846
- async resumePending() {
10847
- const pendingIds = await this.stateStore.listPendingExecutions();
10848
- const resumed = [];
10849
- for (const executionId of pendingIds) {
10850
- try {
10851
- await this.resumeExecution(executionId);
10852
- resumed.push(executionId);
10853
- } catch (err) {
10854
- this.emit("error", {
10855
- type: "resume_failed",
10856
- executionId,
10857
- error: err instanceof Error ? err.message : String(err)
10858
- });
10859
- }
11810
+ resolver.state = "resolving";
11811
+ resolver.resolution = resolutionFinished;
11812
+ try {
11813
+ await this.stateStore.resolveDecision(executionId, decision);
11814
+ resolver.state = "resolved";
11815
+ finishResolution(true);
11816
+ resolver.resolve(decision);
11817
+ } catch (error) {
11818
+ resolver.state = "pending";
11819
+ finishResolution(false);
11820
+ throw error;
10860
11821
  }
10861
- return resumed;
10862
11822
  }
10863
11823
  /**
10864
11824
  * Summarize a list of chat messages into a concise summary string.
@@ -11760,28 +12720,32 @@ var RedisStore = class _RedisStore {
11760
12720
  async saveExecution(execution) {
11761
12721
  const ttl = this.ttlFor("executionHistory");
11762
12722
  const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
11763
- await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(execution), setOptions).zAdd(this.execHistoryZsetKey(), {
12723
+ const storedExecution = normalizeStoredExecution({
12724
+ ...execution,
12725
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
12726
+ });
12727
+ await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(storedExecution), setOptions).zAdd(this.execHistoryZsetKey(), {
11764
12728
  score: execution.startedAt,
11765
12729
  value: execution.executionId
11766
12730
  }).exec();
11767
12731
  }
11768
12732
  async getExecution(executionId) {
11769
12733
  const raw = await this.client.get(this.execHistoryKey(executionId));
11770
- return raw ? JSON.parse(raw) : null;
12734
+ return raw ? normalizeStoredExecution(JSON.parse(raw)) : null;
11771
12735
  }
11772
12736
  async listExecutions(limit) {
11773
- return this.listHistoryByZset(
12737
+ const executions = await this.listHistoryByZset(
11774
12738
  this.execHistorySetKey(),
11775
12739
  this.execHistoryZsetKey(),
11776
12740
  (id) => this.execHistoryKey(id),
11777
12741
  (a, b) => b.startedAt - a.startedAt,
11778
12742
  limit
11779
12743
  );
12744
+ return executions.map(normalizeStoredExecution);
11780
12745
  }
11781
12746
  async deleteExecution(executionId) {
11782
- const [, deletedCount] = await this.client.multi().sRem(this.execHistorySetKey(), executionId).del(this.execHistoryKey(executionId)).zRem(this.execHistoryZsetKey(), executionId).del(this.checkpointKey(executionId)).del(this.executionStateKey(executionId)).sRem(this.pendingExecSetKey(), executionId).del(this.streamingEventsKey(executionId)).sRem(this.streamingIdsKey(), executionId).hDel(this.decisionsKey(), executionId).exec();
11783
- const deleted = typeof deletedCount === "number" ? deletedCount : 0;
11784
- return deleted > 0;
12747
+ const results = await this.client.multi().sRem(this.execHistorySetKey(), executionId).del(this.execHistoryKey(executionId)).zRem(this.execHistoryZsetKey(), executionId).del(this.checkpointKey(executionId)).del(this.executionStateKey(executionId)).sRem(this.pendingExecSetKey(), executionId).del(this.streamingEventsKey(executionId)).sRem(this.streamingIdsKey(), executionId).hDel(this.decisionsKey(), executionId).exec();
12748
+ return results.some((result) => typeof result === "number" && result > 0);
11785
12749
  }
11786
12750
  // ── Eval History ────────────────────────────────────────────────────
11787
12751
  async saveEvalResult(entry) {
@@ -11873,14 +12837,15 @@ var RedisStore = class _RedisStore {
11873
12837
  async getStreamingEvents(executionId) {
11874
12838
  const raws = await this.client.lRange(this.streamingEventsKey(executionId), 0, -1);
11875
12839
  if (raws.length === 0) return [];
11876
- const events = [];
11877
- for (const raw of raws) {
12840
+ return raws.map((raw, index) => {
11878
12841
  try {
11879
- events.push(JSON.parse(raw));
11880
- } catch {
12842
+ return JSON.parse(raw);
12843
+ } catch (error) {
12844
+ throw new Error(`Streaming buffer ${executionId} contains invalid JSON at index ${index}`, {
12845
+ cause: error
12846
+ });
11881
12847
  }
11882
- }
11883
- return events;
12848
+ });
11884
12849
  }
11885
12850
  /** Close the Redis connection. */
11886
12851
  async close() {
@@ -12091,6 +13056,8 @@ function cosineSimilarity2(a, b) {
12091
13056
  }
12092
13057
  export {
12093
13058
  AXL_EVENT_TYPES,
13059
+ AXL_EVENT_TYPES_V2,
13060
+ AXL_TOOL_LIFECYCLE_TYPES_V2,
12094
13061
  AZURE_PROFILE,
12095
13062
  AnthropicProvider,
12096
13063
  AxlError,
@@ -12138,8 +13105,9 @@ export {
12138
13105
  SqliteVectorStore,
12139
13106
  ThinkTagScanner,
12140
13107
  TimeoutError,
12141
- ToolDenied,
13108
+ ToolFailure,
12142
13109
  ToolModelOutputError,
13110
+ UnsupportedEventSchemaVersionError,
12143
13111
  VLLM_PROFILE,
12144
13112
  ValidationError,
12145
13113
  VerifyError,
@@ -12154,6 +13122,8 @@ export {
12154
13122
  extractJson,
12155
13123
  extractThinkTags,
12156
13124
  findConfig,
13125
+ getEventSchemaVersion,
13126
+ getExecutionEventSchemaVersion,
12157
13127
  hasPositiveTokens,
12158
13128
  importModule,
12159
13129
  isCostBearingLeaf,
@@ -12162,6 +13132,7 @@ export {
12162
13132
  isUnpricedLeaf,
12163
13133
  isUsableCost,
12164
13134
  needsTsxLoader,
13135
+ normalizeStoredExecution,
12165
13136
  openaiReasoningEmit,
12166
13137
  parsePartialJson,
12167
13138
  pickDefault,
@@ -12170,6 +13141,7 @@ export {
12170
13141
  reasoningEffortEmit,
12171
13142
  reasoningObjectEmit,
12172
13143
  redactEvent,
13144
+ redactHistoricalEvent,
12173
13145
  registerConditions,
12174
13146
  resolveApiKey,
12175
13147
  resolvePerModel,