@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.cjs CHANGED
@@ -65,6 +65,7 @@ var init_span_manager = __esm({
65
65
  async withSpanAsync(name, attributes, fn) {
66
66
  const otelApi = this.otelApi;
67
67
  return this.tracer.startActiveSpan(name, { attributes }, async (otelSpan) => {
68
+ let statusWasSet = false;
68
69
  const handle = {
69
70
  setAttribute(key, value) {
70
71
  otelSpan.setAttribute(key, value);
@@ -73,7 +74,8 @@ var init_span_manager = __esm({
73
74
  otelSpan.addEvent(eventName, attrs);
74
75
  },
75
76
  setStatus(code, message) {
76
- const statusCode = code === "ok" ? otelApi.SpanStatusCode.OK : otelApi.SpanStatusCode.ERROR;
77
+ statusWasSet = true;
78
+ const statusCode = code === "ok" ? otelApi.SpanStatusCode.OK : code === "error" ? otelApi.SpanStatusCode.ERROR : otelApi.SpanStatusCode.UNSET;
77
79
  otelSpan.setStatus({ code: statusCode, message });
78
80
  },
79
81
  end() {
@@ -82,10 +84,10 @@ var init_span_manager = __esm({
82
84
  };
83
85
  try {
84
86
  const result = await fn(handle);
85
- handle.setStatus("ok");
87
+ if (!statusWasSet) handle.setStatus("ok");
86
88
  return result;
87
89
  } catch (err) {
88
- handle.setStatus("error", err instanceof Error ? err.message : String(err));
90
+ handle.setStatus("error");
89
91
  throw err;
90
92
  } finally {
91
93
  handle.end();
@@ -111,6 +113,8 @@ var init_span_manager = __esm({
111
113
  var index_exports = {};
112
114
  __export(index_exports, {
113
115
  AXL_EVENT_TYPES: () => AXL_EVENT_TYPES,
116
+ AXL_EVENT_TYPES_V2: () => AXL_EVENT_TYPES_V2,
117
+ AXL_TOOL_LIFECYCLE_TYPES_V2: () => AXL_TOOL_LIFECYCLE_TYPES_V2,
114
118
  AZURE_PROFILE: () => AZURE_PROFILE,
115
119
  AnthropicProvider: () => AnthropicProvider,
116
120
  AxlError: () => AxlError,
@@ -158,8 +162,9 @@ __export(index_exports, {
158
162
  SqliteVectorStore: () => SqliteVectorStore,
159
163
  ThinkTagScanner: () => ThinkTagScanner,
160
164
  TimeoutError: () => TimeoutError,
161
- ToolDenied: () => ToolDenied,
165
+ ToolFailure: () => ToolFailure,
162
166
  ToolModelOutputError: () => ToolModelOutputError,
167
+ UnsupportedEventSchemaVersionError: () => UnsupportedEventSchemaVersionError,
163
168
  VLLM_PROFILE: () => VLLM_PROFILE,
164
169
  ValidationError: () => ValidationError,
165
170
  VerifyError: () => VerifyError,
@@ -174,6 +179,8 @@ __export(index_exports, {
174
179
  extractJson: () => extractJson,
175
180
  extractThinkTags: () => extractThinkTags,
176
181
  findConfig: () => findConfig,
182
+ getEventSchemaVersion: () => getEventSchemaVersion,
183
+ getExecutionEventSchemaVersion: () => getExecutionEventSchemaVersion,
177
184
  hasPositiveTokens: () => hasPositiveTokens,
178
185
  importModule: () => importModule,
179
186
  isCostBearingLeaf: () => isCostBearingLeaf,
@@ -182,6 +189,7 @@ __export(index_exports, {
182
189
  isUnpricedLeaf: () => isUnpricedLeaf,
183
190
  isUsableCost: () => isUsableCost,
184
191
  needsTsxLoader: () => needsTsxLoader,
192
+ normalizeStoredExecution: () => normalizeStoredExecution,
185
193
  openaiReasoningEmit: () => openaiReasoningEmit,
186
194
  parsePartialJson: () => parsePartialJson,
187
195
  pickDefault: () => pickDefault,
@@ -190,6 +198,7 @@ __export(index_exports, {
190
198
  reasoningEffortEmit: () => reasoningEffortEmit,
191
199
  reasoningObjectEmit: () => reasoningObjectEmit,
192
200
  redactEvent: () => redactEvent,
201
+ redactHistoricalEvent: () => redactHistoricalEvent,
193
202
  registerConditions: () => registerConditions,
194
203
  resolveApiKey: () => resolveApiKey,
195
204
  resolvePerModel: () => resolvePerModel,
@@ -203,128 +212,7 @@ __export(index_exports, {
203
212
  module.exports = __toCommonJS(index_exports);
204
213
 
205
214
  // src/tool.ts
206
- var DEFAULT_MAX_STRING_LENGTH = 1e4;
207
- function validateStringLengths(value, maxLen, path = "") {
208
- if (typeof value === "string") {
209
- if (value.length > maxLen) {
210
- throw new Error(
211
- `String argument${path ? ` at "${path}"` : ""} exceeds maximum length (${value.length} > ${maxLen})`
212
- );
213
- }
214
- } else if (Array.isArray(value)) {
215
- for (let i = 0; i < value.length; i++) {
216
- validateStringLengths(value[i], maxLen, path ? `${path}[${i}]` : `[${i}]`);
217
- }
218
- } else if (value !== null && typeof value === "object") {
219
- for (const [key, val] of Object.entries(value)) {
220
- validateStringLengths(val, maxLen, path ? `${path}.${key}` : key);
221
- }
222
- }
223
- }
224
- function sleep(ms) {
225
- return new Promise((resolve2) => setTimeout(resolve2, ms));
226
- }
227
- function getBackoffMs(attempt, strategy) {
228
- switch (strategy) {
229
- case "none":
230
- return 0;
231
- case "linear":
232
- return attempt * 1e3;
233
- case "exponential":
234
- return Math.pow(2, attempt - 1) * 1e3;
235
- }
236
- }
237
- function tool(config) {
238
- const retryPolicy = {
239
- attempts: config.retry?.attempts ?? 1,
240
- backoff: config.retry?.backoff ?? "exponential",
241
- on: config.retry?.on
242
- };
243
- const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
244
- const execute = async (input, ctx) => {
245
- const parsed = config.input.parse(input);
246
- if (maxStringLen > 0) {
247
- validateStringLengths(parsed, maxStringLen);
248
- }
249
- const maxAttempts = retryPolicy.attempts ?? 1;
250
- let lastError;
251
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
252
- try {
253
- return await config.handler(parsed, ctx);
254
- } catch (err) {
255
- lastError = err instanceof Error ? err : new Error(String(err));
256
- if (attempt === maxAttempts) break;
257
- if (retryPolicy.on && !retryPolicy.on(lastError)) {
258
- break;
259
- }
260
- const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
261
- if (backoffMs > 0) {
262
- await sleep(backoffMs);
263
- }
264
- }
265
- }
266
- throw lastError;
267
- };
268
- return {
269
- name: config.name,
270
- description: config.description,
271
- inputSchema: config.input,
272
- sensitive: config.sensitive ?? false,
273
- retry: retryPolicy,
274
- requireApproval: config.requireApproval ?? false,
275
- hooks: config.hooks,
276
- ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
277
- async run(ctx, input) {
278
- const startTime = Date.now();
279
- try {
280
- let processedInput = input;
281
- if (config.hooks?.before) {
282
- processedInput = await config.hooks.before(processedInput, ctx);
283
- }
284
- let result = await execute(processedInput, ctx);
285
- if (config.hooks?.after) {
286
- result = await config.hooks.after(result, ctx);
287
- }
288
- ctx.log("tool_call_complete", {
289
- tool: config.name,
290
- duration: Date.now() - startTime
291
- });
292
- return result;
293
- } catch (err) {
294
- ctx.log("tool_call_error", {
295
- tool: config.name,
296
- error: err instanceof Error ? err.message : String(err),
297
- duration: Date.now() - startTime
298
- });
299
- throw err;
300
- }
301
- },
302
- _execute: execute
303
- };
304
- }
305
-
306
- // src/providers/types.ts
307
- function resolveThinkingOptions(options) {
308
- if (options.thinkingBudget !== void 0 && options.thinkingBudget < 0) {
309
- throw new Error(`thinkingBudget must be non-negative, got ${options.thinkingBudget}`);
310
- }
311
- const effort = options.effort;
312
- const thinkingBudget = options.thinkingBudget;
313
- const hasBudgetOverride = thinkingBudget !== void 0 && thinkingBudget > 0;
314
- return {
315
- effort,
316
- thinkingBudget,
317
- includeThoughts: options.includeThoughts ?? false,
318
- // Budget override wins: effort: 'none' + thinkingBudget: 5000 → thinking enabled
319
- thinkingDisabled: (effort === "none" || thinkingBudget === 0) && !hasBudgetOverride,
320
- activeEffort: effort && effort !== "none" ? effort : void 0,
321
- hasBudgetOverride
322
- };
323
- }
324
- async function resolveApiKey(source) {
325
- if (typeof source === "function") return await source() ?? "";
326
- return source ?? "";
327
- }
215
+ var import_zod = require("zod");
328
216
 
329
217
  // src/errors.ts
330
218
  var AxlError = class extends Error {
@@ -335,6 +223,55 @@ var AxlError = class extends Error {
335
223
  this.code = code;
336
224
  }
337
225
  };
226
+ var EventStreamOverflowError = class extends Error {
227
+ maxQueued;
228
+ eventType;
229
+ constructor(maxQueued, eventType, cause) {
230
+ super(
231
+ `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.`
232
+ );
233
+ this.name = "EventStreamOverflowError";
234
+ this.maxQueued = maxQueued;
235
+ this.eventType = eventType;
236
+ if (cause !== void 0) {
237
+ Object.defineProperty(this, "cause", {
238
+ configurable: true,
239
+ enumerable: false,
240
+ value: cause
241
+ });
242
+ }
243
+ }
244
+ };
245
+ function isEventStreamOverflowError(error) {
246
+ return error instanceof EventStreamOverflowError;
247
+ }
248
+ function rethrowEventStreamOverflow(error) {
249
+ if (isEventStreamOverflowError(error)) throw error;
250
+ }
251
+ function preserveErrorCause(error, cause) {
252
+ if (cause === void 0 || cause === error || "cause" in error) return error;
253
+ Object.defineProperty(error, "cause", {
254
+ configurable: true,
255
+ enumerable: false,
256
+ value: cause
257
+ });
258
+ return error;
259
+ }
260
+ var ToolFailure = class extends AxlError {
261
+ modelMessage;
262
+ constructor(options) {
263
+ super(options.code ?? "TOOL_FAILURE", options.message);
264
+ this.name = "ToolFailure";
265
+ this.modelMessage = options.modelMessage;
266
+ if (options.cause !== void 0) {
267
+ Object.defineProperty(this, "cause", {
268
+ configurable: true,
269
+ enumerable: false,
270
+ value: options.cause
271
+ });
272
+ }
273
+ }
274
+ };
338
275
  var ToolModelOutputError = class extends AxlError {
339
276
  toolName;
340
277
  cause;
@@ -440,19 +377,209 @@ var ValidationError = class extends AxlError {
440
377
  this.retries = retries;
441
378
  }
442
379
  };
443
- var ToolDenied = class extends AxlError {
444
- toolName;
445
- agentName;
446
- constructor(toolName, agentName) {
380
+
381
+ // src/tool.ts
382
+ var DEFAULT_MAX_STRING_LENGTH = 1e4;
383
+ var ToolStringLengthError = class extends Error {
384
+ constructor(path, actual, maximum) {
385
+ const renderedPath = path.reduce(
386
+ (current, part) => typeof part === "number" ? `${current}[${part}]` : current ? `${current}.${part}` : part,
387
+ ""
388
+ );
447
389
  super(
448
- "TOOL_DENIED",
449
- `Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
390
+ `String argument${renderedPath ? ` at "${renderedPath}"` : ""} exceeds maximum length (${actual} > ${maximum})`
450
391
  );
451
- this.name = "ToolDenied";
452
- this.toolName = toolName;
453
- this.agentName = agentName;
392
+ this.path = path;
393
+ this.actual = actual;
394
+ this.maximum = maximum;
395
+ this.name = "ToolStringLengthError";
454
396
  }
455
397
  };
398
+ function validateStringLengths(value, maxLen, path = []) {
399
+ if (typeof value === "string") {
400
+ if (value.length > maxLen) {
401
+ throw new ToolStringLengthError(path, value.length, maxLen);
402
+ }
403
+ } else if (Array.isArray(value)) {
404
+ for (let i = 0; i < value.length; i++) {
405
+ validateStringLengths(value[i], maxLen, [...path, i]);
406
+ }
407
+ } else if (value !== null && typeof value === "object") {
408
+ for (const [key, val] of Object.entries(value)) {
409
+ validateStringLengths(val, maxLen, [...path, key]);
410
+ }
411
+ }
412
+ }
413
+ function sleep(ms, signal) {
414
+ if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
415
+ signal.throwIfAborted();
416
+ return new Promise((resolve2, reject) => {
417
+ const timeout = setTimeout(() => {
418
+ signal.removeEventListener("abort", onAbort);
419
+ resolve2();
420
+ }, ms);
421
+ const onAbort = () => {
422
+ clearTimeout(timeout);
423
+ reject(signal.reason);
424
+ };
425
+ signal.addEventListener("abort", onAbort, { once: true });
426
+ });
427
+ }
428
+ function isAbortError(error) {
429
+ try {
430
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
431
+ } catch {
432
+ return false;
433
+ }
434
+ }
435
+ function toolArgumentIssues(error) {
436
+ if (error instanceof import_zod.ZodError) {
437
+ return error.issues.map((issue) => ({
438
+ path: issue.path.map((part) => typeof part === "symbol" ? String(part) : part),
439
+ code: issue.code,
440
+ message: issue.message
441
+ }));
442
+ }
443
+ if (error instanceof ToolStringLengthError) {
444
+ return [
445
+ {
446
+ path: error.path,
447
+ code: "too_big",
448
+ message: error.message
449
+ }
450
+ ];
451
+ }
452
+ return [{ path: [], code: "invalid_arguments", message: "Tool arguments are invalid." }];
453
+ }
454
+ var TOOL_INTERNALS = /* @__PURE__ */ new WeakMap();
455
+ function getToolInternals(tool2) {
456
+ const internals = TOOL_INTERNALS.get(tool2);
457
+ if (!internals) throw new Error(`Tool "${tool2.name}" was not created by tool()`);
458
+ return internals;
459
+ }
460
+ function prepareToolInput(tool2, input) {
461
+ return getToolInternals(tool2).prepareInput(input);
462
+ }
463
+ function executePreparedTool(tool2, input, ctx, options) {
464
+ return getToolInternals(tool2).executePrepared(input, ctx, options);
465
+ }
466
+ function getBackoffMs(attempt, strategy) {
467
+ switch (strategy) {
468
+ case "none":
469
+ return 0;
470
+ case "linear":
471
+ return attempt * 1e3;
472
+ case "exponential":
473
+ return Math.pow(2, attempt - 1) * 1e3;
474
+ }
475
+ }
476
+ function tool(config) {
477
+ const retryPolicy = {
478
+ attempts: config.retry?.attempts ?? 1,
479
+ backoff: config.retry?.backoff ?? "exponential",
480
+ on: config.retry?.on
481
+ };
482
+ const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
483
+ const prepareInput = (input) => {
484
+ const parsed = config.input.parse(input);
485
+ if (maxStringLen > 0) {
486
+ validateStringLengths(parsed, maxStringLen);
487
+ }
488
+ return parsed;
489
+ };
490
+ const executePrepared = async (parsed, ctx, options) => {
491
+ const maxAttempts = retryPolicy.attempts ?? 1;
492
+ let lastError;
493
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
494
+ options?.signal?.throwIfAborted();
495
+ options?.onAttempt?.(attempt);
496
+ try {
497
+ const result = await config.handler(parsed, ctx);
498
+ if (options?.checkAfterHandlerAbort !== false) options?.signal?.throwIfAborted();
499
+ return result;
500
+ } catch (err) {
501
+ if (options?.signal?.aborted) options.signal.throwIfAborted();
502
+ if (isAbortError(err)) throw err;
503
+ rethrowEventStreamOverflow(err);
504
+ lastError = err instanceof Error ? err : new Error(String(err));
505
+ if (attempt === maxAttempts) break;
506
+ if (retryPolicy.on && !retryPolicy.on(lastError)) {
507
+ break;
508
+ }
509
+ const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
510
+ if (backoffMs > 0) {
511
+ await sleep(backoffMs, options?.signal);
512
+ }
513
+ }
514
+ }
515
+ throw lastError;
516
+ };
517
+ const execute = async (input, ctx) => executePrepared(prepareInput(input), ctx);
518
+ const instance = {
519
+ name: config.name,
520
+ description: config.description,
521
+ inputSchema: config.input,
522
+ sensitive: config.sensitive ?? false,
523
+ retry: retryPolicy,
524
+ requireApproval: config.requireApproval ?? false,
525
+ hooks: config.hooks,
526
+ ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
527
+ async run(ctx, input) {
528
+ const startTime = Date.now();
529
+ try {
530
+ let processedInput = input;
531
+ if (config.hooks?.before) {
532
+ processedInput = await config.hooks.before(processedInput, ctx);
533
+ }
534
+ let result = await execute(processedInput, ctx);
535
+ if (config.hooks?.after) {
536
+ result = await config.hooks.after(result, ctx);
537
+ }
538
+ ctx.log("tool_call_complete", {
539
+ tool: config.name,
540
+ duration: Date.now() - startTime
541
+ });
542
+ return result;
543
+ } catch (err) {
544
+ ctx.log("tool_call_error", {
545
+ tool: config.name,
546
+ error: err instanceof Error ? err.message : String(err),
547
+ duration: Date.now() - startTime
548
+ });
549
+ throw err;
550
+ }
551
+ },
552
+ _execute: execute
553
+ };
554
+ TOOL_INTERNALS.set(instance, {
555
+ prepareInput,
556
+ executePrepared
557
+ });
558
+ return instance;
559
+ }
560
+
561
+ // src/providers/types.ts
562
+ function resolveThinkingOptions(options) {
563
+ if (options.thinkingBudget !== void 0 && options.thinkingBudget < 0) {
564
+ throw new Error(`thinkingBudget must be non-negative, got ${options.thinkingBudget}`);
565
+ }
566
+ const effort = options.effort;
567
+ const thinkingBudget = options.thinkingBudget;
568
+ const hasBudgetOverride = thinkingBudget !== void 0 && thinkingBudget > 0;
569
+ return {
570
+ effort,
571
+ thinkingBudget,
572
+ includeThoughts: options.includeThoughts ?? false,
573
+ // Budget override wins: effort: 'none' + thinkingBudget: 5000 → thinking enabled
574
+ thinkingDisabled: (effort === "none" || thinkingBudget === 0) && !hasBudgetOverride,
575
+ activeEffort: effort && effort !== "none" ? effort : void 0,
576
+ hasBudgetOverride
577
+ };
578
+ }
579
+ async function resolveApiKey(source) {
580
+ if (typeof source === "function") return await source() ?? "";
581
+ return source ?? "";
582
+ }
456
583
 
457
584
  // src/providers/errors.ts
458
585
  var ProviderError = class extends AxlError {
@@ -561,7 +688,7 @@ function sleep2(ms, signal) {
561
688
  );
562
689
  });
563
690
  }
564
- function isAbortError(err, signal) {
691
+ function isAbortError2(err, signal) {
565
692
  if (signal?.aborted) return true;
566
693
  return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
567
694
  }
@@ -580,7 +707,7 @@ async function fetchWithRetry(input, init, opts) {
580
707
  try {
581
708
  res = await fetch(input, init);
582
709
  } catch (err) {
583
- if (isAbortError(err, init?.signal ?? void 0)) throw err;
710
+ if (isAbortError2(err, init?.signal ?? void 0)) throw err;
584
711
  if (attempt >= maxRetries) {
585
712
  throw buildProviderError({
586
713
  provider,
@@ -3262,7 +3389,7 @@ var defaultRegistry = new ProviderRegistry();
3262
3389
  // src/context.ts
3263
3390
  var import_node_async_hooks = require("async_hooks");
3264
3391
  var import_node_crypto = require("crypto");
3265
- var import_zod = require("zod");
3392
+ var import_zod2 = require("zod");
3266
3393
 
3267
3394
  // src/partial-json.ts
3268
3395
  var MAX_DEPTH = 256;
@@ -3881,9 +4008,9 @@ var AXL_EVENT_TYPES = [
3881
4008
  // Tool invocation lifecycle
3882
4009
  "tool_call_start",
3883
4010
  "tool_call_end",
4011
+ "tool_call_rejected",
3884
4012
  // Single-point tool events
3885
4013
  "tool_approval",
3886
- "tool_denied",
3887
4014
  // Delegation
3888
4015
  "delegate",
3889
4016
  // Handoff (spans two asks; not AskScoped).
@@ -3914,7 +4041,7 @@ var AXL_EVENT_TYPES = [
3914
4041
  "memory_remember",
3915
4042
  "memory_recall",
3916
4043
  "memory_forget",
3917
- // Durable execution checkpoints (`ctx.checkpoint`)
4044
+ // Scoped execution checkpoints (`ctx.checkpoint`)
3918
4045
  "checkpoint_save",
3919
4046
  "checkpoint_replay",
3920
4047
  // Human-in-the-loop (`ctx.awaitHuman`)
@@ -3929,6 +4056,12 @@ var AXL_EVENT_TYPES = [
3929
4056
  "done",
3930
4057
  "error"
3931
4058
  ];
4059
+ var AXL_EVENT_TYPES_V2 = AXL_EVENT_TYPES;
4060
+ var AXL_TOOL_LIFECYCLE_TYPES_V2 = [
4061
+ "tool_call_start",
4062
+ "tool_call_end",
4063
+ "tool_call_rejected"
4064
+ ];
3932
4065
 
3933
4066
  // src/event-stream.ts
3934
4067
  var STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
@@ -3944,8 +4077,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3944
4077
  "agent_call_end",
3945
4078
  "tool_call_start",
3946
4079
  "tool_call_end",
4080
+ "tool_call_rejected",
3947
4081
  "tool_approval",
3948
- "tool_denied",
3949
4082
  "handoff_start",
3950
4083
  "handoff_return",
3951
4084
  "delegate",
@@ -3959,18 +4092,6 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3959
4092
  "await_human",
3960
4093
  "await_human_resolved"
3961
4094
  ]);
3962
- var EventStreamOverflowError = class extends Error {
3963
- maxQueued;
3964
- eventType;
3965
- constructor(maxQueued, eventType) {
3966
- super(
3967
- `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.`
3968
- );
3969
- this.name = "EventStreamOverflowError";
3970
- this.maxQueued = maxQueued;
3971
- this.eventType = eventType;
3972
- }
3973
- };
3974
4095
  var DEFAULT_MAX_QUEUED = 1e4;
3975
4096
  var DEFAULT_OVERFLOW = "drop-oldest-non-terminal";
3976
4097
  var AxlEventBus = class {
@@ -3981,6 +4102,7 @@ var AxlEventBus = class {
3981
4102
  maxQueued;
3982
4103
  onOverflow;
3983
4104
  overflowWarned = false;
4105
+ droppedEvents = 0;
3984
4106
  /** Callbacks fired exactly once when `_finish()` is called. Used by
3985
4107
  * curated views (e.g., `partialObjects`) that are listener-based and
3986
4108
  * need a termination signal independent of any synthetic `done` event. */
@@ -4026,6 +4148,10 @@ var AxlEventBus = class {
4026
4148
  * can be 4 KB+ each, hence the more aggressive cleanup here).
4027
4149
  */
4028
4150
  stringStreamByAsk = /* @__PURE__ */ new Map();
4151
+ /** Internal fan-out for `stringStream()` subscribers. Kept separate from
4152
+ * the public EventEmitter so strict curated-view overflow is a runtime
4153
+ * signal, not mistaken for (and swallowed as) a buggy user listener. */
4154
+ stringStreamSubscribers = /* @__PURE__ */ new Set();
4029
4155
  /**
4030
4156
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
4031
4157
  * is `await using`-disposed. Set via the second constructor argument.
@@ -4059,6 +4185,12 @@ var AxlEventBus = class {
4059
4185
  this.bus.on("error", () => {
4060
4186
  });
4061
4187
  }
4188
+ /** Machine-readable completeness across this bus's bounded iterable views.
4189
+ * EventEmitter listener delivery is independent and is not described by
4190
+ * this status. */
4191
+ get observationStatus() {
4192
+ return this.droppedEvents === 0 ? { complete: true } : { complete: false, reason: "queue_overflow", droppedEvents: this.droppedEvents };
4193
+ }
4062
4194
  unknownEventWarned = false;
4063
4195
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4064
4196
  on(event, handler) {
@@ -4371,7 +4503,7 @@ var AxlEventBus = class {
4371
4503
  }
4372
4504
  return {
4373
4505
  [Symbol.asyncIterator]() {
4374
- const pending = [];
4506
+ const pending = /* @__PURE__ */ new Map();
4375
4507
  let resolveWaiter = null;
4376
4508
  let finished = self.finished;
4377
4509
  let unsubscribed = false;
@@ -4385,10 +4517,39 @@ var AxlEventBus = class {
4385
4517
  if (filterPath !== void 0 && path !== filterPath) return false;
4386
4518
  return true;
4387
4519
  };
4520
+ const pendingKey = (askId, path) => JSON.stringify([askId, path]);
4521
+ const enqueue = (event) => {
4522
+ const key = pendingKey(event.askId, event.path);
4523
+ const existing = pending.get(key);
4524
+ if (existing) {
4525
+ pending.set(key, {
4526
+ ...event,
4527
+ delta: existing.delta + event.delta
4528
+ });
4529
+ wakeWaiter();
4530
+ return;
4531
+ }
4532
+ if (pending.size >= self.maxQueued) {
4533
+ if (self.onOverflow === "throw") {
4534
+ self.droppedEvents++;
4535
+ throw new EventStreamOverflowError(self.maxQueued, "string_delta");
4536
+ }
4537
+ const oldest = pending.keys().next().value;
4538
+ if (oldest !== void 0) {
4539
+ pending.delete(oldest);
4540
+ self.droppedEvents++;
4541
+ }
4542
+ self.warnOverflow(
4543
+ "dropping oldest pending string field (stringStream subscriber is slower than producer)"
4544
+ );
4545
+ }
4546
+ pending.set(key, event);
4547
+ wakeWaiter();
4548
+ };
4388
4549
  for (const [askId, paths] of self.stringStreamByAsk) {
4389
4550
  for (const [path, acc] of paths) {
4390
4551
  if (!matchesFilter(askId, path)) continue;
4391
- pending.push({
4552
+ enqueue({
4392
4553
  askId,
4393
4554
  agent: acc.agent,
4394
4555
  path,
@@ -4398,51 +4559,37 @@ var AxlEventBus = class {
4398
4559
  });
4399
4560
  }
4400
4561
  }
4401
- const handler = (event) => {
4402
- if (event.type !== "string_delta") return;
4403
- const askId = event.askId ?? "";
4404
- if (!matchesFilter(askId, event.data.path)) return;
4405
- const paths = self.stringStreamByAsk.get(askId);
4406
- const acc = paths?.get(event.data.path);
4407
- const accumulated = acc?.text ?? event.data.delta;
4408
- pending.push({
4409
- askId,
4410
- agent: event.agent,
4411
- path: event.data.path,
4412
- delta: event.data.delta,
4413
- accumulated,
4414
- attempt: event.attempt
4415
- });
4416
- wakeWaiter();
4417
- };
4418
- self.bus.on("string_delta", handler);
4419
- const pipelineHandler = (event) => {
4420
- if (event.type !== "pipeline") return;
4421
- if (event.status !== "failed") return;
4422
- const askId = event.askId ?? "";
4423
- for (let i = pending.length - 1; i >= 0; i--) {
4424
- if (pending[i].askId === askId) pending.splice(i, 1);
4562
+ const subscriber = {
4563
+ push(event) {
4564
+ if (matchesFilter(event.askId, event.path)) enqueue(event);
4565
+ },
4566
+ reset(askId) {
4567
+ for (const [key, event] of pending) {
4568
+ if (event.askId === askId) pending.delete(key);
4569
+ }
4425
4570
  }
4426
4571
  };
4427
- self.bus.on("pipeline", pipelineHandler);
4572
+ self.stringStreamSubscribers.add(subscriber);
4428
4573
  const unsubscribe = () => {
4429
4574
  if (unsubscribed) return;
4430
4575
  unsubscribed = true;
4431
- self.bus.off("string_delta", handler);
4432
- self.bus.off("pipeline", pipelineHandler);
4576
+ self.stringStreamSubscribers.delete(subscriber);
4433
4577
  unsubscribeFinish();
4434
4578
  };
4435
4579
  const unsubscribeFinish = self._onFinish(() => {
4436
4580
  finished = true;
4437
- self.bus.off("string_delta", handler);
4438
- self.bus.off("pipeline", pipelineHandler);
4581
+ self.stringStreamSubscribers.delete(subscriber);
4439
4582
  wakeWaiter();
4440
4583
  });
4441
4584
  return {
4442
4585
  async next() {
4443
4586
  while (true) {
4444
- if (pending.length > 0) {
4445
- return { value: pending.shift(), done: false };
4587
+ if (pending.size > 0) {
4588
+ const first = pending.entries().next().value;
4589
+ if (first) {
4590
+ pending.delete(first[0]);
4591
+ return { value: first[1], done: false };
4592
+ }
4446
4593
  }
4447
4594
  if (finished) {
4448
4595
  unsubscribe();
@@ -4500,6 +4647,7 @@ var AxlEventBus = class {
4500
4647
  */
4501
4648
  _push(event) {
4502
4649
  if (this.finished) return;
4650
+ let overflowError;
4503
4651
  if (event.type === "partial_object" && event.data?.object !== void 0) {
4504
4652
  const askId = event.askId ?? "";
4505
4653
  this.latestPartialByAsk.set(askId, {
@@ -4521,10 +4669,30 @@ var AxlEventBus = class {
4521
4669
  text: (existing?.text ?? "") + event.data.delta,
4522
4670
  attempt: event.attempt
4523
4671
  });
4672
+ const accumulated = paths.get(event.data.path).text;
4673
+ for (const subscriber of this.stringStreamSubscribers) {
4674
+ try {
4675
+ subscriber.push({
4676
+ askId,
4677
+ agent: event.agent,
4678
+ path: event.data.path,
4679
+ delta: event.data.delta,
4680
+ accumulated,
4681
+ attempt: event.attempt
4682
+ });
4683
+ } catch (error) {
4684
+ if (isEventStreamOverflowError(error)) {
4685
+ overflowError ??= error;
4686
+ } else {
4687
+ throw error;
4688
+ }
4689
+ }
4690
+ }
4524
4691
  } else if (event.type === "pipeline" && event.status === "failed") {
4525
4692
  const askId = event.askId ?? "";
4526
4693
  this.latestPartialByAsk.delete(askId);
4527
4694
  this.stringStreamByAsk.delete(askId);
4695
+ for (const subscriber of this.stringStreamSubscribers) subscriber.reset(askId);
4528
4696
  } else if (event.type === "ask_end") {
4529
4697
  this.stringStreamByAsk.delete(event.askId ?? "");
4530
4698
  }
@@ -4539,39 +4707,57 @@ var AxlEventBus = class {
4539
4707
  const waiter = this.waiters.shift();
4540
4708
  if (waiter) {
4541
4709
  waiter({ value: event, done: false });
4710
+ if (overflowError) throw overflowError;
4542
4711
  return;
4543
4712
  }
4544
4713
  if (this.eventQueue.length >= this.maxQueued && !TERMINAL_TYPES.has(event.type)) {
4545
- this.handleOverflow(event);
4714
+ try {
4715
+ this.handleOverflow(event);
4716
+ } catch (error) {
4717
+ if (isEventStreamOverflowError(error)) {
4718
+ overflowError ??= error;
4719
+ } else {
4720
+ throw error;
4721
+ }
4722
+ }
4723
+ if (overflowError) throw overflowError;
4546
4724
  return;
4547
4725
  }
4548
4726
  this.eventQueue.push(event);
4727
+ if (overflowError) throw overflowError;
4549
4728
  }
4550
4729
  /** Apply the overflow policy when the queue is at `maxQueued` and a
4551
4730
  * non-terminal event arrives. Terminal events bypass this path entirely
4552
4731
  * (checked by the caller). */
4553
4732
  handleOverflow(event) {
4554
4733
  if (this.onOverflow === "throw") {
4734
+ this.droppedEvents++;
4555
4735
  throw new EventStreamOverflowError(this.maxQueued, event.type);
4556
4736
  }
4557
4737
  let droppedIdx = -1;
4558
4738
  for (let i = 0; i < this.eventQueue.length; i++) {
4559
- if (!TERMINAL_TYPES.has(this.eventQueue[i].type)) {
4739
+ const candidate = this.eventQueue[i];
4740
+ if (!TERMINAL_TYPES.has(candidate.type)) {
4560
4741
  droppedIdx = i;
4561
4742
  break;
4562
4743
  }
4563
4744
  }
4564
- if (!this.overflowWarned) {
4565
- this.overflowWarned = true;
4566
- console.warn(
4567
- `[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.`
4568
- );
4569
- }
4745
+ this.warnOverflow(
4746
+ droppedIdx >= 0 ? "dropping oldest non-terminal events (consumer is slower than producer)" : "queue contains only protected terminal events; preserving them past the cap"
4747
+ );
4570
4748
  if (droppedIdx >= 0) {
4571
4749
  this.eventQueue.splice(droppedIdx, 1);
4750
+ this.droppedEvents++;
4572
4751
  }
4573
4752
  this.eventQueue.push(event);
4574
4753
  }
4754
+ warnOverflow(detail) {
4755
+ if (this.overflowWarned) return;
4756
+ this.overflowWarned = true;
4757
+ console.warn(
4758
+ `[axl] AxlEventBus queue exceeded maxQueued=${this.maxQueued}; ${detail}. This warning fires once per bus instance.`
4759
+ );
4760
+ }
4575
4761
  /**
4576
4762
  * Mark the bus as finished. Wakes any pending iterators with `done: true`
4577
4763
  * and drops further `_push` calls. Idempotent.
@@ -4638,6 +4824,43 @@ var AxlEventBus = class {
4638
4824
  }
4639
4825
  };
4640
4826
 
4827
+ // src/event-schema.ts
4828
+ var UnsupportedEventSchemaVersionError = class extends Error {
4829
+ constructor(version) {
4830
+ super(`Unsupported Axl event schema version: ${String(version)}`);
4831
+ this.version = version;
4832
+ this.name = "UnsupportedEventSchemaVersionError";
4833
+ }
4834
+ };
4835
+ function getEventSchemaVersion(event) {
4836
+ const version = event.schemaVersion === void 0 ? 1 : event.schemaVersion;
4837
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4838
+ return version;
4839
+ }
4840
+ function getExecutionEventSchemaVersion(execution) {
4841
+ const version = execution.eventSchemaVersion === void 0 ? 1 : execution.eventSchemaVersion;
4842
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4843
+ return version;
4844
+ }
4845
+ function normalizeStoredExecution(execution) {
4846
+ const version = getExecutionEventSchemaVersion(execution);
4847
+ if (!Array.isArray(execution.events)) {
4848
+ throw new TypeError(`Execution ${execution.executionId} has a non-array events field`);
4849
+ }
4850
+ for (const event of execution.events) {
4851
+ const eventVersion = getEventSchemaVersion(event);
4852
+ if (eventVersion !== version) {
4853
+ throw new Error(
4854
+ `Execution ${execution.executionId} declares event schema v${version} but contains a v${eventVersion} event`
4855
+ );
4856
+ }
4857
+ }
4858
+ if (version === 2) {
4859
+ return { ...execution, eventSchemaVersion: 2 };
4860
+ }
4861
+ return { ...execution, eventSchemaVersion: 1 };
4862
+ }
4863
+
4641
4864
  // src/redaction.ts
4642
4865
  var REDACTED = "[redacted]";
4643
4866
  function walkObjectOneLevel(obj, preserveKeys) {
@@ -4688,7 +4911,7 @@ function redactMemory(event) {
4688
4911
  };
4689
4912
  }
4690
4913
  var passthrough = (e) => e;
4691
- var REDACTION_RULES = {
4914
+ var LEGACY_REDACTION_RULES = {
4692
4915
  workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
4693
4916
  workflow_end: (e) => {
4694
4917
  const d = e.data;
@@ -4788,10 +5011,125 @@ var REDACTION_RULES = {
4788
5011
  done: (e) => ({ ...e, data: { result: REDACTED } }),
4789
5012
  error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
4790
5013
  };
5014
+ function redactLegacyEvent(event) {
5015
+ const rule = LEGACY_REDACTION_RULES[event.type];
5016
+ return rule(event);
5017
+ }
5018
+ function redactV2Outcome(outcome) {
5019
+ switch (outcome.status) {
5020
+ case "succeeded":
5021
+ return { ...outcome, result: REDACTED };
5022
+ case "denied":
5023
+ return outcome.reason === void 0 ? outcome : { ...outcome, reason: REDACTED };
5024
+ case "cancelled":
5025
+ return {
5026
+ ...outcome,
5027
+ cancellation: {
5028
+ ...outcome.cancellation,
5029
+ ..."result" in outcome.cancellation ? { result: REDACTED } : {},
5030
+ ...outcome.cancellation.reason !== void 0 ? { reason: REDACTED } : {}
5031
+ }
5032
+ };
5033
+ case "failed": {
5034
+ const failure = outcome.failure;
5035
+ return {
5036
+ ...outcome,
5037
+ failure: {
5038
+ ...failure,
5039
+ ..."result" in failure ? { result: REDACTED } : {},
5040
+ error: {
5041
+ ...failure.error,
5042
+ message: REDACTED,
5043
+ ...failure.error.cause !== void 0 ? { cause: REDACTED } : {}
5044
+ }
5045
+ }
5046
+ };
5047
+ }
5048
+ }
5049
+ }
5050
+ function redactV2Rejection(data) {
5051
+ switch (data.reason) {
5052
+ case "unavailable":
5053
+ return data;
5054
+ case "invalid_json":
5055
+ return { ...data, message: REDACTED };
5056
+ case "invalid_arguments":
5057
+ return {
5058
+ ...data,
5059
+ args: REDACTED,
5060
+ issues: data.issues.map((issue) => ({
5061
+ ...issue,
5062
+ ...issue.message !== void 0 ? { message: REDACTED } : {}
5063
+ }))
5064
+ };
5065
+ }
5066
+ }
5067
+ var V2_TOOL_REDACTION_RULES = {
5068
+ tool_call_start: (event) => ({
5069
+ ...event,
5070
+ data: { ...event.data, args: REDACTED }
5071
+ }),
5072
+ tool_call_end: (event) => ({
5073
+ ...event,
5074
+ data: {
5075
+ ...event.data,
5076
+ args: REDACTED,
5077
+ outcome: redactV2Outcome(event.data.outcome)
5078
+ }
5079
+ }),
5080
+ tool_call_rejected: (event) => ({
5081
+ ...event,
5082
+ data: redactV2Rejection(event.data)
5083
+ })
5084
+ };
5085
+ function reuseLegacyRule(type) {
5086
+ return LEGACY_REDACTION_RULES[type];
5087
+ }
5088
+ var COMMON_V2_REDACTION_RULES = {
5089
+ workflow_start: reuseLegacyRule("workflow_start"),
5090
+ workflow_end: reuseLegacyRule("workflow_end"),
5091
+ ask_start: reuseLegacyRule("ask_start"),
5092
+ ask_end: reuseLegacyRule("ask_end"),
5093
+ agent_call_start: reuseLegacyRule("agent_call_start"),
5094
+ agent_call_end: reuseLegacyRule("agent_call_end"),
5095
+ token: reuseLegacyRule("token"),
5096
+ tool_approval: reuseLegacyRule("tool_approval"),
5097
+ delegate: reuseLegacyRule("delegate"),
5098
+ handoff_start: reuseLegacyRule("handoff_start"),
5099
+ handoff_return: reuseLegacyRule("handoff_return"),
5100
+ pipeline: reuseLegacyRule("pipeline"),
5101
+ partial_object: reuseLegacyRule("partial_object"),
5102
+ string_delta: reuseLegacyRule("string_delta"),
5103
+ verify: reuseLegacyRule("verify"),
5104
+ schema_diagnostic: reuseLegacyRule("schema_diagnostic"),
5105
+ log: reuseLegacyRule("log"),
5106
+ memory_remember: reuseLegacyRule("memory_remember"),
5107
+ memory_recall: reuseLegacyRule("memory_recall"),
5108
+ memory_forget: reuseLegacyRule("memory_forget"),
5109
+ checkpoint_save: reuseLegacyRule("checkpoint_save"),
5110
+ checkpoint_replay: reuseLegacyRule("checkpoint_replay"),
5111
+ await_human: reuseLegacyRule("await_human"),
5112
+ await_human_resolved: reuseLegacyRule("await_human_resolved"),
5113
+ guardrail: reuseLegacyRule("guardrail"),
5114
+ schema_check: reuseLegacyRule("schema_check"),
5115
+ validate: reuseLegacyRule("validate"),
5116
+ done: reuseLegacyRule("done"),
5117
+ error: reuseLegacyRule("error")
5118
+ };
5119
+ var REDACTION_RULES = {
5120
+ ...COMMON_V2_REDACTION_RULES,
5121
+ ...V2_TOOL_REDACTION_RULES
5122
+ };
4791
5123
  function redactEvent(event) {
4792
5124
  const rule = REDACTION_RULES[event.type];
4793
5125
  return rule(event);
4794
5126
  }
5127
+ function redactHistoricalEvent(event) {
5128
+ if (getEventSchemaVersion(event) === 1) {
5129
+ return redactLegacyEvent(event);
5130
+ }
5131
+ return redactEvent(event);
5132
+ }
4795
5133
 
4796
5134
  // src/schema-diagnostics.ts
4797
5135
  var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
@@ -4991,148 +5329,626 @@ function normalize(value, path, stack) {
4991
5329
  "accessor properties are not supported"
4992
5330
  );
4993
5331
  }
4994
- if (typeof key === "symbol" && descriptor?.enumerable) {
4995
- throw invalid(`${path}[symbol]`, "enumerable symbol keys are not supported");
5332
+ if (typeof key === "symbol") {
5333
+ throw invalid(`${path}[symbol]`, "symbol keys are not supported");
5334
+ }
5335
+ }
5336
+ if (typeof descriptors.toJSON?.value === "function") {
5337
+ throw invalid(propertyPath(path, "toJSON"), "custom toJSON is not supported");
5338
+ }
5339
+ const array = Array.isArray(value);
5340
+ if (array && prototype !== Array.prototype && prototype !== null) {
5341
+ throw invalid(path, "array subclasses are not supported");
5342
+ }
5343
+ const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
5344
+ if (typeof inheritedToJSON?.value === "function") {
5345
+ throw invalid(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
5346
+ }
5347
+ const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
5348
+ if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
5349
+ throw invalid(path, "thenable output is not supported");
5350
+ }
5351
+ stack.add(value);
5352
+ try {
5353
+ if (array) {
5354
+ const normalized2 = [];
5355
+ const length = descriptors.length?.value;
5356
+ if (!Number.isSafeInteger(length) || length < 0) {
5357
+ throw invalid(propertyPath(path, "length"), "array length is invalid");
5358
+ }
5359
+ for (let index = 0; index < length; index++) {
5360
+ const descriptor = descriptors[String(index)];
5361
+ if (!descriptor || !("value" in descriptor)) {
5362
+ throw invalid(`${path}[${index}]`, "arrays must be dense data properties");
5363
+ }
5364
+ if (descriptor.value === void 0) {
5365
+ throw invalid(`${path}[${index}]`, "array values cannot be undefined");
5366
+ }
5367
+ normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
5368
+ }
5369
+ for (const key of Object.keys(descriptors)) {
5370
+ const descriptor = descriptors[key];
5371
+ if (!descriptor?.enumerable) continue;
5372
+ const index = Number(key);
5373
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
5374
+ throw invalid(propertyPath(path, key), "non-index array properties are not supported");
5375
+ }
5376
+ }
5377
+ Object.setPrototypeOf(normalized2, null);
5378
+ return normalized2;
5379
+ }
5380
+ if (prototype !== Object.prototype && prototype !== null) {
5381
+ throw invalid(path, "only plain objects and null-prototype records are supported");
5382
+ }
5383
+ const normalized = /* @__PURE__ */ Object.create(null);
5384
+ for (const key of Object.keys(descriptors)) {
5385
+ const descriptor = descriptors[key];
5386
+ if (!descriptor?.enumerable) continue;
5387
+ if (!("value" in descriptor)) {
5388
+ throw invalid(propertyPath(path, key), "accessor properties are not supported");
5389
+ }
5390
+ if (descriptor.value === void 0) continue;
5391
+ normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
5392
+ }
5393
+ return normalized;
5394
+ } finally {
5395
+ stack.delete(value);
5396
+ }
5397
+ }
5398
+ function serializeToolModelOutput(toolName, output) {
5399
+ try {
5400
+ if (import_node_util.types.isPromise(output)) {
5401
+ void Promise.prototype.then.call(output, void 0, () => void 0);
5402
+ }
5403
+ if (typeof output === "string") return output;
5404
+ return JSON.stringify(normalize(output, "$", /* @__PURE__ */ new WeakSet()));
5405
+ } catch (cause) {
5406
+ throw new ToolModelOutputError(toolName, cause);
5407
+ }
5408
+ }
5409
+
5410
+ // src/tool-invocation.ts
5411
+ var INVALID_JSON_MODEL_MESSAGE = "Error: Invalid JSON in tool arguments. Please provide valid JSON.";
5412
+ var INVALID_ARGUMENTS_MODEL_MESSAGE = "Error: Tool arguments are invalid. Correct the arguments and try again.";
5413
+ var SENSITIVE_SUCCESS_MESSAGE = "[REDACTED - sensitive tool output]";
5414
+ var SENSITIVE_FAILURE_MESSAGE = "[REDACTED - sensitive tool failure]";
5415
+ var MAX_MCP_MODEL_ERROR_LENGTH = 2e3;
5416
+ function cloneToolArguments(value) {
5417
+ try {
5418
+ return structuredClone(value);
5419
+ } catch {
5420
+ throw new Error("Prepared tool arguments must be structured-cloneable");
5421
+ }
5422
+ }
5423
+ function parseToolInvocation(options) {
5424
+ const { toolCall, configuredTool, override, mcpCall, mcpTraceName, availableTools } = options;
5425
+ const requestedTool = toolCall.function.name;
5426
+ if (!override && !configuredTool && !mcpCall) {
5427
+ return {
5428
+ kind: "rejected",
5429
+ requestedTool,
5430
+ toolName: requestedTool,
5431
+ callId: toolCall.id,
5432
+ data: { reason: "unavailable", requestedTool, availableTools },
5433
+ modelMessage: `Tool "${requestedTool}" is not available. Available tools: ${availableTools.length > 0 ? availableTools.join(", ") : "none"}`
5434
+ };
5435
+ }
5436
+ let parsedArguments;
5437
+ try {
5438
+ parsedArguments = JSON.parse(toolCall.function.arguments);
5439
+ } catch {
5440
+ return {
5441
+ kind: "rejected",
5442
+ requestedTool,
5443
+ toolName: mcpTraceName ?? requestedTool,
5444
+ callId: toolCall.id,
5445
+ data: {
5446
+ reason: "invalid_json",
5447
+ requestedTool,
5448
+ message: "Tool arguments were not valid JSON."
5449
+ },
5450
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5451
+ };
5452
+ }
5453
+ if (typeof parsedArguments !== "object" || parsedArguments === null || Array.isArray(parsedArguments)) {
5454
+ return {
5455
+ kind: "rejected",
5456
+ requestedTool,
5457
+ toolName: mcpTraceName ?? requestedTool,
5458
+ callId: toolCall.id,
5459
+ data: {
5460
+ reason: "invalid_json",
5461
+ requestedTool,
5462
+ message: "Tool arguments must decode to a JSON object."
5463
+ },
5464
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5465
+ };
5466
+ }
5467
+ const source = override ? { kind: "override", execute: override } : configuredTool ? { kind: "local", tool: configuredTool } : { kind: "mcp", call: mcpCall };
5468
+ let preparedArguments = parsedArguments;
5469
+ if (source.kind === "local") {
5470
+ try {
5471
+ preparedArguments = prepareToolInput(source.tool, parsedArguments);
5472
+ } catch (error) {
5473
+ return {
5474
+ kind: "rejected",
5475
+ requestedTool,
5476
+ toolName: requestedTool,
5477
+ callId: toolCall.id,
5478
+ data: {
5479
+ reason: "invalid_arguments",
5480
+ requestedTool,
5481
+ args: parsedArguments,
5482
+ issues: toolArgumentIssues(error)
5483
+ },
5484
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5485
+ };
4996
5486
  }
4997
5487
  }
4998
- if (typeof descriptors.toJSON?.value === "function") {
4999
- throw invalid(propertyPath(path, "toJSON"), "custom toJSON is not supported");
5488
+ try {
5489
+ preparedArguments = cloneToolArguments(preparedArguments);
5490
+ } catch (error) {
5491
+ return {
5492
+ kind: "rejected",
5493
+ requestedTool,
5494
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5495
+ callId: toolCall.id,
5496
+ data: {
5497
+ reason: "invalid_arguments",
5498
+ requestedTool,
5499
+ args: parsedArguments,
5500
+ issues: toolArgumentIssues(error)
5501
+ },
5502
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5503
+ };
5504
+ }
5505
+ return {
5506
+ requestedTool,
5507
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5508
+ callId: toolCall.id,
5509
+ args: preparedArguments,
5510
+ configuredTool,
5511
+ source
5512
+ };
5513
+ }
5514
+ function isAbortError3(error) {
5515
+ try {
5516
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
5517
+ } catch {
5518
+ return false;
5519
+ }
5520
+ }
5521
+ function normalizeAbortError(reason) {
5522
+ if (isAbortError3(reason)) return reason;
5523
+ const error = new Error("The operation was aborted");
5524
+ error.name = "AbortError";
5525
+ if (reason !== void 0) {
5526
+ Object.defineProperty(error, "cause", { value: reason, enumerable: false });
5527
+ }
5528
+ return error;
5529
+ }
5530
+ function cancellationError(signal, error) {
5531
+ if (signal?.aborted) return normalizeAbortError(signal.reason);
5532
+ return isAbortError3(error) ? normalizeAbortError(error) : void 0;
5533
+ }
5534
+ function eventError(error) {
5535
+ let isError = false;
5536
+ try {
5537
+ isError = error instanceof Error;
5538
+ } catch {
5539
+ }
5540
+ if (isError) {
5541
+ const read = (key) => {
5542
+ try {
5543
+ return Reflect.get(error, key);
5544
+ } catch {
5545
+ return void 0;
5546
+ }
5547
+ };
5548
+ const name = read("name");
5549
+ const message = read("message");
5550
+ const code = read("code");
5551
+ const cause = read("cause");
5552
+ return {
5553
+ name: typeof name === "string" ? name : "Error",
5554
+ message: typeof message === "string" ? message : "Unknown error",
5555
+ ...typeof code === "string" ? { code } : {},
5556
+ ...cause !== void 0 ? { cause } : {}
5557
+ };
5558
+ }
5559
+ try {
5560
+ return { name: "Error", message: String(error) };
5561
+ } catch {
5562
+ return { name: "Error", message: "Unknown thrown value" };
5000
5563
  }
5001
- const array = Array.isArray(value);
5002
- if (array && prototype !== Array.prototype && prototype !== null) {
5003
- throw invalid(path, "array subclasses are not supported");
5564
+ }
5565
+ function isToolFailure(error) {
5566
+ try {
5567
+ return error instanceof ToolFailure;
5568
+ } catch {
5569
+ return false;
5004
5570
  }
5005
- const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
5006
- if (typeof inheritedToJSON?.value === "function") {
5007
- throw invalid(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
5571
+ }
5572
+ function cancellation(phase, error, result) {
5573
+ let reason;
5574
+ try {
5575
+ reason = error instanceof Error && typeof error.message === "string" ? error.message : void 0;
5576
+ } catch {
5008
5577
  }
5009
- const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
5010
- if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
5011
- throw invalid(path, "thenable output is not supported");
5578
+ const cancellation2 = phase === "after_handler" || phase === "after_hook" || phase === "projection" || phase === "serialization" ? { phase, result, ...reason ? { reason } : {} } : { phase, ...reason ? { reason } : {} };
5579
+ return {
5580
+ kind: "cancelled",
5581
+ cancellation: cancellation2,
5582
+ error: normalizeAbortError(error)
5583
+ };
5584
+ }
5585
+ function failed(phase, error, options = {}) {
5586
+ let modelMessage;
5587
+ if (isToolFailure(error)) {
5588
+ try {
5589
+ modelMessage = typeof error.modelMessage === "string" ? error.modelMessage : void 0;
5590
+ } catch {
5591
+ }
5012
5592
  }
5013
- stack.add(value);
5593
+ const toolFailure = modelMessage !== void 0;
5594
+ const common = {
5595
+ phase,
5596
+ kind: toolFailure ? "tool_failure" : "unexpected",
5597
+ disposition: toolFailure ? "continue" : "abort",
5598
+ error: eventError(error)
5599
+ };
5600
+ const failure = phase === "handler" ? { ...common, attempts: options.attempts ?? 1 } : phase === "after_hook" ? { ...common, result: options.result } : common;
5601
+ return {
5602
+ kind: "failed",
5603
+ failure,
5604
+ error,
5605
+ ...modelMessage !== void 0 ? { modelMessage } : {}
5606
+ };
5607
+ }
5608
+ function mcpContent(result) {
5609
+ return result.content.map((content) => content.type === "text" ? content.text : `[${content.type}]`).join("\n");
5610
+ }
5611
+ function safeMcpModelError(content) {
5612
+ let normalized = "";
5613
+ for (const character of content) {
5614
+ const code = character.charCodeAt(0);
5615
+ normalized += code <= 31 && code !== 9 && code !== 10 && code !== 13 || code === 127 ? " " : character;
5616
+ }
5617
+ return `MCP tool error: ${normalized.slice(0, MAX_MCP_MODEL_ERROR_LENGTH)}`;
5618
+ }
5619
+ async function executeAcceptedTool(options) {
5620
+ const { invocation, context, signal, requestApproval, createChildContext } = options;
5621
+ let effectiveArgs = cloneToolArguments(invocation.args);
5622
+ if (invocation.source.kind === "local" && invocation.source.tool.requireApproval) {
5623
+ try {
5624
+ signal?.throwIfAborted();
5625
+ const approval = await requestApproval();
5626
+ signal?.throwIfAborted();
5627
+ if (!approval.approved) return { kind: "denied", reason: approval.reason };
5628
+ } catch (error) {
5629
+ rethrowEventStreamOverflow(error);
5630
+ const abort = cancellationError(signal, error);
5631
+ if (abort !== void 0) return cancellation("approval", abort);
5632
+ return {
5633
+ kind: "failed",
5634
+ failure: {
5635
+ phase: "approval",
5636
+ kind: "infrastructure",
5637
+ disposition: "abort",
5638
+ error: eventError(error)
5639
+ },
5640
+ error
5641
+ };
5642
+ }
5643
+ }
5644
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.before) {
5645
+ try {
5646
+ signal?.throwIfAborted();
5647
+ effectiveArgs = await invocation.source.tool.hooks.before(effectiveArgs, context);
5648
+ signal?.throwIfAborted();
5649
+ } catch (error) {
5650
+ rethrowEventStreamOverflow(error);
5651
+ const abort = cancellationError(signal, error);
5652
+ if (abort !== void 0) return cancellation("before_hook", abort);
5653
+ return failed("before_hook", error);
5654
+ }
5655
+ }
5656
+ let result;
5657
+ let attempts = 1;
5014
5658
  try {
5015
- if (array) {
5016
- const normalized2 = [];
5017
- const length = descriptors.length?.value;
5018
- if (!Number.isSafeInteger(length) || length < 0) {
5019
- throw invalid(propertyPath(path, "length"), "array length is invalid");
5020
- }
5021
- for (let index = 0; index < length; index++) {
5022
- const descriptor = descriptors[String(index)];
5023
- if (!descriptor || !("value" in descriptor)) {
5024
- throw invalid(`${path}[${index}]`, "arrays must be dense data properties");
5025
- }
5026
- if (descriptor.value === void 0) {
5027
- throw invalid(`${path}[${index}]`, "array values cannot be undefined");
5028
- }
5029
- normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
5659
+ signal?.throwIfAborted();
5660
+ switch (invocation.source.kind) {
5661
+ case "override": {
5662
+ const executeOverride = invocation.source.execute;
5663
+ result = await executeOverride(effectiveArgs);
5664
+ break;
5030
5665
  }
5031
- for (const key of Object.keys(descriptors)) {
5032
- const descriptor = descriptors[key];
5033
- if (!descriptor?.enumerable) continue;
5034
- const index = Number(key);
5035
- if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
5036
- throw invalid(propertyPath(path, key), "non-index array properties are not supported");
5666
+ case "local":
5667
+ result = await executePreparedTool(
5668
+ invocation.source.tool,
5669
+ effectiveArgs,
5670
+ createChildContext(),
5671
+ {
5672
+ signal,
5673
+ onAttempt: (attempt) => attempts = attempt,
5674
+ checkAfterHandlerAbort: false
5675
+ }
5676
+ );
5677
+ break;
5678
+ case "mcp": {
5679
+ const mcpResult = await invocation.source.call(effectiveArgs);
5680
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, mcpResult);
5681
+ if (mcpResult.isError) {
5682
+ const content = mcpContent(mcpResult);
5683
+ const error = new Error(content || "MCP tool returned an error");
5684
+ return {
5685
+ kind: "failed",
5686
+ failure: {
5687
+ phase: "handler",
5688
+ kind: "mcp_error",
5689
+ disposition: "continue",
5690
+ error: { name: "McpToolError", message: error.message, code: "MCP_TOOL_ERROR" },
5691
+ attempts: 1
5692
+ },
5693
+ error,
5694
+ modelMessage: safeMcpModelError(content)
5695
+ };
5037
5696
  }
5697
+ result = mcpResult;
5698
+ break;
5038
5699
  }
5039
- Object.setPrototypeOf(normalized2, null);
5040
- return normalized2;
5041
5700
  }
5042
- if (prototype !== Object.prototype && prototype !== null) {
5043
- throw invalid(path, "only plain objects and null-prototype records are supported");
5701
+ } catch (error) {
5702
+ rethrowEventStreamOverflow(error);
5703
+ const abort = cancellationError(signal, error);
5704
+ if (abort !== void 0) return cancellation("handler", abort);
5705
+ return failed("handler", error, { attempts });
5706
+ }
5707
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, result);
5708
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.after) {
5709
+ const rawResult = result;
5710
+ try {
5711
+ result = await invocation.source.tool.hooks.after(result, context);
5712
+ signal?.throwIfAborted();
5713
+ } catch (error) {
5714
+ rethrowEventStreamOverflow(error);
5715
+ const abort = cancellationError(signal, error);
5716
+ if (abort !== void 0) return cancellation("after_hook", abort, rawResult);
5717
+ return failed("after_hook", error, { result: rawResult });
5044
5718
  }
5045
- const normalized = /* @__PURE__ */ Object.create(null);
5046
- for (const key of Object.keys(descriptors)) {
5047
- const descriptor = descriptors[key];
5048
- if (!descriptor?.enumerable) continue;
5049
- if (!("value" in descriptor)) {
5050
- throw invalid(propertyPath(path, key), "accessor properties are not supported");
5719
+ }
5720
+ return { kind: "succeeded", result };
5721
+ }
5722
+ function prepareToolMessage(invocation, outcome) {
5723
+ const sensitive = invocation.configuredTool?.sensitive === true;
5724
+ if (outcome.kind === "denied") {
5725
+ return {
5726
+ ok: true,
5727
+ content: JSON.stringify({ error: "Tool request was denied by human approval." })
5728
+ };
5729
+ }
5730
+ if (outcome.kind === "failed") {
5731
+ if (outcome.failure.disposition !== "continue" || outcome.modelMessage === void 0) {
5732
+ throw new Error("Aborting tool failures cannot produce provider messages");
5733
+ }
5734
+ return { ok: true, content: sensitive ? SENSITIVE_FAILURE_MESSAGE : outcome.modelMessage };
5735
+ }
5736
+ if (outcome.kind !== "succeeded") {
5737
+ throw new Error("Cancelled tool calls cannot produce provider messages");
5738
+ }
5739
+ if (sensitive) return { ok: true, content: SENSITIVE_SUCCESS_MESSAGE };
5740
+ if (invocation.source.kind === "mcp") {
5741
+ try {
5742
+ return { ok: true, content: mcpContent(outcome.result) };
5743
+ } catch (error) {
5744
+ rethrowEventStreamOverflow(error);
5745
+ return { ok: false, phase: "serialization", error };
5746
+ }
5747
+ }
5748
+ if (invocation.configuredTool?.toModelOutput) {
5749
+ try {
5750
+ const project = invocation.configuredTool.toModelOutput;
5751
+ return {
5752
+ ok: true,
5753
+ content: serializeToolModelOutput(
5754
+ invocation.requestedTool,
5755
+ project(outcome.result)
5756
+ )
5757
+ };
5758
+ } catch (error) {
5759
+ rethrowEventStreamOverflow(error);
5760
+ if (error instanceof ToolModelOutputError && isEventStreamOverflowError(error.cause)) {
5761
+ throw error.cause;
5051
5762
  }
5052
- if (descriptor.value === void 0) continue;
5053
- normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
5763
+ return {
5764
+ ok: false,
5765
+ phase: "projection",
5766
+ error: error instanceof ToolModelOutputError && error.toolName === invocation.requestedTool ? error : new ToolModelOutputError(invocation.requestedTool, error)
5767
+ };
5054
5768
  }
5055
- return normalized;
5056
- } finally {
5057
- stack.delete(value);
5058
5769
  }
5059
- }
5060
- function serializeToolModelOutput(toolName, output) {
5061
5770
  try {
5062
- if (import_node_util.types.isPromise(output)) {
5063
- void Promise.prototype.then.call(output, void 0, () => void 0);
5771
+ const content = JSON.stringify(outcome.result);
5772
+ if (content === void 0) throw new Error("Tool result is not JSON serializable");
5773
+ return { ok: true, content };
5774
+ } catch (error) {
5775
+ rethrowEventStreamOverflow(error);
5776
+ return { ok: false, phase: "serialization", error };
5777
+ }
5778
+ }
5779
+ async function settleAcceptedTool(options) {
5780
+ const internal = await executeAcceptedTool(options);
5781
+ if (internal.kind === "cancelled") {
5782
+ return {
5783
+ outcome: { status: "cancelled", cancellation: internal.cancellation },
5784
+ abortError: internal.error
5785
+ };
5786
+ }
5787
+ if (internal.kind === "failed" && internal.failure.disposition === "abort") {
5788
+ return {
5789
+ outcome: { status: "failed", failure: internal.failure },
5790
+ abortError: internal.error
5791
+ };
5792
+ }
5793
+ const result = internal.kind === "succeeded" ? internal.result : void 0;
5794
+ const outputPhase = internal.kind === "succeeded" && !options.invocation.configuredTool?.sensitive && options.invocation.source.kind !== "mcp" && options.invocation.configuredTool?.toModelOutput ? "projection" : "serialization";
5795
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5796
+ return {
5797
+ outcome: {
5798
+ status: "cancelled",
5799
+ cancellation: { phase: outputPhase, result }
5800
+ },
5801
+ abortError: normalizeAbortError(options.signal.reason)
5802
+ };
5803
+ }
5804
+ const prepared = prepareToolMessage(options.invocation, internal);
5805
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5806
+ return {
5807
+ outcome: {
5808
+ status: "cancelled",
5809
+ cancellation: { phase: outputPhase, result }
5810
+ },
5811
+ abortError: normalizeAbortError(options.signal.reason)
5812
+ };
5813
+ }
5814
+ if (!prepared.ok) {
5815
+ const outputAbort = cancellationError(options.signal, prepared.error) ?? (prepared.error instanceof ToolModelOutputError ? cancellationError(options.signal, prepared.error.cause) : void 0);
5816
+ if (outputAbort !== void 0) {
5817
+ return {
5818
+ outcome: {
5819
+ status: "cancelled",
5820
+ cancellation: { phase: prepared.phase, result }
5821
+ },
5822
+ abortError: outputAbort
5823
+ };
5064
5824
  }
5065
- if (typeof output === "string") return output;
5066
- return JSON.stringify(normalize(output, "$", /* @__PURE__ */ new WeakSet()));
5067
- } catch (cause) {
5068
- throw new ToolModelOutputError(toolName, cause);
5825
+ const failure = {
5826
+ phase: prepared.phase,
5827
+ kind: "output",
5828
+ disposition: "abort",
5829
+ error: eventError(prepared.error),
5830
+ result
5831
+ };
5832
+ return {
5833
+ outcome: { status: "failed", failure },
5834
+ abortError: prepared.error
5835
+ };
5836
+ }
5837
+ if (internal.kind === "succeeded") {
5838
+ return {
5839
+ outcome: { status: "succeeded", result: internal.result },
5840
+ providerContent: prepared.content
5841
+ };
5842
+ }
5843
+ if (internal.kind === "denied") {
5844
+ return {
5845
+ outcome: { status: "denied", ...internal.reason ? { reason: internal.reason } : {} },
5846
+ providerContent: prepared.content
5847
+ };
5848
+ }
5849
+ return {
5850
+ outcome: { status: "failed", failure: internal.failure },
5851
+ providerContent: prepared.content
5852
+ };
5853
+ }
5854
+ function recordToolSpanOutcome(span, outcome) {
5855
+ span.setAttribute("axl.tool.outcome", outcome.status);
5856
+ span.setAttribute("axl.tool.success", outcome.status === "succeeded");
5857
+ switch (outcome.status) {
5858
+ case "succeeded":
5859
+ span.setStatus("ok");
5860
+ return;
5861
+ case "denied":
5862
+ span.setStatus("unset");
5863
+ return;
5864
+ case "failed":
5865
+ span.setAttribute("axl.tool.phase", outcome.failure.phase);
5866
+ span.setStatus("error");
5867
+ return;
5868
+ case "cancelled":
5869
+ span.setAttribute("axl.tool.phase", outcome.cancellation.phase);
5870
+ span.setStatus("error");
5069
5871
  }
5070
5872
  }
5071
5873
 
5072
5874
  // src/context.ts
5073
5875
  var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
5074
- function isErrorShaped(value) {
5075
- return value !== null && typeof value === "object" && "error" in value;
5876
+ var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
5877
+ function resolveBranchDrainTimeoutMs(value) {
5878
+ const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
5879
+ if (typeof resolved !== "number" || Number.isNaN(resolved) || !Number.isFinite(resolved) || resolved < 0) {
5880
+ throw new AxlError(
5881
+ "INVALID_CONFIG",
5882
+ `branchDrainTimeoutMs must be a non-negative finite number (got ${String(resolved)})`
5883
+ );
5884
+ }
5885
+ return resolved;
5076
5886
  }
5077
- function isErrorShapedSafely(value) {
5887
+ function parseHumanDecision(value) {
5888
+ const invalid2 = () => {
5889
+ throw new AxlError(
5890
+ "INVALID_HUMAN_DECISION",
5891
+ "Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
5892
+ );
5893
+ };
5894
+ if (typeof value !== "object" || value === null || Array.isArray(value)) invalid2();
5895
+ let prototype;
5078
5896
  try {
5079
- return isErrorShaped(value);
5897
+ prototype = Object.getPrototypeOf(value);
5080
5898
  } catch {
5081
- return false;
5899
+ return invalid2();
5082
5900
  }
5083
- }
5084
- function recordToolSpanResult(span, value, suppressInspectionErrors) {
5085
- let isError = false;
5086
- let errorMessage;
5087
- const inspect = () => {
5088
- if (isErrorShaped(value)) {
5089
- isError = true;
5090
- errorMessage = value.error;
5091
- } else {
5092
- isError = false;
5093
- }
5094
- };
5095
- if (suppressInspectionErrors) {
5901
+ if (prototype !== Object.prototype && prototype !== null) invalid2();
5902
+ let ownKeys;
5903
+ try {
5904
+ ownKeys = Reflect.ownKeys(value);
5905
+ } catch {
5906
+ return invalid2();
5907
+ }
5908
+ if (ownKeys.some(
5909
+ (key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
5910
+ )) {
5911
+ invalid2();
5912
+ }
5913
+ const readDataProperty = (key) => {
5914
+ let descriptor;
5096
5915
  try {
5097
- inspect();
5916
+ descriptor = Object.getOwnPropertyDescriptor(value, key);
5098
5917
  } catch {
5099
- isError = false;
5100
- errorMessage = void 0;
5918
+ return invalid2();
5919
+ }
5920
+ if (!descriptor) {
5921
+ if (ownKeys.includes(key)) invalid2();
5922
+ return { present: false, value: void 0 };
5923
+ }
5924
+ if (!ownKeys.includes(key)) return invalid2();
5925
+ if (!("value" in descriptor)) return invalid2();
5926
+ return { present: true, value: descriptor.value };
5927
+ };
5928
+ const approved = readDataProperty("approved");
5929
+ const data = readDataProperty("data");
5930
+ const reason = readDataProperty("reason");
5931
+ if (approved.present && approved.value === true && !reason.present) {
5932
+ if (data.value === void 0 || typeof data.value === "string") {
5933
+ return { approved: true, ...data.value === void 0 ? {} : { data: data.value } };
5101
5934
  }
5102
- } else {
5103
- inspect();
5104
- }
5105
- span.setAttribute("axl.tool.success", !isError);
5106
- if (isError) span.setStatus("error", errorMessage);
5107
- }
5108
- function prepareToolModelOutput(toolName, project) {
5109
- let output;
5110
- try {
5111
- output = project();
5112
- } catch (cause) {
5113
- throw new ToolModelOutputError(toolName, cause);
5114
5935
  }
5115
- return serializeToolModelOutput(toolName, output);
5116
- }
5117
- function finalizeToolResult(options) {
5118
- const { toolName, configuredTool, outcome, legacyContent, emitEnd, beforeProjection } = options;
5119
- const projectionEligible = configuredTool !== void 0 && !configuredTool.sensitive && outcome.kind === "returned" && configuredTool.toModelOutput !== void 0;
5120
- if (projectionEligible) {
5121
- emitEnd();
5122
- beforeProjection();
5123
- const project = configuredTool.toModelOutput;
5124
- return prepareToolModelOutput(toolName, () => project(outcome.value));
5936
+ if (approved.present && approved.value === false && !data.present) {
5937
+ if (reason.value === void 0 || typeof reason.value === "string") {
5938
+ return {
5939
+ approved: false,
5940
+ ...reason.value === void 0 ? {} : { reason: reason.value }
5941
+ };
5942
+ }
5125
5943
  }
5126
- const content = legacyContent();
5127
- emitEnd();
5128
- return content;
5944
+ return invalid2();
5129
5945
  }
5130
5946
  var askStorage = new import_node_async_hooks.AsyncLocalStorage();
5131
5947
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
5132
5948
  function zodToJsonSchema(schema) {
5133
5949
  const cached = inlineSchemaCache.get(schema);
5134
5950
  if (cached !== void 0) return cached;
5135
- const result = import_zod.z.toJSONSchema(schema, { unrepresentable: "any" });
5951
+ const result = import_zod2.z.toJSONSchema(schema, { unrepresentable: "any" });
5136
5952
  delete result.$schema;
5137
5953
  Object.freeze(result);
5138
5954
  inlineSchemaCache.set(schema, result);
@@ -5151,7 +5967,7 @@ var nativeSchemaCache = /* @__PURE__ */ new WeakMap();
5151
5967
  function deriveNativeSchema(schema) {
5152
5968
  const cached = nativeSchemaCache.get(schema);
5153
5969
  if (cached !== void 0) return cached;
5154
- const json = import_zod.z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
5970
+ const json = import_zod2.z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
5155
5971
  delete json.$schema;
5156
5972
  nativeSchemaCache.set(schema, json);
5157
5973
  return json;
@@ -5159,7 +5975,7 @@ function deriveNativeSchema(schema) {
5159
5975
  function renderSchemaForPrompt(schema) {
5160
5976
  const cached = promptSchemaCache.get(schema);
5161
5977
  if (cached !== void 0) return cached;
5162
- const json = import_zod.z.toJSONSchema(schema, {
5978
+ const json = import_zod2.z.toJSONSchema(schema, {
5163
5979
  unrepresentable: "any",
5164
5980
  io: "input",
5165
5981
  reused: "ref"
@@ -5260,9 +6076,8 @@ var WorkflowContext = class _WorkflowContext {
5260
6076
  providerRegistry;
5261
6077
  sessionHistory;
5262
6078
  onTrace;
5263
- onToken;
5264
- onToolCall;
5265
6079
  pendingDecisions;
6080
+ awaitHumanClaims;
5266
6081
  budgetContext;
5267
6082
  stateStore;
5268
6083
  /** Root step counter for this execution. Inherited by every ctx.ask()
@@ -5283,27 +6098,27 @@ var WorkflowContext = class _WorkflowContext {
5283
6098
  * the caller-supplied key.
5284
6099
  */
5285
6100
  autoCheckpointCounters;
5286
- /** Idempotency guards for `workflow_start` / `workflow_end` emission.
6101
+ branchDrainState;
6102
+ /** Root-shared idempotency guards for workflow lifecycle emission.
5287
6103
  * Both `runtime.execute()` and `runtime.stream()` have paths where a
5288
6104
  * post-emit side-effect (checkpoint deletion, state-store persistence)
5289
6105
  * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
5290
6106
  * fired — the outer catch would then fire `_emitWorkflowEnd({status:
5291
6107
  * 'failed'})` for a second time. These flags make both emitters
5292
6108
  * single-fire so consumers never see paired-then-conflicting terminal
5293
- * events for one execution. */
5294
- _workflowStartEmitted = false;
5295
- _workflowEndEmitted = false;
6109
+ * events for one execution. Sharing the state with child contexts also
6110
+ * suppresses late branch events after bounded finalization. */
6111
+ workflowLifecycleState;
5296
6112
  signal;
5297
6113
  summaryCache;
5298
6114
  workflowName;
5299
6115
  mcpManager;
5300
6116
  spanManager;
5301
6117
  memoryManager;
5302
- resumeMode;
5303
6118
  toolOverrides;
5304
6119
  awaitHumanHandler;
5305
- onAgentStart;
5306
6120
  onAgentCallComplete;
6121
+ _onDecisionCleanupFailed;
5307
6122
  /** Shared mutable slot for the lazy `AxlEventBus`. The slot is shared
5308
6123
  * by reference across parent/child contexts so a late allocation
5309
6124
  * (consumer subscribes after a child context exists) is visible
@@ -5312,6 +6127,7 @@ var WorkflowContext = class _WorkflowContext {
5312
6127
  * contexts that never observe). */
5313
6128
  _busRef;
5314
6129
  _eventStreamOptions;
6130
+ _forceStreaming;
5315
6131
  /** Removes the constructor-registered `'abort'` listener from
5316
6132
  * `this.signal` so a long-lived signal (e.g., a request-scoped or
5317
6133
  * process-wide signal reused across many `runtime.createContext()` /
@@ -5331,8 +6147,7 @@ var WorkflowContext = class _WorkflowContext {
5331
6147
  * *between* asks. `ctx.events.partialObjects` is the coalescing view
5332
6148
  * designed for streaming-structured-output UIs.
5333
6149
  * - **Ad-hoc contexts** from `runtime.createContext()`: the same
5334
- * iterable replaces the legacy `onToken` / `onToolCall` /
5335
- * `onAgentStart` callbacks.
6150
+ * iterable provides typed per-context observation.
5336
6151
  * - **Cross-execution**: prefer `runtime.on('trace', …)` instead — that
5337
6152
  * fans out across all executions, while `ctx.events` is per-context.
5338
6153
  *
@@ -5347,9 +6162,9 @@ var WorkflowContext = class _WorkflowContext {
5347
6162
  *
5348
6163
  * **Subscribe before the first `ctx.ask()`.** The streaming code
5349
6164
  * path inside `ctx.ask()` activates only when an observer is
5350
- * present at the time the ask starts (`_streamingEnabled` — either
5351
- * the legacy `onToken` callback is set or `ctx.events` has been
5352
- * allocated). If you allocate `ctx.events` AFTER a `ctx.ask()` has
6165
+ * present at the time the ask starts (`_streamingEnabled` — an explicit
6166
+ * runtime streaming mode or allocated `ctx.events`). If you allocate
6167
+ * `ctx.events` AFTER a `ctx.ask()` has
5353
6168
  * begun, that in-flight ask will not stream `token` /
5354
6169
  * `partial_object` events at all (the agent loop went through
5355
6170
  * `provider.chat` instead of `provider.stream`). Subsequent asks
@@ -5388,18 +6203,16 @@ var WorkflowContext = class _WorkflowContext {
5388
6203
  this._busRef.current?._finish();
5389
6204
  this.abortListenerCleanup?.();
5390
6205
  }
5391
- /** True if any observer wants per-token streaming for asks started
5392
- * from this context: either the legacy `onToken` callback is set
5393
- * (e.g., `runtime.stream()` plumbs a sentinel) OR `ctx.events` has
5394
- * been allocated (a workflow-handler consumer subscribed before
5395
- * the ask). `ctx.ask` reads this to decide whether to enter the
6206
+ /** True when asks should use provider streaming: `runtime.stream()` set an
6207
+ * explicit internal mode or `ctx.events` has been allocated. `ctx.ask`
6208
+ * reads this to decide whether to enter the
5396
6209
  * streaming code path; the gate is re-checked per ask, so a
5397
6210
  * consumer subscribing AFTER the first ask started still gets
5398
6211
  * streaming on the next one. The check `_busRef.current !== undefined`
5399
6212
  * is a one-way flag — `_finish` does not unset the reference, so
5400
6213
  * once an observer was present, every subsequent ask streams. */
5401
6214
  get _streamingEnabled() {
5402
- return this.onToken !== void 0 || this._busRef.current !== void 0;
6215
+ return this._forceStreaming || this._busRef.current !== void 0;
5403
6216
  }
5404
6217
  constructor(init) {
5405
6218
  this.input = init.input;
@@ -5409,9 +6222,8 @@ var WorkflowContext = class _WorkflowContext {
5409
6222
  this.providerRegistry = init.providerRegistry;
5410
6223
  this.sessionHistory = init.sessionHistory ?? [];
5411
6224
  this.onTrace = init.onTrace;
5412
- this.onToken = init.onToken;
5413
- this.onToolCall = init.onToolCall;
5414
6225
  this.pendingDecisions = init.pendingDecisions;
6226
+ this.awaitHumanClaims = init.awaitHumanClaims ?? /* @__PURE__ */ new Map();
5415
6227
  this.budgetContext = init.budgetContext;
5416
6228
  this.stateStore = init.stateStore;
5417
6229
  this.signal = init.signal;
@@ -5419,13 +6231,13 @@ var WorkflowContext = class _WorkflowContext {
5419
6231
  this.mcpManager = init.mcpManager;
5420
6232
  this.spanManager = init.spanManager;
5421
6233
  this.memoryManager = init.memoryManager;
5422
- this.resumeMode = init.resumeMode ?? false;
5423
6234
  this.toolOverrides = init.toolOverrides;
5424
6235
  this.awaitHumanHandler = init.awaitHumanHandler;
5425
- this.onAgentStart = init.onAgentStart;
5426
6236
  this.onAgentCallComplete = init.onAgentCallComplete;
6237
+ this._onDecisionCleanupFailed = init._onDecisionCleanupFailed;
5427
6238
  this._busRef = init._busRef ?? { current: void 0 };
5428
6239
  this._eventStreamOptions = init.eventStreamOptions;
6240
+ this._forceStreaming = init._forceStreaming ?? false;
5429
6241
  if (init._busRef === void 0 && this.signal && !this.signal.aborted) {
5430
6242
  const signal = this.signal;
5431
6243
  const onAbort = () => {
@@ -5441,6 +6253,15 @@ var WorkflowContext = class _WorkflowContext {
5441
6253
  byAgent: /* @__PURE__ */ new Map(),
5442
6254
  root: 0
5443
6255
  };
6256
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
6257
+ this.branchDrainState = init.branchDrainState ?? {
6258
+ pending: /* @__PURE__ */ new Set(),
6259
+ timeoutMs: branchDrainTimeoutMs
6260
+ };
6261
+ this.workflowLifecycleState = init.workflowLifecycleState ?? {
6262
+ startEmitted: false,
6263
+ endEmitted: false
6264
+ };
5444
6265
  if (init.metadata?.summaryCache) {
5445
6266
  this.summaryCache = init.metadata.summaryCache;
5446
6267
  }
@@ -5449,16 +6270,9 @@ var WorkflowContext = class _WorkflowContext {
5449
6270
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
5450
6271
  * Shares: budget tracking, abort signals, trace emission, provider registry,
5451
6272
  * state store, span manager, memory manager, MCP manager, config,
5452
- * awaitHuman handler, pending decisions, tool overrides, AND the
5453
- * streaming callbacks (onToken / onAgentStart / onToolCall).
6273
+ * awaitHuman handler, pending decisions, tool overrides, and event bus.
5454
6274
  * Isolates: session history.
5455
6275
  *
5456
- * Streaming callbacks now propagate into nested asks because every callback
5457
- * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
5458
- * consumers that want root-only behavior can filter on `meta.depth === 0`
5459
- * instead of relying on the runtime to drop nested events. This is the
5460
- * "nested ask visibility" fix from spec §3.2.
5461
- *
5462
6276
  * The shared step counter lives in `askStorage` (ALS), so there is no
5463
6277
  * per-context counter to pass through anymore. Nested events are
5464
6278
  * correlated to their parent via `parentAskId` (set on every nested ask
@@ -5479,12 +6293,11 @@ var WorkflowContext = class _WorkflowContext {
5479
6293
  spanManager: this.spanManager,
5480
6294
  memoryManager: this.memoryManager,
5481
6295
  onTrace: this.onTrace,
5482
- onToken: this.onToken,
5483
- onToolCall: this.onToolCall,
5484
- onAgentStart: this.onAgentStart,
5485
6296
  onAgentCallComplete: this.onAgentCallComplete,
6297
+ _onDecisionCleanupFailed: this._onDecisionCleanupFailed,
5486
6298
  awaitHumanHandler: this.awaitHumanHandler,
5487
6299
  pendingDecisions: this.pendingDecisions,
6300
+ awaitHumanClaims: this.awaitHumanClaims,
5488
6301
  toolOverrides: this.toolOverrides,
5489
6302
  signal: this.signal,
5490
6303
  workflowName: this.workflowName,
@@ -5492,12 +6305,15 @@ var WorkflowContext = class _WorkflowContext {
5492
6305
  // emitted from `ctx.ask` / spawn / race / parallel / map in this
5493
6306
  // child context don't collide with the parent's in the state store.
5494
6307
  autoCheckpointCounters: this.autoCheckpointCounters,
6308
+ branchDrainState: this.branchDrainState,
6309
+ workflowLifecycleState: this.workflowLifecycleState,
5495
6310
  // Share the parent's bus slot (mutable ref). Late allocation in
5496
6311
  // either parent or child propagates because both read the same
5497
6312
  // slot. Nested-ask events (agent-as-tool pattern) bubble up to
5498
6313
  // the parent's iterator with `parentAskId`/`depth` correlation
5499
6314
  // intact.
5500
6315
  _busRef: this._busRef,
6316
+ _forceStreaming: this._forceStreaming,
5501
6317
  eventStreamOptions: this._eventStreamOptions
5502
6318
  // Isolated: sessionHistory (empty)
5503
6319
  });
@@ -5510,28 +6326,194 @@ var WorkflowContext = class _WorkflowContext {
5510
6326
  get currentSignal() {
5511
6327
  return signalStorage.getStore() ?? this.signal;
5512
6328
  }
5513
- /**
5514
- * Build a `CallbackMeta` for the current ask frame. Used at every
5515
- * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
5516
- * route or filter by ask correlation (`meta.depth === 0` for root
5517
- * chat UIs, etc.).
5518
- *
5519
- * If the call is somehow outside an ask frame (e.g., a programmatic
5520
- * `tool.run()` test harness), falls back to a synthetic meta keyed off
5521
- * `executionId` so the type contract holds — consumers see `depth: 0`
5522
- * and can ignore those events.
5523
- */
5524
- currentCallbackMeta(agentName) {
5525
- const frame = askStorage.getStore();
5526
- if (frame) {
5527
- return {
5528
- askId: frame.askId,
5529
- ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
5530
- depth: frame.depth,
5531
- agent: agentName
6329
+ _registerBranchDrain(promise) {
6330
+ const state = this.branchDrainState;
6331
+ state.pending.add(promise);
6332
+ void promise.then(
6333
+ () => state.pending.delete(promise),
6334
+ (error) => {
6335
+ state.pending.delete(promise);
6336
+ if (!state.overflow && isEventStreamOverflowError(error)) {
6337
+ state.overflow = error;
6338
+ }
6339
+ }
6340
+ );
6341
+ }
6342
+ /** Wait for race/quorum losers (including nested branch work) to settle.
6343
+ * Primitive calls still resolve as soon as their winner/quorum is known;
6344
+ * this is only the runtime's terminal/persistence barrier. */
6345
+ async _drainBranchWork() {
6346
+ const state = this.branchDrainState;
6347
+ state.deadline ??= Date.now() + state.timeoutMs;
6348
+ while (state.pending.size > 0) {
6349
+ const overflow2 = state.overflow;
6350
+ state.overflow = void 0;
6351
+ if (overflow2) throw overflow2;
6352
+ const remaining = state.deadline - Date.now();
6353
+ if (remaining <= 0) {
6354
+ return {
6355
+ complete: false,
6356
+ reason: "branch_drain_timeout",
6357
+ pendingContinuations: state.pending.size,
6358
+ timeoutMs: state.timeoutMs
6359
+ };
6360
+ }
6361
+ let timer;
6362
+ const settled = await Promise.race([
6363
+ Promise.allSettled([...state.pending]).then(() => true),
6364
+ new Promise((resolve2) => {
6365
+ timer = setTimeout(() => resolve2(false), remaining);
6366
+ })
6367
+ ]);
6368
+ if (timer) clearTimeout(timer);
6369
+ if (!settled) {
6370
+ return {
6371
+ complete: false,
6372
+ reason: "branch_drain_timeout",
6373
+ pendingContinuations: state.pending.size,
6374
+ timeoutMs: state.timeoutMs
6375
+ };
6376
+ }
6377
+ }
6378
+ const overflow = state.overflow;
6379
+ state.overflow = void 0;
6380
+ if (overflow) throw overflow;
6381
+ state.deadline = void 0;
6382
+ return { complete: true };
6383
+ }
6384
+ /** Resolve, execute, and observe one non-handoff tool request. Rejections
6385
+ * never start. Every accepted request emits one terminal event before a
6386
+ * continuation message or abort escapes this coordinator. */
6387
+ async observeToolInvocation(agent2, toolCall, currentMessages, availableTools) {
6388
+ const signal = this.currentSignal;
6389
+ signal?.throwIfAborted();
6390
+ const requestedTool = toolCall.function.name;
6391
+ const configuredTool = agent2._config.tools?.find(
6392
+ (candidate) => candidate.name === requestedTool
6393
+ );
6394
+ const override = this.toolOverrides?.get(requestedTool);
6395
+ const mcpManager = this.mcpManager;
6396
+ const mcpMatch = !configuredTool ? mcpManager?.getToolsForAgent(agent2._config.mcp, agent2._config.mcpTools).find(({ tool: tool2 }) => tool2.name === requestedTool) : void 0;
6397
+ const parsed = parseToolInvocation({
6398
+ toolCall,
6399
+ configuredTool,
6400
+ override,
6401
+ ...mcpMatch && mcpManager ? {
6402
+ mcpCall: (args) => mcpManager.callTool(requestedTool, args, mcpMatch.server),
6403
+ mcpTraceName: `${mcpMatch.server}:${requestedTool}`
6404
+ } : {},
6405
+ availableTools
6406
+ });
6407
+ signal?.throwIfAborted();
6408
+ if ("kind" in parsed) {
6409
+ this.emitEvent({
6410
+ type: "tool_call_rejected",
6411
+ agent: agent2._name,
6412
+ tool: parsed.toolName,
6413
+ callId: parsed.callId,
6414
+ data: parsed.data
6415
+ });
6416
+ currentMessages.push({
6417
+ role: "tool",
6418
+ content: parsed.modelMessage,
6419
+ tool_call_id: parsed.callId
6420
+ });
6421
+ return;
6422
+ }
6423
+ const startArgs = cloneToolArguments(parsed.args);
6424
+ this.emitEvent({
6425
+ type: "tool_call_start",
6426
+ agent: agent2._name,
6427
+ tool: parsed.toolName,
6428
+ callId: parsed.callId,
6429
+ data: {
6430
+ args: startArgs,
6431
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {}
6432
+ }
6433
+ });
6434
+ const startedAt = Date.now();
6435
+ const requestApproval = async () => {
6436
+ const approve = async () => {
6437
+ const approvalArgs = cloneToolArguments(parsed.args);
6438
+ const decision = await this.awaitHuman({
6439
+ channel: "tool_approval",
6440
+ prompt: `Tool "${parsed.toolName}" wants to execute with args: ${JSON.stringify(parsed.args)}`,
6441
+ metadata: { toolName: parsed.toolName, args: approvalArgs, agent: agent2._name }
6442
+ });
6443
+ return decision.approved ? { approved: true } : { approved: false, reason: decision.reason ?? "Denied by human" };
5532
6444
  };
6445
+ const approval = this.spanManager ? await this.spanManager.withSpanAsync(
6446
+ "axl.tool.approval",
6447
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6448
+ async (span) => {
6449
+ const value = await approve();
6450
+ span.setAttribute("axl.tool.approval.approved", value.approved);
6451
+ return value;
6452
+ }
6453
+ ) : await approve();
6454
+ this.emitEvent({
6455
+ type: "tool_approval",
6456
+ agent: agent2._name,
6457
+ tool: parsed.toolName,
6458
+ callId: parsed.callId,
6459
+ data: {
6460
+ approved: approval.approved,
6461
+ args: cloneToolArguments(parsed.args),
6462
+ ...approval.reason ? { reason: approval.reason } : {}
6463
+ }
6464
+ });
6465
+ return approval;
6466
+ };
6467
+ let terminalEmitted = false;
6468
+ const executeAndObserve = async (span) => {
6469
+ const settlement2 = await settleAcceptedTool({
6470
+ invocation: parsed,
6471
+ context: this,
6472
+ signal,
6473
+ requestApproval,
6474
+ createChildContext: () => this.createChildContext()
6475
+ });
6476
+ const duration = Date.now() - startedAt;
6477
+ span?.setAttribute("axl.tool.duration", duration);
6478
+ if (span) recordToolSpanOutcome(span, settlement2.outcome);
6479
+ if (terminalEmitted) {
6480
+ throw new Error(`Tool invocation ${parsed.callId} emitted more than one terminal event`);
6481
+ }
6482
+ terminalEmitted = true;
6483
+ try {
6484
+ this.emitEvent({
6485
+ type: "tool_call_end",
6486
+ agent: agent2._name,
6487
+ tool: parsed.toolName,
6488
+ callId: parsed.callId,
6489
+ duration,
6490
+ data: {
6491
+ args: cloneToolArguments(parsed.args),
6492
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {},
6493
+ outcome: settlement2.outcome
6494
+ }
6495
+ });
6496
+ } catch (error) {
6497
+ if (error instanceof Error && settlement2.abortError !== void 0) {
6498
+ throw preserveErrorCause(error, settlement2.abortError);
6499
+ }
6500
+ throw error;
6501
+ }
6502
+ return settlement2;
6503
+ };
6504
+ const settlement = this.spanManager ? await this.spanManager.withSpanAsync(
6505
+ "axl.tool.call",
6506
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6507
+ executeAndObserve
6508
+ ) : await executeAndObserve();
6509
+ if (settlement.providerContent !== void 0) {
6510
+ currentMessages.push({
6511
+ role: "tool",
6512
+ content: settlement.providerContent,
6513
+ tool_call_id: parsed.callId
6514
+ });
5533
6515
  }
5534
- return { askId: this.executionId, depth: 0, agent: agentName };
6516
+ if (settlement.abortError !== void 0) throw settlement.abortError;
5535
6517
  }
5536
6518
  // ── ctx.ask() ─────────────────────────────────────────────────────────
5537
6519
  async ask(agent2, prompt, options) {
@@ -5560,18 +6542,21 @@ var WorkflowContext = class _WorkflowContext {
5560
6542
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
5561
6543
  const usageCapture = {};
5562
6544
  const doCall = async () => {
5563
- const result = await this.executeAgentCall(
6545
+ const result2 = await this.executeAgentCall(
5564
6546
  agent2,
5565
6547
  prompt,
5566
6548
  options,
5567
6549
  void 0,
5568
6550
  usageCapture
5569
6551
  );
5570
- return result;
6552
+ return result2;
5571
6553
  };
5572
6554
  let outcome;
6555
+ let askFailure;
6556
+ let askFailed = false;
6557
+ let result;
5573
6558
  try {
5574
- const result = this.spanManager ? await this.spanManager.withSpanAsync(
6559
+ result = this.spanManager ? await this.spanManager.withSpanAsync(
5575
6560
  "axl.agent.ask",
5576
6561
  {
5577
6562
  "axl.agent.name": agent2._name,
@@ -5623,43 +6608,41 @@ var WorkflowContext = class _WorkflowContext {
5623
6608
  stop: options?.stop ?? agent2._config.stop,
5624
6609
  providerOptions: options?.providerOptions ?? agent2._config.providerOptions
5625
6610
  });
5626
- } catch (hookErr) {
5627
- console.error(
5628
- "[axl] onAgentCallComplete hook threw; ask outcome unchanged:",
5629
- hookErr instanceof Error ? hookErr.message : String(hookErr)
5630
- );
5631
- }
5632
- }
5633
- return result;
5634
- } catch (err) {
5635
- outcome = {
5636
- ok: false,
5637
- error: err instanceof Error ? err.message : String(err)
5638
- };
5639
- throw err;
5640
- } finally {
5641
- try {
5642
- this.emitEvent({
5643
- type: "ask_end",
5644
- outcome: outcome ?? {
5645
- ok: false,
5646
- error: "ask_end emitted without outcome \u2014 internal bug"
5647
- },
5648
- cost: frame.askCost.value,
5649
- ...frame.askUnpriced ? { unpriced: true } : {},
5650
- duration: Date.now() - askStart
5651
- });
5652
- } catch (emitErr) {
5653
- if (emitErr instanceof EventStreamOverflowError && outcome && !outcome.ok) {
6611
+ } catch (hookErr) {
5654
6612
  console.error(
5655
- "[axl] ask_end emit overflowed during error path; preserving original error:",
5656
- emitErr.message
6613
+ "[axl] onAgentCallComplete hook threw; ask outcome unchanged:",
6614
+ hookErr instanceof Error ? hookErr.message : String(hookErr)
5657
6615
  );
5658
- } else {
5659
- throw emitErr;
5660
6616
  }
5661
6617
  }
6618
+ } catch (err) {
6619
+ askFailed = true;
6620
+ askFailure = err;
6621
+ outcome = {
6622
+ ok: false,
6623
+ error: err instanceof Error ? err.message : String(err)
6624
+ };
6625
+ }
6626
+ let terminalFailure;
6627
+ let terminalFailed = false;
6628
+ try {
6629
+ this.emitEvent({
6630
+ type: "ask_end",
6631
+ outcome: outcome ?? {
6632
+ ok: false,
6633
+ error: "ask_end emitted without outcome \u2014 internal bug"
6634
+ },
6635
+ cost: frame.askCost.value,
6636
+ ...frame.askUnpriced ? { unpriced: true } : {},
6637
+ duration: Date.now() - askStart
6638
+ });
6639
+ } catch (error) {
6640
+ terminalFailed = true;
6641
+ terminalFailure = isEventStreamOverflowError(error) ? preserveErrorCause(error, askFailure) : error;
5662
6642
  }
6643
+ if (terminalFailed) throw terminalFailure;
6644
+ if (askFailed) throw askFailure;
6645
+ return result;
5663
6646
  });
5664
6647
  },
5665
6648
  { agent: agentName }
@@ -5881,7 +6864,6 @@ ${appendedSchemaText}`;
5881
6864
  }
5882
6865
  } : { type: "json_object" };
5883
6866
  }
5884
- const callbackMeta = this.currentCallbackMeta(agent2._name);
5885
6867
  const callParams = {
5886
6868
  ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
5887
6869
  maxTokens: chatOptions.maxTokens,
@@ -5920,7 +6902,6 @@ ${appendedSchemaText}`;
5920
6902
  ...messagesSnapshot ? { messages: messagesSnapshot } : {}
5921
6903
  }
5922
6904
  });
5923
- this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
5924
6905
  let response;
5925
6906
  try {
5926
6907
  this.currentSignal?.throwIfAborted();
@@ -5930,7 +6911,7 @@ ${appendedSchemaText}`;
5930
6911
  const toolCallBuffers = /* @__PURE__ */ new Map();
5931
6912
  let streamProviderMetadata;
5932
6913
  let thinkingContent = "";
5933
- const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod.z.ZodObject;
6914
+ const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod2.z.ZodObject;
5934
6915
  const currentAttempt = schemaRetries + 1;
5935
6916
  const walker = streamingObjectEnabled ? new StreamingWalker({
5936
6917
  onStringDelta: (path, delta) => {
@@ -5946,7 +6927,6 @@ ${appendedSchemaText}`;
5946
6927
  if (chunk.type === "text_delta") {
5947
6928
  content2 += chunk.content;
5948
6929
  this.emitEvent({ type: "token", data: chunk.content });
5949
- this.onToken?.(chunk.content, callbackMeta);
5950
6930
  if (walker) {
5951
6931
  walker.processChunk(chunk.content);
5952
6932
  if (walker.consumeBoundary()) {
@@ -6056,6 +7036,7 @@ ${appendedSchemaText}`;
6056
7036
  ...retryReason ? { retryReason } : {}
6057
7037
  }
6058
7038
  });
7039
+ this.currentSignal?.throwIfAborted();
6059
7040
  if (response.tool_calls && response.tool_calls.length > 0) {
6060
7041
  currentMessages.push({
6061
7042
  role: "assistant",
@@ -6225,263 +7206,12 @@ ${appendedSchemaText}`;
6225
7206
  return await handoffFn();
6226
7207
  }
6227
7208
  }
6228
- const tool2 = agent2._config.tools?.find((candidate) => candidate.name === toolName);
6229
- const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
6230
- const toolOverride = this.toolOverrides?.get(toolName);
6231
- if (toolOverride) {
6232
- let toolArgs2;
6233
- try {
6234
- toolArgs2 = JSON.parse(toolCall.function.arguments);
6235
- } catch {
6236
- currentMessages.push({
6237
- role: "tool",
6238
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6239
- tool_call_id: toolCall.id
6240
- });
6241
- continue;
6242
- }
6243
- this.emitEvent({
6244
- type: "tool_call_start",
6245
- tool: toolName,
6246
- callId: toolCall.id,
6247
- data: { args: toolArgs2 }
6248
- });
6249
- this.onToolCall?.(
6250
- { name: toolName, args: toolArgs2, callId: toolCall.id },
6251
- callbackMeta
6252
- );
6253
- const toolStart2 = Date.now();
6254
- const suppressErrorShapeInspection2 = tool2?.sensitive === true || tool2?.toModelOutput !== void 0;
6255
- const executeOverride = async () => {
6256
- try {
6257
- return { kind: "returned", value: await toolOverride(toolArgs2) };
6258
- } catch (err) {
6259
- return {
6260
- kind: "thrown",
6261
- value: { error: err instanceof Error ? err.message : String(err) }
6262
- };
6263
- }
6264
- };
6265
- const outcome = this.spanManager ? await this.spanManager.withSpanAsync(
6266
- "axl.tool.call",
6267
- {
6268
- "axl.tool.name": toolName,
6269
- "axl.agent.name": agent2._name
6270
- },
6271
- async (span) => {
6272
- const result = await executeOverride();
6273
- span.setAttribute("axl.tool.duration", Date.now() - toolStart2);
6274
- recordToolSpanResult(span, result.value, suppressErrorShapeInspection2);
6275
- return result;
6276
- }
6277
- ) : await executeOverride();
6278
- const toolResult2 = outcome.value;
6279
- const resultContent2 = finalizeToolResult({
6280
- toolName,
6281
- configuredTool: tool2,
6282
- outcome,
6283
- legacyContent: () => tool2?.sensitive ? "[REDACTED - sensitive tool output]" : JSON.stringify(toolResult2),
6284
- emitEnd: () => {
6285
- const toolDuration2 = Date.now() - toolStart2;
6286
- this.emitEvent({
6287
- type: "tool_call_end",
6288
- agent: agent2._name,
6289
- tool: toolName,
6290
- duration: toolDuration2,
6291
- data: { args: toolArgs2, result: toolResult2, callId: toolCall.id }
6292
- });
6293
- },
6294
- beforeProjection: () => this.currentSignal?.throwIfAborted()
6295
- });
6296
- currentMessages.push({
6297
- role: "tool",
6298
- content: resultContent2,
6299
- tool_call_id: toolCall.id
6300
- });
6301
- continue;
6302
- }
6303
- if (!tool2 && !isMcpTool) {
6304
- this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
6305
- currentMessages.push({
6306
- role: "tool",
6307
- content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
6308
- tool_call_id: toolCall.id
6309
- });
6310
- continue;
6311
- }
6312
- let toolArgs;
6313
- try {
6314
- toolArgs = JSON.parse(toolCall.function.arguments);
6315
- } catch {
6316
- currentMessages.push({
6317
- role: "tool",
6318
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6319
- tool_call_id: toolCall.id
6320
- });
6321
- continue;
6322
- }
6323
- this.emitEvent({
6324
- type: "tool_call_start",
6325
- tool: toolName,
6326
- callId: toolCall.id,
6327
- data: { args: toolArgs }
6328
- });
6329
- this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
6330
- const toolStart = Date.now();
6331
- const suppressErrorShapeInspection = tool2?.sensitive === true || tool2?.toModelOutput !== void 0;
6332
- if (tool2 && tool2.requireApproval) {
6333
- const approvalFn = async () => {
6334
- const decision = await this.awaitHuman({
6335
- channel: "tool_approval",
6336
- prompt: `Tool "${toolName}" wants to execute with args: ${JSON.stringify(toolArgs)}`,
6337
- metadata: { toolName, args: toolArgs, agent: agent2._name }
6338
- });
6339
- if (!decision.approved) {
6340
- const reason = decision.reason ?? "Denied by human";
6341
- currentMessages.push({
6342
- role: "tool",
6343
- content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
6344
- tool_call_id: toolCall.id
6345
- });
6346
- return { approved: false, reason };
6347
- }
6348
- return { approved: true };
6349
- };
6350
- let approvalOutcome;
6351
- if (this.spanManager) {
6352
- approvalOutcome = await this.spanManager.withSpanAsync(
6353
- "axl.tool.approval",
6354
- {
6355
- "axl.tool.name": toolName,
6356
- "axl.agent.name": agent2._name
6357
- },
6358
- async (span) => {
6359
- const result = await approvalFn();
6360
- span.setAttribute("axl.tool.approval.approved", result.approved);
6361
- return result;
6362
- }
6363
- );
6364
- } else {
6365
- approvalOutcome = await approvalFn();
6366
- }
6367
- this.emitEvent({
6368
- type: "tool_approval",
6369
- agent: agent2._name,
6370
- tool: toolName,
6371
- data: {
6372
- approved: approvalOutcome.approved,
6373
- args: toolArgs,
6374
- ...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
6375
- }
6376
- });
6377
- if (!approvalOutcome.approved) continue;
6378
- }
6379
- if (tool2 && tool2.hooks?.before) {
6380
- try {
6381
- toolArgs = await tool2.hooks.before(toolArgs, this);
6382
- } catch (err) {
6383
- const errorMsg = err instanceof Error ? err.message : String(err);
6384
- currentMessages.push({
6385
- role: "tool",
6386
- content: JSON.stringify({ error: `Before hook error: ${errorMsg}` }),
6387
- tool_call_id: toolCall.id
6388
- });
6389
- continue;
6390
- }
6391
- }
6392
- const executeTool = async () => {
6393
- let outcome;
6394
- let resultContent2;
6395
- if (isMcpTool && this.mcpManager) {
6396
- try {
6397
- const mcpResult = await this.mcpManager.callTool(toolName, toolArgs);
6398
- outcome = { kind: "returned", value: mcpResult };
6399
- resultContent2 = mcpResult.content.map(
6400
- (c) => c.type === "text" ? c.text : `[${c.type}]`
6401
- ).join("\n");
6402
- if (mcpResult.isError) {
6403
- resultContent2 = `Error: ${resultContent2}`;
6404
- }
6405
- } catch (err) {
6406
- outcome = {
6407
- kind: "thrown",
6408
- value: { error: err instanceof Error ? err.message : String(err) }
6409
- };
6410
- resultContent2 = JSON.stringify(outcome.value);
6411
- }
6412
- } else if (tool2) {
6413
- const childCtx = this.createChildContext();
6414
- try {
6415
- outcome = { kind: "returned", value: await tool2._execute(toolArgs, childCtx) };
6416
- } catch (err) {
6417
- outcome = {
6418
- kind: "thrown",
6419
- value: { error: err instanceof Error ? err.message : String(err) }
6420
- };
6421
- }
6422
- if (outcome.kind === "returned" && tool2.hooks?.after && !(suppressErrorShapeInspection ? isErrorShapedSafely(outcome.value) : isErrorShaped(outcome.value))) {
6423
- try {
6424
- outcome = {
6425
- kind: "returned",
6426
- value: await tool2.hooks.after(outcome.value, this)
6427
- };
6428
- } catch (err) {
6429
- outcome = {
6430
- kind: "thrown",
6431
- value: {
6432
- error: `After hook error: ${err instanceof Error ? err.message : String(err)}`
6433
- }
6434
- };
6435
- }
6436
- }
6437
- if (tool2.sensitive) {
6438
- resultContent2 = "[REDACTED - sensitive tool output]";
6439
- } else if (outcome.kind === "thrown" || tool2.toModelOutput === void 0) {
6440
- resultContent2 = JSON.stringify(outcome.value);
6441
- }
6442
- } else {
6443
- outcome = { kind: "returned", value: void 0 };
6444
- resultContent2 = "Tool execution error";
6445
- }
6446
- return { outcome, resultContent: resultContent2 };
6447
- };
6448
- const traceName = isMcpTool && this.mcpManager ? this.mcpManager.getQualifiedName(toolName) ?? toolName : toolName;
6449
- const execution = this.spanManager ? await this.spanManager.withSpanAsync(
6450
- "axl.tool.call",
6451
- {
6452
- "axl.tool.name": traceName,
6453
- "axl.agent.name": agent2._name
6454
- },
6455
- async (span) => {
6456
- const r = await executeTool();
6457
- span.setAttribute("axl.tool.duration", Date.now() - toolStart);
6458
- recordToolSpanResult(span, r.outcome.value, suppressErrorShapeInspection);
6459
- return r;
6460
- }
6461
- ) : await executeTool();
6462
- const toolResult = execution.outcome.value;
6463
- const toolDuration = Date.now() - toolStart;
6464
- const resultContent = finalizeToolResult({
6465
- toolName,
6466
- configuredTool: tool2,
6467
- outcome: execution.outcome,
6468
- legacyContent: () => execution.resultContent,
6469
- emitEnd: () => {
6470
- this.emitEvent({
6471
- type: "tool_call_end",
6472
- agent: agent2._name,
6473
- tool: traceName,
6474
- duration: toolDuration,
6475
- data: { args: toolArgs, result: toolResult, callId: toolCall.id }
6476
- });
6477
- },
6478
- beforeProjection: () => this.currentSignal?.throwIfAborted()
6479
- });
6480
- currentMessages.push({
6481
- role: "tool",
6482
- content: resultContent,
6483
- tool_call_id: toolCall.id
6484
- });
7209
+ await this.observeToolInvocation(
7210
+ agent2,
7211
+ toolCall,
7212
+ currentMessages,
7213
+ toolDefs.map((tool2) => tool2.function.name)
7214
+ );
6485
7215
  }
6486
7216
  continue;
6487
7217
  }
@@ -6601,7 +7331,7 @@ ${appendedSchemaText}`;
6601
7331
  pendingRetryReason = "schema";
6602
7332
  continue;
6603
7333
  }
6604
- const zodErr = schemaErr instanceof import_zod.ZodError ? schemaErr : new import_zod.ZodError([
7334
+ const zodErr = schemaErr instanceof import_zod2.ZodError ? schemaErr : new import_zod2.ZodError([
6605
7335
  {
6606
7336
  code: "custom",
6607
7337
  path: [],
@@ -6618,6 +7348,7 @@ ${appendedSchemaText}`;
6618
7348
  metadata: this.metadata
6619
7349
  });
6620
7350
  } catch (err) {
7351
+ rethrowEventStreamOverflow(err);
6621
7352
  const reason = err instanceof Error ? err.message : String(err);
6622
7353
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
6623
7354
  }
@@ -6786,7 +7517,7 @@ ${appendedSchemaText}`;
6786
7517
  );
6787
7518
  }
6788
7519
  }
6789
- const rootIsObject = schema instanceof import_zod.z.ZodObject;
7520
+ const rootIsObject = schema instanceof import_zod2.z.ZodObject;
6790
7521
  const hasTools = toolDefs.length > 0;
6791
7522
  if (this._streamingEnabled && (!rootIsObject || hasTools)) {
6792
7523
  const cause = !rootIsObject ? "non-object" : "tools";
@@ -6954,12 +7685,12 @@ ${summaryResponse.content}`
6954
7685
  * Execute a function with checkpoint-replay semantics.
6955
7686
  *
6956
7687
  * On first execution, runs `fn()`, saves the result under `name`, and
6957
- * returns it. On replay (resume after restart), returns the saved result
6958
- * without re-executing. This prevents duplicate side effects (double API
6959
- * calls, double refunds, etc.).
7688
+ * returns it. A later evaluation in the same explicit execution scope
7689
+ * returns the saved result without re-executing. Axl does not restart or
7690
+ * resume workflow continuations across processes.
6960
7691
  *
6961
7692
  * `name` must be a stable, caller-supplied identifier — the same call
6962
- * site MUST pass the same name across runs of the same execution for
7693
+ * site MUST pass the same name across evaluations of the same execution for
6963
7694
  * replay to work. Names are scoped to a single execution and must be
6964
7695
  * unique within it; reusing a name (e.g. inside a loop) gives
6965
7696
  * last-write-wins behavior. For loops, compose names from the iterator:
@@ -7082,6 +7813,7 @@ ${summaryResponse.content}`
7082
7813
  for (let i = 0; i < n; i++) {
7083
7814
  const index = i;
7084
7815
  const p = signalStorage.run(composedSignal, () => fn(index));
7816
+ this._registerBranchDrain(p);
7085
7817
  p.then((value) => {
7086
7818
  if (settled) return;
7087
7819
  results[index] = { ok: true, value };
@@ -7096,6 +7828,14 @@ ${summaryResponse.content}`
7096
7828
  reject(new QuorumNotMet(quorum, successCount, results));
7097
7829
  }
7098
7830
  }).catch((err) => {
7831
+ if (isEventStreamOverflowError(err)) {
7832
+ if (!settled) {
7833
+ settled = true;
7834
+ controller.abort();
7835
+ reject(err);
7836
+ }
7837
+ return;
7838
+ }
7099
7839
  if (settled) return;
7100
7840
  const isAbort = err instanceof DOMException && err.name === "AbortError";
7101
7841
  if (isAbort) {
@@ -7118,14 +7858,16 @@ ${summaryResponse.content}`
7118
7858
  }
7119
7859
  const parentSignal = this.currentSignal;
7120
7860
  const promises = Array.from({ length: n }, (_, i) => {
7121
- const run = () => fn(i).then((value) => ({ ok: true, value })).catch(
7122
- (err) => ({
7861
+ const run = () => fn(i).then((value) => ({ ok: true, value })).catch((err) => {
7862
+ rethrowEventStreamOverflow(err);
7863
+ return {
7123
7864
  ok: false,
7124
7865
  error: err instanceof Error ? err.message : String(err)
7125
- })
7126
- );
7866
+ };
7867
+ });
7127
7868
  return parentSignal ? signalStorage.run(parentSignal, run) : run();
7128
7869
  });
7870
+ for (const promise of promises) this._registerBranchDrain(promise);
7129
7871
  return Promise.all(promises);
7130
7872
  }
7131
7873
  // ── ctx.vote() ────────────────────────────────────────────────────────
@@ -7263,6 +8005,7 @@ ${summaryResponse.content}`
7263
8005
  try {
7264
8006
  validateResult = await options.validate(parsed, { metadata: this.metadata });
7265
8007
  } catch (err) {
8008
+ rethrowEventStreamOverflow(err);
7266
8009
  const reason = err instanceof Error ? err.message : String(err);
7267
8010
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
7268
8011
  }
@@ -7280,6 +8023,7 @@ ${summaryResponse.content}`
7280
8023
  emitVerifyOutcome(true, attempt + 1);
7281
8024
  return parsed;
7282
8025
  } catch (err) {
8026
+ rethrowEventStreamOverflow(err);
7283
8027
  if (err instanceof ValidationError) {
7284
8028
  lastRetry = {
7285
8029
  error: err.reason,
@@ -7302,12 +8046,12 @@ ${summaryResponse.content}`
7302
8046
  }
7303
8047
  continue;
7304
8048
  }
7305
- const errorMsg = err instanceof import_zod.ZodError ? err.message : err instanceof Error ? err.message : String(err);
8049
+ const errorMsg = err instanceof import_zod2.ZodError ? err.message : err instanceof Error ? err.message : String(err);
7306
8050
  lastRetry = { error: errorMsg, output: rawOutput };
7307
8051
  if (attempt === maxRetries) {
7308
8052
  emitVerifyOutcome(false, attempt + 1, errorMsg);
7309
8053
  if (options?.fallback !== void 0) return options.fallback;
7310
- const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([{ code: "custom", path: [], message: errorMsg }]);
8054
+ const zodErr = err instanceof import_zod2.ZodError ? err : new import_zod2.ZodError([{ code: "custom", path: [], message: errorMsg }]);
7311
8055
  throw new VerifyError(rawOutput, zodErr, maxRetries);
7312
8056
  }
7313
8057
  }
@@ -7316,7 +8060,7 @@ ${summaryResponse.content}`
7316
8060
  if (options?.fallback !== void 0) return options.fallback;
7317
8061
  throw new VerifyError(
7318
8062
  lastRetry?.output,
7319
- new import_zod.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
8063
+ new import_zod2.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
7320
8064
  maxRetries
7321
8065
  );
7322
8066
  }
@@ -7346,6 +8090,7 @@ ${summaryResponse.content}`
7346
8090
  const unpriced = this.budgetContext.unpriced;
7347
8091
  return { value, budgetExceeded: exceeded, totalCost, unpriced };
7348
8092
  } catch (err) {
8093
+ rethrowEventStreamOverflow(err);
7349
8094
  if (this.budgetContext.exceeded) {
7350
8095
  return {
7351
8096
  value: null,
@@ -7446,7 +8191,8 @@ ${summaryResponse.content}`
7446
8191
  let remaining = fns.length;
7447
8192
  for (const fn of fns) {
7448
8193
  const p = signalStorage.run(composedSignal, fn);
7449
- p.then(async (value) => {
8194
+ this._registerBranchDrain(p);
8195
+ const continuation = p.then(async (value) => {
7450
8196
  if (settled) return;
7451
8197
  if (schema) {
7452
8198
  const parsed = schema.safeParse(value);
@@ -7476,6 +8222,7 @@ ${summaryResponse.content}`
7476
8222
  return;
7477
8223
  }
7478
8224
  } catch (err) {
8225
+ rethrowEventStreamOverflow(err);
7479
8226
  remaining--;
7480
8227
  lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
7481
8228
  if (remaining === 0 && !settled) {
@@ -7495,6 +8242,14 @@ ${summaryResponse.content}`
7495
8242
  controller.abort();
7496
8243
  resolve2(value);
7497
8244
  }).catch((err) => {
8245
+ if (isEventStreamOverflowError(err)) {
8246
+ if (!settled) {
8247
+ settled = true;
8248
+ controller.abort();
8249
+ reject(err);
8250
+ }
8251
+ throw err;
8252
+ }
7498
8253
  if (settled) return;
7499
8254
  if (err instanceof DOMException && err.name === "AbortError") {
7500
8255
  remaining--;
@@ -7511,6 +8266,7 @@ ${summaryResponse.content}`
7511
8266
  reject(lastError);
7512
8267
  }
7513
8268
  });
8269
+ this._registerBranchDrain(continuation);
7514
8270
  }
7515
8271
  });
7516
8272
  }
@@ -7549,9 +8305,19 @@ ${summaryResponse.content}`
7549
8305
  const idx = nextIndex++;
7550
8306
  try {
7551
8307
  const value = mapSignal ? await signalStorage.run(mapSignal, () => fn(items[idx], idx)) : await fn(items[idx], idx);
8308
+ if (settled) return;
7552
8309
  results[idx] = { ok: true, value };
7553
8310
  successCount++;
7554
8311
  } catch (err) {
8312
+ if (isEventStreamOverflowError(err)) {
8313
+ settled = true;
8314
+ controller?.abort();
8315
+ reject(err);
8316
+ throw err;
8317
+ }
8318
+ if (settled) {
8319
+ return;
8320
+ }
7555
8321
  if (err instanceof DOMException && err.name === "AbortError" && controller?.signal.aborted) {
7556
8322
  completedCount++;
7557
8323
  return;
@@ -7577,7 +8343,9 @@ ${summaryResponse.content}`
7577
8343
  };
7578
8344
  const workers = Math.min(concurrency, items.length);
7579
8345
  for (let i = 0; i < workers; i++) {
7580
- runNext().catch((err) => {
8346
+ const worker = runNext();
8347
+ this._registerBranchDrain(worker);
8348
+ worker.catch((err) => {
7581
8349
  if (!settled) reject(err);
7582
8350
  });
7583
8351
  }
@@ -7603,12 +8371,44 @@ ${summaryResponse.content}`
7603
8371
  return this._awaitHumanImpl(options);
7604
8372
  }
7605
8373
  async _awaitHumanImpl(options) {
8374
+ const branchSignal = this.currentSignal;
8375
+ const makeAbortError = (reason) => {
8376
+ if (typeof DOMException !== "undefined") {
8377
+ return new DOMException(
8378
+ typeof reason === "string" ? reason : "awaitHuman aborted",
8379
+ "AbortError"
8380
+ );
8381
+ }
8382
+ const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
8383
+ err.name = "AbortError";
8384
+ return err;
8385
+ };
8386
+ const awaitWithAbort = async (promise, signal2 = branchSignal) => {
8387
+ if (!signal2) return promise;
8388
+ if (signal2.aborted) throw makeAbortError(signal2.reason);
8389
+ let onAbort;
8390
+ try {
8391
+ return await Promise.race([
8392
+ promise,
8393
+ new Promise((_, reject) => {
8394
+ onAbort = () => reject(makeAbortError(signal2.reason));
8395
+ signal2.addEventListener("abort", onAbort, { once: true });
8396
+ })
8397
+ ]);
8398
+ } finally {
8399
+ if (onAbort) signal2.removeEventListener("abort", onAbort);
8400
+ }
8401
+ };
7606
8402
  if (this.awaitHumanHandler) {
8403
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
7607
8404
  this.emitEvent({
7608
8405
  type: "await_human",
7609
8406
  data: { channel: options.channel, prompt: options.prompt }
7610
8407
  });
7611
- const decision2 = await this.awaitHumanHandler(options);
8408
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
8409
+ const decision2 = parseHumanDecision(
8410
+ await awaitWithAbort(Promise.resolve(this.awaitHumanHandler(options)))
8411
+ );
7612
8412
  this.emitEvent({
7613
8413
  type: "await_human_resolved",
7614
8414
  data: { channel: options.channel, decision: decision2 }
@@ -7620,68 +8420,126 @@ ${summaryResponse.content}`
7620
8420
  "Tool requires approval but no approval handler is configured. Provide awaitHumanHandler to createContext() or use runtime.execute() with workflow infrastructure."
7621
8421
  );
7622
8422
  }
7623
- if (this.stateStore) {
7624
- await this.stateStore.savePendingDecision(this.executionId, {
7625
- executionId: this.executionId,
7626
- channel: options.channel,
7627
- prompt: options.prompt,
7628
- metadata: options.metadata,
7629
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7630
- });
7631
- await this.stateStore.saveExecutionState(this.executionId, {
7632
- workflow: this.workflowName ?? "unknown",
7633
- input: this.input,
7634
- step: this.stepRefRoot.value,
7635
- status: "waiting",
7636
- metadata: {
7637
- ...this.metadata,
7638
- awaitHumanChannel: options.channel,
7639
- awaitHumanPrompt: options.prompt
7640
- }
7641
- });
7642
- }
7643
- this.emitEvent({
7644
- type: "await_human",
7645
- data: { channel: options.channel, prompt: options.prompt }
8423
+ const existingClaim = this.awaitHumanClaims.get(this.executionId);
8424
+ if (existingClaim) {
8425
+ const error = new AxlError(
8426
+ "CONCURRENT_HUMAN_DECISION_UNSUPPORTED",
8427
+ `Execution "${this.executionId}" already has an active human decision. Concurrent awaitHuman calls require request-scoped decision identifiers.`
8428
+ );
8429
+ existingClaim.abort(error);
8430
+ throw error;
8431
+ }
8432
+ const claimController = new AbortController();
8433
+ this.awaitHumanClaims.set(this.executionId, claimController);
8434
+ const signal = branchSignal ? AbortSignal.any([branchSignal, claimController.signal]) : claimController.signal;
8435
+ let saveAttempted = false;
8436
+ let markReady;
8437
+ const ready = new Promise((resolve2) => {
8438
+ markReady = resolve2;
7646
8439
  });
7647
- const makeAbortError = (reason) => {
7648
- if (typeof DOMException !== "undefined") {
7649
- return new DOMException(
7650
- typeof reason === "string" ? reason : "awaitHuman aborted",
7651
- "AbortError"
7652
- );
7653
- }
7654
- const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
7655
- err.name = "AbortError";
7656
- return err;
8440
+ let markCleanupFinished;
8441
+ const cleanupFinished = new Promise((resolve2) => {
8442
+ markCleanupFinished = resolve2;
8443
+ });
8444
+ let resolveDecision;
8445
+ let rejectDecision;
8446
+ const decisionPromise = new Promise((resolve2, reject) => {
8447
+ resolveDecision = resolve2;
8448
+ rejectDecision = reject;
8449
+ });
8450
+ void decisionPromise.catch(() => {
8451
+ });
8452
+ const registeredResolver = {
8453
+ resolve: resolveDecision,
8454
+ ready,
8455
+ state: "preparing",
8456
+ cancelled: false,
8457
+ cleanupFinished
7657
8458
  };
7658
- const decision = await new Promise((resolve2, reject) => {
7659
- if (this.signal?.aborted) {
7660
- reject(makeAbortError(this.signal.reason));
7661
- return;
8459
+ this.pendingDecisions.set(this.executionId, registeredResolver);
8460
+ const abortListener = () => {
8461
+ registeredResolver.cancelled = true;
8462
+ markReady();
8463
+ rejectDecision(makeAbortError(signal.reason));
8464
+ };
8465
+ signal.addEventListener("abort", abortListener, { once: true });
8466
+ let decision;
8467
+ try {
8468
+ if (signal.aborted) throw makeAbortError(signal.reason);
8469
+ if (this.stateStore) {
8470
+ saveAttempted = true;
8471
+ await this.stateStore.savePendingDecision(this.executionId, {
8472
+ executionId: this.executionId,
8473
+ channel: options.channel,
8474
+ prompt: options.prompt,
8475
+ metadata: options.metadata,
8476
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8477
+ });
7662
8478
  }
7663
- this.pendingDecisions.set(this.executionId, resolve2);
7664
- const onAbort = () => {
7665
- const current = this.pendingDecisions.get(this.executionId);
7666
- if (current === resolve2) {
7667
- this.pendingDecisions.delete(this.executionId);
8479
+ if (signal.aborted) throw makeAbortError(signal.reason);
8480
+ this.emitEvent({
8481
+ type: "await_human",
8482
+ data: { channel: options.channel, prompt: options.prompt }
8483
+ });
8484
+ registeredResolver.state = "pending";
8485
+ markReady();
8486
+ decision = await decisionPromise;
8487
+ } catch (error) {
8488
+ if (registeredResolver.state === "preparing") {
8489
+ registeredResolver.state = "failed";
8490
+ markReady();
8491
+ }
8492
+ let needsCompensation = saveAttempted;
8493
+ const inFlightResolution = registeredResolver.resolution;
8494
+ if (registeredResolver.state === "resolving" && inFlightResolution) {
8495
+ needsCompensation = !await inFlightResolution;
8496
+ } else if (registeredResolver.state === "resolved") {
8497
+ needsCompensation = false;
8498
+ }
8499
+ if (this.stateStore && needsCompensation) {
8500
+ try {
8501
+ await this.stateStore.resolveDecision(this.executionId, {
8502
+ approved: false,
8503
+ reason: "Execution aborted while awaiting approval"
8504
+ });
8505
+ } catch (cleanupError) {
8506
+ if (error instanceof Error && Object.isExtensible(error)) {
8507
+ Object.defineProperty(error, "cleanupError", {
8508
+ configurable: true,
8509
+ enumerable: false,
8510
+ value: cleanupError
8511
+ });
8512
+ }
8513
+ try {
8514
+ this._onDecisionCleanupFailed?.({
8515
+ executionId: this.executionId,
8516
+ ...this.workflowName ? { workflow: this.workflowName } : {},
8517
+ operation: "resolveDecision_compensation",
8518
+ error: cleanupError
8519
+ });
8520
+ } catch (observerError) {
8521
+ console.error(
8522
+ "[axl] decision_cleanup_failed listener threw; workflow outcome unchanged:",
8523
+ observerError instanceof Error ? observerError.message : String(observerError)
8524
+ );
8525
+ }
7668
8526
  }
7669
- reject(makeAbortError(this.signal?.reason));
7670
- };
7671
- this.signal?.addEventListener("abort", onAbort, { once: true });
7672
- });
8527
+ }
8528
+ throw error;
8529
+ } finally {
8530
+ signal.removeEventListener("abort", abortListener);
8531
+ if (this.pendingDecisions.get(this.executionId) === registeredResolver) {
8532
+ this.pendingDecisions.delete(this.executionId);
8533
+ }
8534
+ if (this.awaitHumanClaims.get(this.executionId) === claimController) {
8535
+ this.awaitHumanClaims.delete(this.executionId);
8536
+ }
8537
+ markCleanupFinished();
8538
+ }
7673
8539
  this.emitEvent({
7674
8540
  type: "await_human_resolved",
7675
8541
  data: { channel: options.channel, decision }
7676
8542
  });
7677
- if (this.stateStore) {
7678
- await this.stateStore.saveExecutionState(this.executionId, {
7679
- workflow: this.workflowName ?? "unknown",
7680
- input: this.input,
7681
- step: this.stepRefRoot.value,
7682
- status: "running"
7683
- });
7684
- }
7685
8543
  return decision;
7686
8544
  }
7687
8545
  /**
@@ -7733,8 +8591,8 @@ ${summaryResponse.content}`
7733
8591
  // Internal: the runtime is the only caller, user workflows never call these.
7734
8592
  /** @internal — idempotent; no-ops on second+ call within one ctx. */
7735
8593
  _emitWorkflowStart(input) {
7736
- if (this._workflowStartEmitted) return;
7737
- this._workflowStartEmitted = true;
8594
+ if (this.workflowLifecycleState.startEmitted) return;
8595
+ this.workflowLifecycleState.startEmitted = true;
7738
8596
  this.emitEvent({
7739
8597
  type: "workflow_start",
7740
8598
  workflow: this.workflowName,
@@ -7750,8 +8608,8 @@ ${summaryResponse.content}`
7750
8608
  * status. First-wins semantics: the completed event stands, the
7751
8609
  * inner side-effect failure propagates as a normal thrown error. */
7752
8610
  _emitWorkflowEnd(info) {
7753
- if (this._workflowEndEmitted) return;
7754
- this._workflowEndEmitted = true;
8611
+ if (this.workflowLifecycleState.endEmitted) return;
8612
+ this.workflowLifecycleState.endEmitted = true;
7755
8613
  this.emitEvent({
7756
8614
  type: "workflow_end",
7757
8615
  workflow: this.workflowName,
@@ -7761,7 +8619,8 @@ ${summaryResponse.content}`
7761
8619
  duration: info.duration,
7762
8620
  ...info.result !== void 0 ? { result: info.result } : {},
7763
8621
  ...info.error !== void 0 ? { error: info.error } : {},
7764
- ...info.aborted ? { aborted: true } : {}
8622
+ ...info.aborted ? { aborted: true } : {},
8623
+ ...info.observation ? { observation: info.observation } : {}
7765
8624
  }
7766
8625
  });
7767
8626
  }
@@ -8050,6 +8909,7 @@ ${summaryResponse.content}`
8050
8909
  * consumer sites via the `type` discriminator.
8051
8910
  */
8052
8911
  emitEvent(partial) {
8912
+ if (this.workflowLifecycleState.endEmitted && partial.type !== "workflow_end") return;
8053
8913
  const data = partial.data;
8054
8914
  const frame = askStorage.getStore();
8055
8915
  const step = (frame?.stepRef ?? this.stepRefRoot).value++;
@@ -8075,6 +8935,7 @@ ${summaryResponse.content}`
8075
8935
  }
8076
8936
  }
8077
8937
  const event = {
8938
+ schemaVersion: 2,
8078
8939
  executionId: this.executionId,
8079
8940
  step,
8080
8941
  timestamp: Date.now(),
@@ -8095,13 +8956,13 @@ ${summaryResponse.content}`
8095
8956
  ...partial,
8096
8957
  data
8097
8958
  };
8098
- const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
8959
+ const finalEvent = this.config.trace?.redact ? redactHistoricalEvent(event) : event;
8099
8960
  let overflowErr;
8100
8961
  if (this.onTrace) {
8101
8962
  try {
8102
8963
  this.onTrace(finalEvent);
8103
8964
  } catch (err) {
8104
- if (err instanceof EventStreamOverflowError) {
8965
+ if (isEventStreamOverflowError(err)) {
8105
8966
  overflowErr = err;
8106
8967
  } else {
8107
8968
  console.error(
@@ -8116,7 +8977,7 @@ ${summaryResponse.content}`
8116
8977
  try {
8117
8978
  bus._push(finalEvent);
8118
8979
  } catch (err) {
8119
- if (err instanceof EventStreamOverflowError) {
8980
+ if (isEventStreamOverflowError(err)) {
8120
8981
  if (!overflowErr) {
8121
8982
  overflowErr = err;
8122
8983
  } else {
@@ -8303,26 +9164,31 @@ var MemoryStore = class {
8303
9164
  }
8304
9165
  // ── Execution History ──────────────────────────────────────────────
8305
9166
  async saveExecution(execution) {
8306
- this.executionHistory.set(execution.executionId, structuredClone(execution));
9167
+ const normalized = normalizeStoredExecution({
9168
+ ...execution,
9169
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
9170
+ });
9171
+ this.executionHistory.set(execution.executionId, structuredClone(normalized));
8307
9172
  }
8308
9173
  async getExecution(executionId) {
8309
9174
  const exec = this.executionHistory.get(executionId);
8310
- return exec ? structuredClone(exec) : null;
9175
+ return exec ? normalizeStoredExecution(structuredClone(exec)) : null;
8311
9176
  }
8312
9177
  async listExecutions(limit) {
8313
9178
  const sorted = [...this.executionHistory.values()].sort((a, b) => b.startedAt - a.startedAt);
8314
9179
  const result = limit ? sorted.slice(0, limit) : sorted;
8315
- return result.map((e) => structuredClone(e));
9180
+ return result.map((e) => normalizeStoredExecution(structuredClone(e)));
8316
9181
  }
8317
9182
  async deleteExecution(executionId) {
8318
- const removed = this.executionHistory.delete(executionId);
8319
- this.checkpoints.delete(executionId);
8320
- this.executionStates.delete(executionId);
8321
- this.streamingEvents.delete(executionId);
8322
- if (this.decisions.delete(executionId)) {
9183
+ const removedHistory = this.executionHistory.delete(executionId);
9184
+ const removedCheckpoints = this.checkpoints.delete(executionId);
9185
+ const removedState = this.executionStates.delete(executionId);
9186
+ const removedStreaming = this.streamingEvents.delete(executionId);
9187
+ const removedDecision = this.decisions.delete(executionId);
9188
+ if (removedDecision) {
8323
9189
  this.persistAwaitHumanState();
8324
9190
  }
8325
- return removed;
9191
+ return removedHistory || removedCheckpoints || removedState || removedStreaming || removedDecision;
8326
9192
  }
8327
9193
  // ── Eval History ──────────────────────────────────────────────────
8328
9194
  async saveEvalResult(entry) {
@@ -8428,16 +9294,23 @@ function rowToExecutionInfo(row) {
8428
9294
  startedAt: row.started_at,
8429
9295
  completedAt: row.completed_at ?? void 0,
8430
9296
  duration: row.duration,
8431
- error: row.error ?? void 0,
8432
- events: safeJsonParse(row.events) ?? []
9297
+ events: safeJsonParse(row.events) ?? [],
9298
+ ...row.event_schema_version != null ? { eventSchemaVersion: row.event_schema_version } : {}
8433
9299
  };
9300
+ if (row.error != null) info.error = row.error;
8434
9301
  if (row.metadata != null) {
8435
9302
  const parsed = safeJsonParse(row.metadata);
8436
9303
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8437
9304
  info.metadata = parsed;
8438
9305
  }
8439
9306
  }
8440
- return info;
9307
+ if (row.observation != null) {
9308
+ const parsed = safeJsonParse(row.observation);
9309
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9310
+ info.observation = parsed;
9311
+ }
9312
+ }
9313
+ return normalizeStoredExecution(info);
8441
9314
  }
8442
9315
  var SQLiteStore = class {
8443
9316
  db;
@@ -8477,6 +9350,10 @@ var SQLiteStore = class {
8477
9350
  * column (TEXT, nullable, JSON-serialized) so caller-supplied
8478
9351
  * `ExecuteOptions.metadata` round-trips through history. Existing
8479
9352
  * rows get `NULL`, deserialized as `metadata: undefined`.
9353
+ * - v4 (event schema): add `execution_history.event_schema_version`.
9354
+ * Existing rows remain `NULL`, the documented v1 sentinel.
9355
+ * - v5 (observation completeness): add `execution_history.observation`.
9356
+ * Existing rows remain `NULL`, meaning they predate the contract.
8480
9357
  *
8481
9358
  * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
8482
9359
  * IF NOT EXISTS` runs against the post-migration column name. Fresh
@@ -8494,7 +9371,7 @@ var SQLiteStore = class {
8494
9371
  * transactional re-read costs nothing.
8495
9372
  */
8496
9373
  migrate() {
8497
- const TARGET_VERSION = 3;
9374
+ const TARGET_VERSION = 5;
8498
9375
  const current = this.db.pragma("user_version", { simple: true });
8499
9376
  if (current >= TARGET_VERSION) return;
8500
9377
  this.db.exec("BEGIN IMMEDIATE");
@@ -8522,6 +9399,18 @@ var SQLiteStore = class {
8522
9399
  this.db.exec("ALTER TABLE execution_history ADD COLUMN metadata TEXT");
8523
9400
  }
8524
9401
  }
9402
+ if (committed < 4) {
9403
+ const cols = this.db.pragma("table_info(execution_history)");
9404
+ if (cols.length > 0 && !cols.some((c) => c.name === "event_schema_version")) {
9405
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN event_schema_version INTEGER");
9406
+ }
9407
+ }
9408
+ if (committed < 5) {
9409
+ const cols = this.db.pragma("table_info(execution_history)");
9410
+ if (cols.length > 0 && !cols.some((c) => c.name === "observation")) {
9411
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN observation TEXT");
9412
+ }
9413
+ }
8525
9414
  this.db.pragma(`user_version = ${TARGET_VERSION}`);
8526
9415
  this.db.exec("COMMIT");
8527
9416
  } catch (err) {
@@ -8585,7 +9474,9 @@ var SQLiteStore = class {
8585
9474
  duration INTEGER NOT NULL DEFAULT 0,
8586
9475
  error TEXT,
8587
9476
  events TEXT NOT NULL,
8588
- metadata TEXT
9477
+ metadata TEXT,
9478
+ event_schema_version INTEGER,
9479
+ observation TEXT
8589
9480
  );
8590
9481
 
8591
9482
  CREATE TABLE IF NOT EXISTS eval_history (
@@ -8670,7 +9561,7 @@ var SQLiteStore = class {
8670
9561
  const stmt = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?");
8671
9562
  stmt.run(executionId);
8672
9563
  }
8673
- // ── Execution State (for awaitHuman suspend/resume) ────────────────────
9564
+ // ── Legacy application-managed execution state ────────────────────────
8674
9565
  async saveExecutionState(executionId, state) {
8675
9566
  const stmt = this.db.prepare(
8676
9567
  "INSERT OR REPLACE INTO execution_state (execution_id, workflow, input, step, status, metadata) VALUES (?, ?, ?, ?, ?, ?)"
@@ -8705,19 +9596,25 @@ var SQLiteStore = class {
8705
9596
  }
8706
9597
  // ── Execution History ────────────────────────────────────────────────────
8707
9598
  async saveExecution(execution) {
9599
+ const normalized = normalizeStoredExecution({
9600
+ ...execution,
9601
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
9602
+ });
8708
9603
  this.db.prepare(
8709
- "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
9604
+ "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8710
9605
  ).run(
8711
- execution.executionId,
8712
- execution.workflow,
8713
- execution.status,
8714
- execution.totalCost,
8715
- execution.startedAt,
8716
- execution.completedAt ?? null,
8717
- execution.duration,
8718
- execution.error ?? null,
8719
- JSON.stringify(execution.events),
8720
- execution.metadata !== void 0 ? JSON.stringify(execution.metadata) : null
9606
+ normalized.executionId,
9607
+ normalized.workflow,
9608
+ normalized.status,
9609
+ normalized.totalCost,
9610
+ normalized.startedAt,
9611
+ normalized.completedAt ?? null,
9612
+ normalized.duration,
9613
+ normalized.error ?? null,
9614
+ JSON.stringify(normalized.events),
9615
+ normalized.metadata !== void 0 ? JSON.stringify(normalized.metadata) : null,
9616
+ normalized.eventSchemaVersion,
9617
+ normalized.observation !== void 0 ? JSON.stringify(normalized.observation) : null
8721
9618
  );
8722
9619
  }
8723
9620
  async getExecution(executionId) {
@@ -8731,11 +9628,11 @@ var SQLiteStore = class {
8731
9628
  }
8732
9629
  async deleteExecution(executionId) {
8733
9630
  const tx = this.db.transaction(() => {
8734
- const result = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
8735
- this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
8736
- this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
8737
- this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
8738
- return result.changes > 0;
9631
+ const historyResult = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
9632
+ const checkpointsResult = this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
9633
+ const stateResult = this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
9634
+ const decisionsResult = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
9635
+ return historyResult.changes + checkpointsResult.changes + stateResult.changes + decisionsResult.changes > 0;
8739
9636
  });
8740
9637
  return tx();
8741
9638
  }
@@ -9061,6 +9958,11 @@ var AxlStream = class extends import_node_stream.Readable {
9061
9958
  get lifecycle() {
9062
9959
  return this.events.lifecycle;
9063
9960
  }
9961
+ /** Completeness of this stream's iterator queue. Check after consumption
9962
+ * before treating an unmatched lifecycle event as a producer defect. */
9963
+ get observationStatus() {
9964
+ return this.events.observationStatus;
9965
+ }
9064
9966
  /** Coalescing iterator over `partial_object` events — yields the latest
9065
9967
  * object payload per `askId`. Designed for streaming-structured-output
9066
9968
  * UIs: when the producer outpaces the consumer, intermediate snapshots
@@ -9159,6 +10061,7 @@ var AxlStream = class extends import_node_stream.Readable {
9159
10061
  if (this.events.isFinished) return;
9160
10062
  this.result = result;
9161
10063
  const doneEvent = {
10064
+ schemaVersion: 2,
9162
10065
  type: "done",
9163
10066
  executionId,
9164
10067
  step: Number.MAX_SAFE_INTEGER,
@@ -9177,6 +10080,7 @@ var AxlStream = class extends import_node_stream.Readable {
9177
10080
  _error(error, executionId) {
9178
10081
  if (this.events.isFinished) return;
9179
10082
  const errorEvent = {
10083
+ schemaVersion: 2,
9180
10084
  type: "error",
9181
10085
  executionId,
9182
10086
  step: Number.MAX_SAFE_INTEGER,
@@ -9777,11 +10681,7 @@ var STREAMING_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
9777
10681
  "partial_object",
9778
10682
  "string_delta"
9779
10683
  ]);
9780
- var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set([
9781
- "sessionId",
9782
- "sessionHistory",
9783
- "resumeMode"
9784
- ]);
10684
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["sessionId", "sessionHistory"]);
9785
10685
  function liftPersistedMetadata(metadata) {
9786
10686
  if (!metadata) return void 0;
9787
10687
  const filtered = {};
@@ -9899,7 +10799,7 @@ var StreamingFlusher = class {
9899
10799
  await Promise.allSettled(pendings);
9900
10800
  }
9901
10801
  };
9902
- function isAbortError2(err) {
10802
+ function isAbortError4(err) {
9903
10803
  return typeof err === "object" && err !== null && err.name === "AbortError";
9904
10804
  }
9905
10805
  function pushEventBounded(execInfo, event, cap) {
@@ -9911,7 +10811,18 @@ function pushEventBounded(execInfo, event, cap) {
9911
10811
  const last = execInfo.events[execInfo.events.length - 1];
9912
10812
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9913
10813
  if (!alreadyTruncated) {
10814
+ if (execInfo.observation?.complete !== false) {
10815
+ execInfo.observation = {
10816
+ complete: false,
10817
+ reason: "persistence_truncated",
10818
+ maxEvents: cap
10819
+ };
10820
+ }
10821
+ if (event.type === "workflow_end") {
10822
+ event.data.observation = execInfo.observation;
10823
+ }
9914
10824
  execInfo.events[execInfo.events.length - 1] = {
10825
+ schemaVersion: 2,
9915
10826
  type: "log",
9916
10827
  executionId: event.executionId,
9917
10828
  step: event.step,
@@ -9940,7 +10851,9 @@ function boundRecoveredEvents(events, cap) {
9940
10851
  const last = truncated[truncated.length - 1];
9941
10852
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9942
10853
  if (!alreadyTruncated) {
10854
+ const version = getEventSchemaVersion(events[0] ?? {});
9943
10855
  truncated[truncated.length - 1] = {
10856
+ ...version === 2 ? { schemaVersion: 2 } : {},
9944
10857
  type: "log",
9945
10858
  executionId: events[0]?.executionId ?? "",
9946
10859
  step: last?.step ?? 0,
@@ -9954,6 +10867,16 @@ function boundRecoveredEvents(events, cap) {
9954
10867
  }
9955
10868
  return truncated;
9956
10869
  }
10870
+ var REMOVED_OBSERVATION_CALLBACKS = ["onToken", "onToolCall", "onAgentStart"];
10871
+ function assertNoRemovedObservationCallbacks(options) {
10872
+ if (options === null || typeof options !== "object") return;
10873
+ const removed = REMOVED_OBSERVATION_CALLBACKS.filter((key) => Object.hasOwn(options, key));
10874
+ if (removed.length === 0) return;
10875
+ throw new AxlError(
10876
+ "INVALID_CONFIG",
10877
+ `runtime.createContext() no longer accepts ${removed.join(", ")}. Observe ctx.events or use runtime.stream() instead. See docs/migration/stream-first-observation.md.`
10878
+ );
10879
+ }
9957
10880
  var costScopeStorage = new import_node_async_hooks2.AsyncLocalStorage();
9958
10881
  function forwardAbortSignal(external, internal) {
9959
10882
  if (!external) return () => {
@@ -10115,9 +11038,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10115
11038
  * → parse output → emit `workflow_end` → delete checkpoints → persist.
10116
11039
  *
10117
11040
  * Shared between `execute()` and `stream()`. Centralizes the
10118
- * "start iff end" pairing invariant so a future emit path
10119
- * (e.g., `resumeExecution`) can inherit correct behavior by routing
10120
- * through this helper. Also factors out the AbortError detection.
11041
+ * "start iff end" pairing invariant and AbortError detection.
10121
11042
  *
10122
11043
  * Side effects on `execInfo`: sets `status` / `completedAt` /
10123
11044
  * `duration` / `result` (or `error`) before emitting `workflow_end`.
@@ -10141,18 +11062,23 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10141
11062
  */
10142
11063
  async runWorkflowBody(args) {
10143
11064
  const { workflow: workflow2, ctx, execInfo, validated, span, cleanupAbortForwarder } = args;
11065
+ let observation = execInfo.observation ?? { complete: true };
10144
11066
  try {
10145
11067
  ctx._emitWorkflowStart(validated);
10146
11068
  const raw = await workflow2.handler(ctx);
11069
+ const drainedObservation = await ctx._drainBranchWork();
11070
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
10147
11071
  const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
10148
11072
  execInfo.status = "completed";
10149
11073
  execInfo.completedAt = Date.now();
10150
11074
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
10151
11075
  execInfo.result = result;
11076
+ execInfo.observation = observation;
10152
11077
  ctx._emitWorkflowEnd({
10153
11078
  status: "completed",
10154
11079
  duration: execInfo.duration,
10155
- result
11080
+ result,
11081
+ observation
10156
11082
  });
10157
11083
  if (this.stateStore.deleteCheckpoints) {
10158
11084
  await this.stateStore.deleteCheckpoints(execInfo.executionId);
@@ -10162,19 +11088,28 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10162
11088
  this.persistExecution(execInfo);
10163
11089
  return result;
10164
11090
  } catch (err) {
10165
- const aborted = isAbortError2(err);
11091
+ let terminalError = err;
11092
+ try {
11093
+ const drainedObservation = await ctx._drainBranchWork();
11094
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
11095
+ } catch (drainErr) {
11096
+ terminalError = drainErr instanceof Error ? preserveErrorCause(drainErr, terminalError) : drainErr;
11097
+ }
11098
+ const aborted = isAbortError4(terminalError);
10166
11099
  execInfo.status = "failed";
10167
11100
  execInfo.completedAt = Date.now();
10168
11101
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
10169
- execInfo.error = err instanceof Error ? err.message : String(err);
11102
+ execInfo.error = terminalError instanceof Error ? terminalError.message : String(terminalError);
11103
+ execInfo.observation = observation;
10170
11104
  ctx._emitWorkflowEnd({
10171
11105
  status: "failed",
10172
11106
  duration: execInfo.duration,
10173
11107
  error: execInfo.error,
10174
- ...aborted ? { aborted: true } : {}
11108
+ ...aborted ? { aborted: true } : {},
11109
+ observation
10175
11110
  });
10176
11111
  this.persistExecution(execInfo);
10177
- throw err;
11112
+ throw terminalError;
10178
11113
  } finally {
10179
11114
  this.abortControllers.delete(execInfo.executionId);
10180
11115
  cleanupAbortForwarder?.();
@@ -10362,13 +11297,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10362
11297
  return result;
10363
11298
  }
10364
11299
  /** Get all execution info (running + completed + historical). */
10365
- /** Normalize an `ExecutionInfo` loaded from a `StateStore` so the
10366
- * `events: AxlEvent[]` type contract holds for downstream consumers
11300
+ /** Normalize an execution loaded from a `StateStore` so the
11301
+ * historical event union holds for downstream consumers
10367
11302
  * (Studio aggregators, REST routes, redaction). Custom or legacy stores
10368
11303
  * may persist rows without `events` (or with a non-array value) — without
10369
11304
  * this guard, a single malformed row crashes every iterator over
10370
- * `exec.events` and takes down dependent features. Mutates the row
10371
- * in place; logs at most one warning per `executionId`. */
11305
+ * `exec.events` and takes down dependent features. Returns a normalized
11306
+ * copy; logs at most one warning per `executionId`. */
10372
11307
  normalizeStoredExecution(exec) {
10373
11308
  if (!Array.isArray(exec.events)) {
10374
11309
  if (!this.warnedMalformedExecutions.has(exec.executionId)) {
@@ -10377,9 +11312,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10377
11312
  `[axl] StateStore returned execution ${exec.executionId} with non-array events (got ${exec.events === null ? "null" : typeof exec.events}); coercing to []`
10378
11313
  );
10379
11314
  }
10380
- exec.events = [];
11315
+ return normalizeStoredExecution({ ...exec, events: [] });
10381
11316
  }
10382
- return exec;
11317
+ return normalizeStoredExecution(exec);
10383
11318
  }
10384
11319
  async getExecutions() {
10385
11320
  if (!this.historicalExecutionsLoadPromise && this.stateStore.listExecutions) {
@@ -10389,8 +11324,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10389
11324
  this.historicalExecutions.set(exec.executionId, this.normalizeStoredExecution(exec));
10390
11325
  }
10391
11326
  }
10392
- }).catch(() => {
11327
+ }).catch((error) => {
10393
11328
  this.historicalExecutionsLoadPromise = null;
11329
+ throw error;
10394
11330
  });
10395
11331
  }
10396
11332
  if (this.historicalExecutionsLoadPromise) {
@@ -10408,6 +11344,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10408
11344
  * and automatically emits trace events and tracks cost.
10409
11345
  */
10410
11346
  createContext(options) {
11347
+ assertNoRemovedObservationCallbacks(options);
10411
11348
  const executionId = (0, import_node_crypto3.randomUUID)();
10412
11349
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
10413
11350
  this.registerWithCostScope(executionId);
@@ -10423,10 +11360,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10423
11360
  memoryManager: this.memoryManager,
10424
11361
  sessionHistory: options?.sessionHistory,
10425
11362
  signal: options?.signal,
10426
- onToken: options?.onToken,
10427
- onToolCall: options?.onToolCall,
10428
- onAgentStart: options?.onAgentStart,
10429
11363
  awaitHumanHandler: options?.awaitHumanHandler,
11364
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10430
11365
  eventStreamOptions: options?.events,
10431
11366
  onTrace: (event) => {
10432
11367
  this.emit("trace", event);
@@ -10460,6 +11395,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10460
11395
  );
10461
11396
  }
10462
11397
  const validated = workflow2.inputSchema.parse(input);
11398
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10463
11399
  const executionId = (0, import_node_crypto3.randomUUID)();
10464
11400
  const controller = new AbortController();
10465
11401
  this.abortControllers.set(executionId, controller);
@@ -10472,11 +11408,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10472
11408
  executionId,
10473
11409
  workflow: name,
10474
11410
  status: "running",
11411
+ eventSchemaVersion: 2,
10475
11412
  events: [],
10476
11413
  totalCost: 0,
10477
11414
  unpriced: false,
10478
11415
  startedAt: Date.now(),
10479
11416
  duration: 0,
11417
+ observation: { complete: true },
10480
11418
  metadata: liftPersistedMetadata(options?.metadata)
10481
11419
  };
10482
11420
  this.executions.set(executionId, execInfo);
@@ -10490,6 +11428,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10490
11428
  sessionHistory,
10491
11429
  signal: controller.signal,
10492
11430
  eventStreamOptions: options?.events,
11431
+ branchDrainTimeoutMs,
11432
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10493
11433
  onTrace: (event) => {
10494
11434
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10495
11435
  execInfo.totalCost += eventCostContribution(event);
@@ -10534,7 +11474,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10534
11474
  workflowName: name,
10535
11475
  mcpManager: this.mcpManager,
10536
11476
  memoryManager: this.memoryManager,
10537
- resumeMode: !!options?.metadata?.resumeMode,
10538
11477
  spanManager: this.spanManager,
10539
11478
  budgetContext: {
10540
11479
  totalCost: 0,
@@ -10565,6 +11504,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10565
11504
  }
10566
11505
  /** Execute a workflow and return a stream. */
10567
11506
  stream(name, input, options) {
11507
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10568
11508
  const axlStream = new AxlStream(options?.events);
10569
11509
  const controller = new AbortController();
10570
11510
  axlStream.on("close", () => controller.abort());
@@ -10585,11 +11525,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10585
11525
  executionId,
10586
11526
  workflow: name,
10587
11527
  status: "running",
11528
+ eventSchemaVersion: 2,
10588
11529
  events: [],
10589
11530
  totalCost: 0,
10590
11531
  unpriced: false,
10591
11532
  startedAt: Date.now(),
10592
11533
  duration: 0,
11534
+ observation: { complete: true },
10593
11535
  metadata: liftPersistedMetadata(options?.metadata)
10594
11536
  };
10595
11537
  this.executions.set(executionId, execInfo);
@@ -10602,12 +11544,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10602
11544
  sessionHistory,
10603
11545
  signal: controller.signal,
10604
11546
  eventStreamOptions: options?.events,
10605
- // Sentinel `onToken` so the streaming code path in WorkflowContext
10606
- // is entered (it's gated on `this.onToken` being defined). The
10607
- // actual `token` AxlEvents are emitted via `emitEvent` and reach
10608
- // the wire through `onTrace` — this callback is just the gate.
10609
- onToken: () => {
10610
- },
11547
+ branchDrainTimeoutMs,
11548
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
11549
+ // Transport mode is explicit internal state. It is inherited by child
11550
+ // contexts and does not allocate an unconsumed ctx.events queue.
11551
+ _forceStreaming: true,
10611
11552
  onTrace: (event) => {
10612
11553
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10613
11554
  execInfo.totalCost += eventCostContribution(event);
@@ -10653,7 +11594,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10653
11594
  workflowName: name,
10654
11595
  mcpManager: this.mcpManager,
10655
11596
  memoryManager: this.memoryManager,
10656
- resumeMode: !!options?.metadata?.resumeMode,
10657
11597
  spanManager: this.spanManager,
10658
11598
  budgetContext: {
10659
11599
  totalCost: 0,
@@ -10817,6 +11757,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10817
11757
  await this.stateStore.finalizeStreamingEvents?.(id);
10818
11758
  continue;
10819
11759
  }
11760
+ const eventSchemaVersion = getEventSchemaVersion(events[0]);
11761
+ for (const event of events) {
11762
+ if (getEventSchemaVersion(event) !== eventSchemaVersion) {
11763
+ throw new Error(`Streaming buffer ${id} contains mixed event schema versions`);
11764
+ }
11765
+ }
10820
11766
  const boundedEvents = boundRecoveredEvents(events, this.maxEventsPerExecution);
10821
11767
  const workflowStartEvent = boundedEvents.find((e) => e.type === "workflow_start");
10822
11768
  const lastEvent = boundedEvents[boundedEvents.length - 1];
@@ -10825,7 +11771,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10825
11771
  const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
10826
11772
  const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
10827
11773
  const unpriced = boundedEvents.some(isUnpricedLeaf);
10828
- const synthesized = {
11774
+ const executionBase = {
10829
11775
  executionId: id,
10830
11776
  workflow: workflowName,
10831
11777
  status: "failed",
@@ -10837,6 +11783,16 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10837
11783
  duration: completedAt - startedAt,
10838
11784
  error: "process terminated (recovered from streaming buffer)"
10839
11785
  };
11786
+ const synthesized = eventSchemaVersion === 2 ? {
11787
+ ...executionBase,
11788
+ eventSchemaVersion: 2,
11789
+ events: boundedEvents,
11790
+ observation: { complete: false, reason: "process_interrupted" }
11791
+ } : {
11792
+ ...executionBase,
11793
+ eventSchemaVersion: 1,
11794
+ events: boundedEvents
11795
+ };
10840
11796
  if (this.stateStore.saveExecution) {
10841
11797
  try {
10842
11798
  await this.stateStore.saveExecution(synthesized);
@@ -10917,7 +11873,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10917
11873
  */
10918
11874
  async deleteExecution(id) {
10919
11875
  const wasActive = this.executions.has(id);
10920
- const hadPendingDecision = this.pendingDecisionResolvers.has(id);
11876
+ const pendingDecisionResolver = this.pendingDecisionResolvers.get(id);
11877
+ const hadPendingDecision = pendingDecisionResolver !== void 0;
10921
11878
  if (wasActive) {
10922
11879
  this.pendingDeletedExecutions.add(id);
10923
11880
  this.streamableExecutionIds.delete(id);
@@ -10926,8 +11883,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10926
11883
  controller.abort();
10927
11884
  }
10928
11885
  }
10929
- if (hadPendingDecision) {
10930
- this.pendingDecisionResolvers.delete(id);
11886
+ if (pendingDecisionResolver) pendingDecisionResolver.cancelled = true;
11887
+ if (pendingDecisionResolver) {
11888
+ await pendingDecisionResolver.cleanupFinished;
11889
+ if (this.pendingDecisionResolvers.get(id) === pendingDecisionResolver) {
11890
+ this.pendingDecisionResolvers.delete(id);
11891
+ }
10931
11892
  }
10932
11893
  const existing = await this.getExecution(id);
10933
11894
  const workflow2 = existing?.workflow;
@@ -11005,60 +11966,68 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
11005
11966
  }
11006
11967
  /** Resolve a pending human decision. */
11007
11968
  async resolveDecision(executionId, decision) {
11008
- const resolver = this.pendingDecisionResolvers.get(executionId);
11969
+ decision = parseHumanDecision(decision);
11970
+ let resolver = this.pendingDecisionResolvers.get(executionId);
11009
11971
  if (resolver) {
11010
- resolver(decision);
11011
- this.pendingDecisionResolvers.delete(executionId);
11972
+ if (resolver.cancelled) {
11973
+ throw new AxlError(
11974
+ "PENDING_DECISION_NOT_FOUND",
11975
+ `The pending human decision for execution "${executionId}" is no longer active.`
11976
+ );
11977
+ }
11978
+ await resolver.ready;
11979
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11980
+ throw new AxlError(
11981
+ "PENDING_DECISION_NOT_FOUND",
11982
+ `The pending human decision for execution "${executionId}" is no longer active.`
11983
+ );
11984
+ }
11012
11985
  }
11013
- await this.stateStore.resolveDecision(executionId, decision);
11014
11986
  if (!resolver) {
11015
- const state = await this.stateStore.getExecutionState(executionId);
11016
- if (state && state.status === "waiting") {
11017
- await this.resumeExecution(executionId);
11987
+ const pending = await this.stateStore.getPendingDecisions();
11988
+ resolver = this.pendingDecisionResolvers.get(executionId);
11989
+ if (resolver) {
11990
+ if (resolver.cancelled) {
11991
+ throw new AxlError(
11992
+ "PENDING_DECISION_NOT_FOUND",
11993
+ `The pending human decision for execution "${executionId}" is no longer active.`
11994
+ );
11995
+ }
11996
+ await resolver.ready;
11997
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11998
+ throw new AxlError(
11999
+ "PENDING_DECISION_NOT_FOUND",
12000
+ `The pending human decision for execution "${executionId}" is no longer active.`
12001
+ );
12002
+ }
12003
+ } else if (!pending.some((request) => request.executionId === executionId)) {
12004
+ throw new AxlError(
12005
+ "PENDING_DECISION_NOT_FOUND",
12006
+ `No pending human decision exists for execution "${executionId}".`
12007
+ );
12008
+ } else {
12009
+ throw new AxlError(
12010
+ "CROSS_PROCESS_RESUME_UNSUPPORTED",
12011
+ `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.`
12012
+ );
11018
12013
  }
11019
12014
  }
11020
- }
11021
- /**
11022
- * Resume a specific execution that was waiting for a human decision.
11023
- * Re-runs the workflow from scratch; the workflow should use checkpoint-replay
11024
- * to skip already-completed steps and pick up from the awaitHuman point.
11025
- */
11026
- async resumeExecution(executionId) {
11027
- const state = await this.stateStore.getExecutionState(executionId);
11028
- if (!state) {
11029
- throw new Error(`No execution state found for "${executionId}"`);
11030
- }
11031
- const workflow2 = this.workflows.get(state.workflow);
11032
- if (!workflow2) {
11033
- throw new Error(
11034
- `Workflow "${state.workflow}" not registered. Cannot resume execution "${executionId}".`
11035
- );
11036
- }
11037
- return this.execute(state.workflow, state.input, {
11038
- metadata: { ...state.metadata, resumedFrom: executionId, resumeMode: true }
12015
+ let finishResolution;
12016
+ const resolutionFinished = new Promise((resolve2) => {
12017
+ finishResolution = resolve2;
11039
12018
  });
11040
- }
11041
- /**
11042
- * Resume all pending executions that were waiting for human decisions.
11043
- * Call this on startup to resume workflows that were interrupted by a restart.
11044
- * Returns the execution IDs that were resumed.
11045
- */
11046
- async resumePending() {
11047
- const pendingIds = await this.stateStore.listPendingExecutions();
11048
- const resumed = [];
11049
- for (const executionId of pendingIds) {
11050
- try {
11051
- await this.resumeExecution(executionId);
11052
- resumed.push(executionId);
11053
- } catch (err) {
11054
- this.emit("error", {
11055
- type: "resume_failed",
11056
- executionId,
11057
- error: err instanceof Error ? err.message : String(err)
11058
- });
11059
- }
12019
+ resolver.state = "resolving";
12020
+ resolver.resolution = resolutionFinished;
12021
+ try {
12022
+ await this.stateStore.resolveDecision(executionId, decision);
12023
+ resolver.state = "resolved";
12024
+ finishResolution(true);
12025
+ resolver.resolve(decision);
12026
+ } catch (error) {
12027
+ resolver.state = "pending";
12028
+ finishResolution(false);
12029
+ throw error;
11060
12030
  }
11061
- return resumed;
11062
12031
  }
11063
12032
  /**
11064
12033
  * Summarize a list of chat messages into a concise summary string.
@@ -11960,28 +12929,32 @@ var RedisStore = class _RedisStore {
11960
12929
  async saveExecution(execution) {
11961
12930
  const ttl = this.ttlFor("executionHistory");
11962
12931
  const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
11963
- await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(execution), setOptions).zAdd(this.execHistoryZsetKey(), {
12932
+ const storedExecution = normalizeStoredExecution({
12933
+ ...execution,
12934
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
12935
+ });
12936
+ await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(storedExecution), setOptions).zAdd(this.execHistoryZsetKey(), {
11964
12937
  score: execution.startedAt,
11965
12938
  value: execution.executionId
11966
12939
  }).exec();
11967
12940
  }
11968
12941
  async getExecution(executionId) {
11969
12942
  const raw = await this.client.get(this.execHistoryKey(executionId));
11970
- return raw ? JSON.parse(raw) : null;
12943
+ return raw ? normalizeStoredExecution(JSON.parse(raw)) : null;
11971
12944
  }
11972
12945
  async listExecutions(limit) {
11973
- return this.listHistoryByZset(
12946
+ const executions = await this.listHistoryByZset(
11974
12947
  this.execHistorySetKey(),
11975
12948
  this.execHistoryZsetKey(),
11976
12949
  (id) => this.execHistoryKey(id),
11977
12950
  (a, b) => b.startedAt - a.startedAt,
11978
12951
  limit
11979
12952
  );
12953
+ return executions.map(normalizeStoredExecution);
11980
12954
  }
11981
12955
  async deleteExecution(executionId) {
11982
- 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();
11983
- const deleted = typeof deletedCount === "number" ? deletedCount : 0;
11984
- return deleted > 0;
12956
+ 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();
12957
+ return results.some((result) => typeof result === "number" && result > 0);
11985
12958
  }
11986
12959
  // ── Eval History ────────────────────────────────────────────────────
11987
12960
  async saveEvalResult(entry) {
@@ -12073,14 +13046,15 @@ var RedisStore = class _RedisStore {
12073
13046
  async getStreamingEvents(executionId) {
12074
13047
  const raws = await this.client.lRange(this.streamingEventsKey(executionId), 0, -1);
12075
13048
  if (raws.length === 0) return [];
12076
- const events = [];
12077
- for (const raw of raws) {
13049
+ return raws.map((raw, index) => {
12078
13050
  try {
12079
- events.push(JSON.parse(raw));
12080
- } catch {
13051
+ return JSON.parse(raw);
13052
+ } catch (error) {
13053
+ throw new Error(`Streaming buffer ${executionId} contains invalid JSON at index ${index}`, {
13054
+ cause: error
13055
+ });
12081
13056
  }
12082
- }
12083
- return events;
13057
+ });
12084
13058
  }
12085
13059
  /** Close the Redis connection. */
12086
13060
  async close() {
@@ -12292,6 +13266,8 @@ function cosineSimilarity2(a, b) {
12292
13266
  // Annotate the CommonJS export names for ESM import in node:
12293
13267
  0 && (module.exports = {
12294
13268
  AXL_EVENT_TYPES,
13269
+ AXL_EVENT_TYPES_V2,
13270
+ AXL_TOOL_LIFECYCLE_TYPES_V2,
12295
13271
  AZURE_PROFILE,
12296
13272
  AnthropicProvider,
12297
13273
  AxlError,
@@ -12339,8 +13315,9 @@ function cosineSimilarity2(a, b) {
12339
13315
  SqliteVectorStore,
12340
13316
  ThinkTagScanner,
12341
13317
  TimeoutError,
12342
- ToolDenied,
13318
+ ToolFailure,
12343
13319
  ToolModelOutputError,
13320
+ UnsupportedEventSchemaVersionError,
12344
13321
  VLLM_PROFILE,
12345
13322
  ValidationError,
12346
13323
  VerifyError,
@@ -12355,6 +13332,8 @@ function cosineSimilarity2(a, b) {
12355
13332
  extractJson,
12356
13333
  extractThinkTags,
12357
13334
  findConfig,
13335
+ getEventSchemaVersion,
13336
+ getExecutionEventSchemaVersion,
12358
13337
  hasPositiveTokens,
12359
13338
  importModule,
12360
13339
  isCostBearingLeaf,
@@ -12363,6 +13342,7 @@ function cosineSimilarity2(a, b) {
12363
13342
  isUnpricedLeaf,
12364
13343
  isUsableCost,
12365
13344
  needsTsxLoader,
13345
+ normalizeStoredExecution,
12366
13346
  openaiReasoningEmit,
12367
13347
  parsePartialJson,
12368
13348
  pickDefault,
@@ -12371,6 +13351,7 @@ function cosineSimilarity2(a, b) {
12371
13351
  reasoningEffortEmit,
12372
13352
  reasoningObjectEmit,
12373
13353
  redactEvent,
13354
+ redactHistoricalEvent,
12374
13355
  registerConditions,
12375
13356
  resolveApiKey,
12376
13357
  resolvePerModel,