@axlsdk/axl 0.19.0 → 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,7 +162,9 @@ __export(index_exports, {
158
162
  SqliteVectorStore: () => SqliteVectorStore,
159
163
  ThinkTagScanner: () => ThinkTagScanner,
160
164
  TimeoutError: () => TimeoutError,
161
- ToolDenied: () => ToolDenied,
165
+ ToolFailure: () => ToolFailure,
166
+ ToolModelOutputError: () => ToolModelOutputError,
167
+ UnsupportedEventSchemaVersionError: () => UnsupportedEventSchemaVersionError,
162
168
  VLLM_PROFILE: () => VLLM_PROFILE,
163
169
  ValidationError: () => ValidationError,
164
170
  VerifyError: () => VerifyError,
@@ -173,6 +179,8 @@ __export(index_exports, {
173
179
  extractJson: () => extractJson,
174
180
  extractThinkTags: () => extractThinkTags,
175
181
  findConfig: () => findConfig,
182
+ getEventSchemaVersion: () => getEventSchemaVersion,
183
+ getExecutionEventSchemaVersion: () => getExecutionEventSchemaVersion,
176
184
  hasPositiveTokens: () => hasPositiveTokens,
177
185
  importModule: () => importModule,
178
186
  isCostBearingLeaf: () => isCostBearingLeaf,
@@ -181,6 +189,7 @@ __export(index_exports, {
181
189
  isUnpricedLeaf: () => isUnpricedLeaf,
182
190
  isUsableCost: () => isUsableCost,
183
191
  needsTsxLoader: () => needsTsxLoader,
192
+ normalizeStoredExecution: () => normalizeStoredExecution,
184
193
  openaiReasoningEmit: () => openaiReasoningEmit,
185
194
  parsePartialJson: () => parsePartialJson,
186
195
  pickDefault: () => pickDefault,
@@ -189,6 +198,7 @@ __export(index_exports, {
189
198
  reasoningEffortEmit: () => reasoningEffortEmit,
190
199
  reasoningObjectEmit: () => reasoningObjectEmit,
191
200
  redactEvent: () => redactEvent,
201
+ redactHistoricalEvent: () => redactHistoricalEvent,
192
202
  registerConditions: () => registerConditions,
193
203
  resolveApiKey: () => resolveApiKey,
194
204
  resolvePerModel: () => resolvePerModel,
@@ -201,27 +211,257 @@ __export(index_exports, {
201
211
  });
202
212
  module.exports = __toCommonJS(index_exports);
203
213
 
214
+ // src/tool.ts
215
+ var import_zod = require("zod");
216
+
217
+ // src/errors.ts
218
+ var AxlError = class extends Error {
219
+ code;
220
+ constructor(code, message) {
221
+ super(message);
222
+ this.name = "AxlError";
223
+ this.code = code;
224
+ }
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
+ };
275
+ var ToolModelOutputError = class extends AxlError {
276
+ toolName;
277
+ cause;
278
+ constructor(toolName, cause) {
279
+ super("TOOL_MODEL_OUTPUT_ERROR", `Failed to prepare model output for tool "${toolName}"`);
280
+ this.name = "ToolModelOutputError";
281
+ this.toolName = toolName;
282
+ this.cause = cause;
283
+ Object.defineProperty(this, "cause", { enumerable: false });
284
+ }
285
+ };
286
+ var VerifyError = class extends AxlError {
287
+ lastOutput;
288
+ zodError;
289
+ retries;
290
+ constructor(lastOutput, zodError, retries) {
291
+ super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
292
+ this.name = "VerifyError";
293
+ this.lastOutput = lastOutput;
294
+ this.zodError = zodError;
295
+ this.retries = retries;
296
+ }
297
+ };
298
+ var QuorumNotMet = class extends AxlError {
299
+ results;
300
+ constructor(required, actual, results) {
301
+ super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
302
+ this.name = "QuorumNotMet";
303
+ this.results = results;
304
+ }
305
+ };
306
+ var NoConsensus = class extends AxlError {
307
+ constructor(reason) {
308
+ super("NO_CONSENSUS", `No consensus: ${reason}`);
309
+ this.name = "NoConsensus";
310
+ }
311
+ };
312
+ var TimeoutError = class extends AxlError {
313
+ constructor(operation, timeoutMs) {
314
+ super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
315
+ this.name = "TimeoutError";
316
+ }
317
+ };
318
+ function formatBudgetCost(cost) {
319
+ if (Number.isNaN(cost)) return "$NaN";
320
+ if (cost === Infinity) return "$Infinity";
321
+ if (cost === -Infinity) return "-$Infinity";
322
+ if (cost === 0) return "$0.00";
323
+ const sign = cost < 0 ? "-" : "";
324
+ const abs = Math.abs(cost);
325
+ if (abs < 1e-6) return `${sign}< $0.000001`;
326
+ if (abs < 1e-4) {
327
+ const [mantissa, exponent] = abs.toExponential(2).split("e");
328
+ return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
329
+ }
330
+ if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
331
+ return `${sign}$${abs.toFixed(2)}`;
332
+ }
333
+ var BudgetExceededError = class extends AxlError {
334
+ limit;
335
+ spent;
336
+ policy;
337
+ constructor(limit, spent, policy) {
338
+ super(
339
+ "BUDGET_EXCEEDED",
340
+ `Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
341
+ limit
342
+ )} limit (policy: ${policy})`
343
+ );
344
+ this.name = "BudgetExceededError";
345
+ this.limit = limit;
346
+ this.spent = spent;
347
+ this.policy = policy;
348
+ }
349
+ };
350
+ var MaxTurnsError = class extends AxlError {
351
+ maxTurns;
352
+ constructor(operation, maxTurns) {
353
+ super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
354
+ this.name = "MaxTurnsError";
355
+ this.maxTurns = maxTurns;
356
+ }
357
+ };
358
+ var GuardrailError = class extends AxlError {
359
+ guardrailType;
360
+ reason;
361
+ constructor(guardrailType, reason) {
362
+ super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
363
+ this.name = "GuardrailError";
364
+ this.guardrailType = guardrailType;
365
+ this.reason = reason;
366
+ }
367
+ };
368
+ var ValidationError = class extends AxlError {
369
+ lastOutput;
370
+ reason;
371
+ retries;
372
+ constructor(lastOutput, reason, retries) {
373
+ super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
374
+ this.name = "ValidationError";
375
+ this.lastOutput = lastOutput;
376
+ this.reason = reason;
377
+ this.retries = retries;
378
+ }
379
+ };
380
+
204
381
  // src/tool.ts
205
382
  var DEFAULT_MAX_STRING_LENGTH = 1e4;
206
- function validateStringLengths(value, maxLen, path = "") {
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
+ );
389
+ super(
390
+ `String argument${renderedPath ? ` at "${renderedPath}"` : ""} exceeds maximum length (${actual} > ${maximum})`
391
+ );
392
+ this.path = path;
393
+ this.actual = actual;
394
+ this.maximum = maximum;
395
+ this.name = "ToolStringLengthError";
396
+ }
397
+ };
398
+ function validateStringLengths(value, maxLen, path = []) {
207
399
  if (typeof value === "string") {
208
400
  if (value.length > maxLen) {
209
- throw new Error(
210
- `String argument${path ? ` at "${path}"` : ""} exceeds maximum length (${value.length} > ${maxLen})`
211
- );
401
+ throw new ToolStringLengthError(path, value.length, maxLen);
212
402
  }
213
403
  } else if (Array.isArray(value)) {
214
404
  for (let i = 0; i < value.length; i++) {
215
- validateStringLengths(value[i], maxLen, path ? `${path}[${i}]` : `[${i}]`);
405
+ validateStringLengths(value[i], maxLen, [...path, i]);
216
406
  }
217
407
  } else if (value !== null && typeof value === "object") {
218
408
  for (const [key, val] of Object.entries(value)) {
219
- validateStringLengths(val, maxLen, path ? `${path}.${key}` : key);
409
+ validateStringLengths(val, maxLen, [...path, key]);
220
410
  }
221
411
  }
222
412
  }
223
- function sleep(ms) {
224
- return new Promise((resolve2) => setTimeout(resolve2, ms));
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);
225
465
  }
226
466
  function getBackoffMs(attempt, strategy) {
227
467
  switch (strategy) {
@@ -240,17 +480,27 @@ function tool(config) {
240
480
  on: config.retry?.on
241
481
  };
242
482
  const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
243
- const execute = async (input, ctx) => {
483
+ const prepareInput = (input) => {
244
484
  const parsed = config.input.parse(input);
245
485
  if (maxStringLen > 0) {
246
486
  validateStringLengths(parsed, maxStringLen);
247
487
  }
488
+ return parsed;
489
+ };
490
+ const executePrepared = async (parsed, ctx, options) => {
248
491
  const maxAttempts = retryPolicy.attempts ?? 1;
249
492
  let lastError;
250
493
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
494
+ options?.signal?.throwIfAborted();
495
+ options?.onAttempt?.(attempt);
251
496
  try {
252
- return await config.handler(parsed, ctx);
497
+ const result = await config.handler(parsed, ctx);
498
+ if (options?.checkAfterHandlerAbort !== false) options?.signal?.throwIfAborted();
499
+ return result;
253
500
  } catch (err) {
501
+ if (options?.signal?.aborted) options.signal.throwIfAborted();
502
+ if (isAbortError(err)) throw err;
503
+ rethrowEventStreamOverflow(err);
254
504
  lastError = err instanceof Error ? err : new Error(String(err));
255
505
  if (attempt === maxAttempts) break;
256
506
  if (retryPolicy.on && !retryPolicy.on(lastError)) {
@@ -258,13 +508,14 @@ function tool(config) {
258
508
  }
259
509
  const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
260
510
  if (backoffMs > 0) {
261
- await sleep(backoffMs);
511
+ await sleep(backoffMs, options?.signal);
262
512
  }
263
513
  }
264
514
  }
265
515
  throw lastError;
266
516
  };
267
- return {
517
+ const execute = async (input, ctx) => executePrepared(prepareInput(input), ctx);
518
+ const instance = {
268
519
  name: config.name,
269
520
  description: config.description,
270
521
  inputSchema: config.input,
@@ -272,6 +523,7 @@ function tool(config) {
272
523
  retry: retryPolicy,
273
524
  requireApproval: config.requireApproval ?? false,
274
525
  hooks: config.hooks,
526
+ ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
275
527
  async run(ctx, input) {
276
528
  const startTime = Date.now();
277
529
  try {
@@ -299,6 +551,11 @@ function tool(config) {
299
551
  },
300
552
  _execute: execute
301
553
  };
554
+ TOOL_INTERNALS.set(instance, {
555
+ prepareInput,
556
+ executePrepared
557
+ });
558
+ return instance;
302
559
  }
303
560
 
304
561
  // src/providers/types.ts
@@ -324,123 +581,6 @@ async function resolveApiKey(source) {
324
581
  return source ?? "";
325
582
  }
326
583
 
327
- // src/errors.ts
328
- var AxlError = class extends Error {
329
- code;
330
- constructor(code, message) {
331
- super(message);
332
- this.name = "AxlError";
333
- this.code = code;
334
- }
335
- };
336
- var VerifyError = class extends AxlError {
337
- lastOutput;
338
- zodError;
339
- retries;
340
- constructor(lastOutput, zodError, retries) {
341
- super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
342
- this.name = "VerifyError";
343
- this.lastOutput = lastOutput;
344
- this.zodError = zodError;
345
- this.retries = retries;
346
- }
347
- };
348
- var QuorumNotMet = class extends AxlError {
349
- results;
350
- constructor(required, actual, results) {
351
- super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
352
- this.name = "QuorumNotMet";
353
- this.results = results;
354
- }
355
- };
356
- var NoConsensus = class extends AxlError {
357
- constructor(reason) {
358
- super("NO_CONSENSUS", `No consensus: ${reason}`);
359
- this.name = "NoConsensus";
360
- }
361
- };
362
- var TimeoutError = class extends AxlError {
363
- constructor(operation, timeoutMs) {
364
- super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
365
- this.name = "TimeoutError";
366
- }
367
- };
368
- function formatBudgetCost(cost) {
369
- if (Number.isNaN(cost)) return "$NaN";
370
- if (cost === Infinity) return "$Infinity";
371
- if (cost === -Infinity) return "-$Infinity";
372
- if (cost === 0) return "$0.00";
373
- const sign = cost < 0 ? "-" : "";
374
- const abs = Math.abs(cost);
375
- if (abs < 1e-6) return `${sign}< $0.000001`;
376
- if (abs < 1e-4) {
377
- const [mantissa, exponent] = abs.toExponential(2).split("e");
378
- return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
379
- }
380
- if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
381
- return `${sign}$${abs.toFixed(2)}`;
382
- }
383
- var BudgetExceededError = class extends AxlError {
384
- limit;
385
- spent;
386
- policy;
387
- constructor(limit, spent, policy) {
388
- super(
389
- "BUDGET_EXCEEDED",
390
- `Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
391
- limit
392
- )} limit (policy: ${policy})`
393
- );
394
- this.name = "BudgetExceededError";
395
- this.limit = limit;
396
- this.spent = spent;
397
- this.policy = policy;
398
- }
399
- };
400
- var MaxTurnsError = class extends AxlError {
401
- maxTurns;
402
- constructor(operation, maxTurns) {
403
- super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
404
- this.name = "MaxTurnsError";
405
- this.maxTurns = maxTurns;
406
- }
407
- };
408
- var GuardrailError = class extends AxlError {
409
- guardrailType;
410
- reason;
411
- constructor(guardrailType, reason) {
412
- super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
413
- this.name = "GuardrailError";
414
- this.guardrailType = guardrailType;
415
- this.reason = reason;
416
- }
417
- };
418
- var ValidationError = class extends AxlError {
419
- lastOutput;
420
- reason;
421
- retries;
422
- constructor(lastOutput, reason, retries) {
423
- super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
424
- this.name = "ValidationError";
425
- this.lastOutput = lastOutput;
426
- this.reason = reason;
427
- this.retries = retries;
428
- }
429
- };
430
- var ToolDenied = class extends AxlError {
431
- toolName;
432
- agentName;
433
- constructor(toolName, agentName) {
434
- super(
435
- "TOOL_DENIED",
436
- `Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
437
- );
438
- this.name = "ToolDenied";
439
- this.toolName = toolName;
440
- this.agentName = agentName;
441
- }
442
- };
443
-
444
584
  // src/providers/errors.ts
445
585
  var ProviderError = class extends AxlError {
446
586
  /** Adapter/profile name, e.g. `'openai'`, `'anthropic'`, `'google'`. */
@@ -548,7 +688,7 @@ function sleep2(ms, signal) {
548
688
  );
549
689
  });
550
690
  }
551
- function isAbortError(err, signal) {
691
+ function isAbortError2(err, signal) {
552
692
  if (signal?.aborted) return true;
553
693
  return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
554
694
  }
@@ -567,7 +707,7 @@ async function fetchWithRetry(input, init, opts) {
567
707
  try {
568
708
  res = await fetch(input, init);
569
709
  } catch (err) {
570
- if (isAbortError(err, init?.signal ?? void 0)) throw err;
710
+ if (isAbortError2(err, init?.signal ?? void 0)) throw err;
571
711
  if (attempt >= maxRetries) {
572
712
  throw buildProviderError({
573
713
  provider,
@@ -2353,6 +2493,15 @@ function sanitizeSchemaForGemini(schema) {
2353
2493
  }
2354
2494
  return out;
2355
2495
  }
2496
+ function parseGeminiFunctionResponse(content) {
2497
+ let parsed;
2498
+ try {
2499
+ parsed = JSON.parse(content);
2500
+ } catch {
2501
+ return { result: content };
2502
+ }
2503
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
2504
+ }
2356
2505
  var GEMINI_PRICING = {
2357
2506
  "gemini-2.5-pro": [125e-8, 1e-5],
2358
2507
  "gemini-2.5-flash": [3e-7, 25e-7],
@@ -2688,15 +2837,9 @@ var GeminiProvider = class {
2688
2837
  }
2689
2838
  } else if (msg.role === "tool") {
2690
2839
  const functionName = toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
2691
- let responseData;
2692
- try {
2693
- responseData = JSON.parse(msg.content);
2694
- } catch {
2695
- responseData = { result: msg.content };
2696
- }
2697
2840
  const functionResponse = {
2698
2841
  name: functionName,
2699
- response: responseData
2842
+ response: parseGeminiFunctionResponse(msg.content)
2700
2843
  };
2701
2844
  if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
2702
2845
  functionResponse.id = msg.tool_call_id;
@@ -3246,7 +3389,7 @@ var defaultRegistry = new ProviderRegistry();
3246
3389
  // src/context.ts
3247
3390
  var import_node_async_hooks = require("async_hooks");
3248
3391
  var import_node_crypto = require("crypto");
3249
- var import_zod = require("zod");
3392
+ var import_zod2 = require("zod");
3250
3393
 
3251
3394
  // src/partial-json.ts
3252
3395
  var MAX_DEPTH = 256;
@@ -3865,9 +4008,9 @@ var AXL_EVENT_TYPES = [
3865
4008
  // Tool invocation lifecycle
3866
4009
  "tool_call_start",
3867
4010
  "tool_call_end",
4011
+ "tool_call_rejected",
3868
4012
  // Single-point tool events
3869
4013
  "tool_approval",
3870
- "tool_denied",
3871
4014
  // Delegation
3872
4015
  "delegate",
3873
4016
  // Handoff (spans two asks; not AskScoped).
@@ -3898,7 +4041,7 @@ var AXL_EVENT_TYPES = [
3898
4041
  "memory_remember",
3899
4042
  "memory_recall",
3900
4043
  "memory_forget",
3901
- // Durable execution checkpoints (`ctx.checkpoint`)
4044
+ // Scoped execution checkpoints (`ctx.checkpoint`)
3902
4045
  "checkpoint_save",
3903
4046
  "checkpoint_replay",
3904
4047
  // Human-in-the-loop (`ctx.awaitHuman`)
@@ -3913,6 +4056,12 @@ var AXL_EVENT_TYPES = [
3913
4056
  "done",
3914
4057
  "error"
3915
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
+ ];
3916
4065
 
3917
4066
  // src/event-stream.ts
3918
4067
  var STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
@@ -3928,8 +4077,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3928
4077
  "agent_call_end",
3929
4078
  "tool_call_start",
3930
4079
  "tool_call_end",
4080
+ "tool_call_rejected",
3931
4081
  "tool_approval",
3932
- "tool_denied",
3933
4082
  "handoff_start",
3934
4083
  "handoff_return",
3935
4084
  "delegate",
@@ -3943,18 +4092,6 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3943
4092
  "await_human",
3944
4093
  "await_human_resolved"
3945
4094
  ]);
3946
- var EventStreamOverflowError = class extends Error {
3947
- maxQueued;
3948
- eventType;
3949
- constructor(maxQueued, eventType) {
3950
- super(
3951
- `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.`
3952
- );
3953
- this.name = "EventStreamOverflowError";
3954
- this.maxQueued = maxQueued;
3955
- this.eventType = eventType;
3956
- }
3957
- };
3958
4095
  var DEFAULT_MAX_QUEUED = 1e4;
3959
4096
  var DEFAULT_OVERFLOW = "drop-oldest-non-terminal";
3960
4097
  var AxlEventBus = class {
@@ -3965,6 +4102,7 @@ var AxlEventBus = class {
3965
4102
  maxQueued;
3966
4103
  onOverflow;
3967
4104
  overflowWarned = false;
4105
+ droppedEvents = 0;
3968
4106
  /** Callbacks fired exactly once when `_finish()` is called. Used by
3969
4107
  * curated views (e.g., `partialObjects`) that are listener-based and
3970
4108
  * need a termination signal independent of any synthetic `done` event. */
@@ -4010,6 +4148,10 @@ var AxlEventBus = class {
4010
4148
  * can be 4 KB+ each, hence the more aggressive cleanup here).
4011
4149
  */
4012
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();
4013
4155
  /**
4014
4156
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
4015
4157
  * is `await using`-disposed. Set via the second constructor argument.
@@ -4043,6 +4185,12 @@ var AxlEventBus = class {
4043
4185
  this.bus.on("error", () => {
4044
4186
  });
4045
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
+ }
4046
4194
  unknownEventWarned = false;
4047
4195
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4048
4196
  on(event, handler) {
@@ -4355,7 +4503,7 @@ var AxlEventBus = class {
4355
4503
  }
4356
4504
  return {
4357
4505
  [Symbol.asyncIterator]() {
4358
- const pending = [];
4506
+ const pending = /* @__PURE__ */ new Map();
4359
4507
  let resolveWaiter = null;
4360
4508
  let finished = self.finished;
4361
4509
  let unsubscribed = false;
@@ -4369,10 +4517,39 @@ var AxlEventBus = class {
4369
4517
  if (filterPath !== void 0 && path !== filterPath) return false;
4370
4518
  return true;
4371
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
+ };
4372
4549
  for (const [askId, paths] of self.stringStreamByAsk) {
4373
4550
  for (const [path, acc] of paths) {
4374
4551
  if (!matchesFilter(askId, path)) continue;
4375
- pending.push({
4552
+ enqueue({
4376
4553
  askId,
4377
4554
  agent: acc.agent,
4378
4555
  path,
@@ -4382,51 +4559,37 @@ var AxlEventBus = class {
4382
4559
  });
4383
4560
  }
4384
4561
  }
4385
- const handler = (event) => {
4386
- if (event.type !== "string_delta") return;
4387
- const askId = event.askId ?? "";
4388
- if (!matchesFilter(askId, event.data.path)) return;
4389
- const paths = self.stringStreamByAsk.get(askId);
4390
- const acc = paths?.get(event.data.path);
4391
- const accumulated = acc?.text ?? event.data.delta;
4392
- pending.push({
4393
- askId,
4394
- agent: event.agent,
4395
- path: event.data.path,
4396
- delta: event.data.delta,
4397
- accumulated,
4398
- attempt: event.attempt
4399
- });
4400
- wakeWaiter();
4401
- };
4402
- self.bus.on("string_delta", handler);
4403
- const pipelineHandler = (event) => {
4404
- if (event.type !== "pipeline") return;
4405
- if (event.status !== "failed") return;
4406
- const askId = event.askId ?? "";
4407
- for (let i = pending.length - 1; i >= 0; i--) {
4408
- 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
+ }
4409
4570
  }
4410
4571
  };
4411
- self.bus.on("pipeline", pipelineHandler);
4572
+ self.stringStreamSubscribers.add(subscriber);
4412
4573
  const unsubscribe = () => {
4413
4574
  if (unsubscribed) return;
4414
4575
  unsubscribed = true;
4415
- self.bus.off("string_delta", handler);
4416
- self.bus.off("pipeline", pipelineHandler);
4576
+ self.stringStreamSubscribers.delete(subscriber);
4417
4577
  unsubscribeFinish();
4418
4578
  };
4419
4579
  const unsubscribeFinish = self._onFinish(() => {
4420
4580
  finished = true;
4421
- self.bus.off("string_delta", handler);
4422
- self.bus.off("pipeline", pipelineHandler);
4581
+ self.stringStreamSubscribers.delete(subscriber);
4423
4582
  wakeWaiter();
4424
4583
  });
4425
4584
  return {
4426
4585
  async next() {
4427
4586
  while (true) {
4428
- if (pending.length > 0) {
4429
- 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
+ }
4430
4593
  }
4431
4594
  if (finished) {
4432
4595
  unsubscribe();
@@ -4484,6 +4647,7 @@ var AxlEventBus = class {
4484
4647
  */
4485
4648
  _push(event) {
4486
4649
  if (this.finished) return;
4650
+ let overflowError;
4487
4651
  if (event.type === "partial_object" && event.data?.object !== void 0) {
4488
4652
  const askId = event.askId ?? "";
4489
4653
  this.latestPartialByAsk.set(askId, {
@@ -4505,10 +4669,30 @@ var AxlEventBus = class {
4505
4669
  text: (existing?.text ?? "") + event.data.delta,
4506
4670
  attempt: event.attempt
4507
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
+ }
4508
4691
  } else if (event.type === "pipeline" && event.status === "failed") {
4509
4692
  const askId = event.askId ?? "";
4510
4693
  this.latestPartialByAsk.delete(askId);
4511
4694
  this.stringStreamByAsk.delete(askId);
4695
+ for (const subscriber of this.stringStreamSubscribers) subscriber.reset(askId);
4512
4696
  } else if (event.type === "ask_end") {
4513
4697
  this.stringStreamByAsk.delete(event.askId ?? "");
4514
4698
  }
@@ -4523,39 +4707,57 @@ var AxlEventBus = class {
4523
4707
  const waiter = this.waiters.shift();
4524
4708
  if (waiter) {
4525
4709
  waiter({ value: event, done: false });
4710
+ if (overflowError) throw overflowError;
4526
4711
  return;
4527
4712
  }
4528
4713
  if (this.eventQueue.length >= this.maxQueued && !TERMINAL_TYPES.has(event.type)) {
4529
- 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;
4530
4724
  return;
4531
4725
  }
4532
4726
  this.eventQueue.push(event);
4727
+ if (overflowError) throw overflowError;
4533
4728
  }
4534
4729
  /** Apply the overflow policy when the queue is at `maxQueued` and a
4535
4730
  * non-terminal event arrives. Terminal events bypass this path entirely
4536
4731
  * (checked by the caller). */
4537
4732
  handleOverflow(event) {
4538
4733
  if (this.onOverflow === "throw") {
4734
+ this.droppedEvents++;
4539
4735
  throw new EventStreamOverflowError(this.maxQueued, event.type);
4540
4736
  }
4541
4737
  let droppedIdx = -1;
4542
4738
  for (let i = 0; i < this.eventQueue.length; i++) {
4543
- if (!TERMINAL_TYPES.has(this.eventQueue[i].type)) {
4739
+ const candidate = this.eventQueue[i];
4740
+ if (!TERMINAL_TYPES.has(candidate.type)) {
4544
4741
  droppedIdx = i;
4545
4742
  break;
4546
4743
  }
4547
4744
  }
4548
- if (!this.overflowWarned) {
4549
- this.overflowWarned = true;
4550
- console.warn(
4551
- `[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.`
4552
- );
4553
- }
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
+ );
4554
4748
  if (droppedIdx >= 0) {
4555
4749
  this.eventQueue.splice(droppedIdx, 1);
4750
+ this.droppedEvents++;
4556
4751
  }
4557
4752
  this.eventQueue.push(event);
4558
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
+ }
4559
4761
  /**
4560
4762
  * Mark the bus as finished. Wakes any pending iterators with `done: true`
4561
4763
  * and drops further `_push` calls. Idempotent.
@@ -4622,6 +4824,43 @@ var AxlEventBus = class {
4622
4824
  }
4623
4825
  };
4624
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
+
4625
4864
  // src/redaction.ts
4626
4865
  var REDACTED = "[redacted]";
4627
4866
  function walkObjectOneLevel(obj, preserveKeys) {
@@ -4672,7 +4911,7 @@ function redactMemory(event) {
4672
4911
  };
4673
4912
  }
4674
4913
  var passthrough = (e) => e;
4675
- var REDACTION_RULES = {
4914
+ var LEGACY_REDACTION_RULES = {
4676
4915
  workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
4677
4916
  workflow_end: (e) => {
4678
4917
  const d = e.data;
@@ -4772,10 +5011,125 @@ var REDACTION_RULES = {
4772
5011
  done: (e) => ({ ...e, data: { result: REDACTED } }),
4773
5012
  error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
4774
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
+ };
4775
5123
  function redactEvent(event) {
4776
5124
  const rule = REDACTION_RULES[event.type];
4777
5125
  return rule(event);
4778
5126
  }
5127
+ function redactHistoricalEvent(event) {
5128
+ if (getEventSchemaVersion(event) === 1) {
5129
+ return redactLegacyEvent(event);
5130
+ }
5131
+ return redactEvent(event);
5132
+ }
4779
5133
 
4780
5134
  // src/schema-diagnostics.ts
4781
5135
  var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
@@ -4830,110 +5184,771 @@ function detectDroppedRefinements(schema) {
4830
5184
  if ("right" in def) walk(def.right, path);
4831
5185
  if (typeof def.getter === "function") {
4832
5186
  try {
4833
- walk(def.getter(), path);
5187
+ walk(def.getter(), path);
5188
+ } catch {
5189
+ }
5190
+ }
5191
+ };
5192
+ walk(schema, "");
5193
+ const result = { count, paths: [...paths] };
5194
+ refinementCache.set(schema, result);
5195
+ return result;
5196
+ }
5197
+ var DOCS_URL = "docs/observability.md#schema-diagnostics";
5198
+ var MAX_WARNED_KEYS = 2048;
5199
+ var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
5200
+ function warnSchemaDiagnosticOnce(key, message, silent) {
5201
+ if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
5202
+ if (warnedDiagnosticKeys.has(key)) return;
5203
+ if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
5204
+ warnedDiagnosticKeys.add(key);
5205
+ console.warn(`[axl] ${message} See ${DOCS_URL}.`);
5206
+ }
5207
+
5208
+ // src/config.ts
5209
+ function defineConfig(config) {
5210
+ return config;
5211
+ }
5212
+ function parseDuration(duration) {
5213
+ const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/);
5214
+ if (!match) throw new Error(`Invalid duration format: "${duration}"`);
5215
+ const value = parseFloat(match[1]);
5216
+ const unit = match[2];
5217
+ switch (unit) {
5218
+ case "ms":
5219
+ return value;
5220
+ case "s":
5221
+ return value * 1e3;
5222
+ case "m":
5223
+ return value * 6e4;
5224
+ case "h":
5225
+ return value * 36e5;
5226
+ default:
5227
+ throw new Error(`Unknown duration unit: "${unit}"`);
5228
+ }
5229
+ }
5230
+ function parseCost(cost) {
5231
+ const match = cost.match(/^\$?([\d.]+)$/);
5232
+ if (!match) throw new Error(`Invalid cost format: "${cost}"`);
5233
+ return parseFloat(match[1]);
5234
+ }
5235
+ function resolveConfig(config) {
5236
+ const resolved = { ...config };
5237
+ if (process.env.AXL_DEFAULT_PROVIDER) {
5238
+ resolved.defaultProvider = process.env.AXL_DEFAULT_PROVIDER;
5239
+ }
5240
+ if (process.env.AXL_STATE_STORE) {
5241
+ const envStore = process.env.AXL_STATE_STORE;
5242
+ if (envStore === "memory" || envStore === "sqlite") {
5243
+ resolved.state = {
5244
+ ...resolved.state,
5245
+ store: envStore
5246
+ };
5247
+ }
5248
+ }
5249
+ if (process.env.AXL_TRACE_ENABLED !== void 0) {
5250
+ resolved.trace = { ...resolved.trace, enabled: process.env.AXL_TRACE_ENABLED === "true" };
5251
+ }
5252
+ if (process.env.AXL_TRACE_LEVEL) {
5253
+ resolved.trace = {
5254
+ ...resolved.trace,
5255
+ level: process.env.AXL_TRACE_LEVEL
5256
+ };
5257
+ }
5258
+ if (process.env.OPENAI_API_KEY) {
5259
+ if (!resolved.providers) resolved.providers = {};
5260
+ resolved.providers.openai = {
5261
+ ...resolved.providers.openai ?? {},
5262
+ apiKey: process.env.OPENAI_API_KEY
5263
+ };
5264
+ }
5265
+ if (process.env.ANTHROPIC_API_KEY) {
5266
+ if (!resolved.providers) resolved.providers = {};
5267
+ resolved.providers.anthropic = {
5268
+ ...resolved.providers.anthropic ?? {},
5269
+ apiKey: process.env.ANTHROPIC_API_KEY
5270
+ };
5271
+ }
5272
+ const googleKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
5273
+ if (googleKey) {
5274
+ if (!resolved.providers) resolved.providers = {};
5275
+ resolved.providers.google = {
5276
+ ...resolved.providers.google ?? {},
5277
+ apiKey: googleKey
5278
+ };
5279
+ }
5280
+ return resolved;
5281
+ }
5282
+
5283
+ // src/tool-model-output.ts
5284
+ var import_node_util = require("util");
5285
+ function invalid(path, reason) {
5286
+ return new Error(`Invalid tool model output at ${path}: ${reason}`);
5287
+ }
5288
+ function propertyPath(parent, key) {
5289
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return `${parent}.${key}`;
5290
+ let escaped = "";
5291
+ for (const character of key) {
5292
+ const code = character.charCodeAt(0);
5293
+ if (character === "\\" || character === '"') escaped += `\\${character}`;
5294
+ else if (code <= 31 || code === 8232 || code === 8233) {
5295
+ escaped += `\\u${code.toString(16).padStart(4, "0")}`;
5296
+ } else escaped += character;
5297
+ }
5298
+ return `${parent}["${escaped}"]`;
5299
+ }
5300
+ function normalize(value, path, stack) {
5301
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
5302
+ if (typeof value === "number") {
5303
+ if (!Number.isFinite(value)) throw invalid(path, "number must be finite");
5304
+ return value;
5305
+ }
5306
+ if (value === void 0) throw invalid(path, "undefined is only allowed on object properties");
5307
+ if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
5308
+ throw invalid(path, `unsupported ${typeof value}`);
5309
+ }
5310
+ if (typeof value !== "object") throw invalid(path, `unsupported ${typeof value}`);
5311
+ let prototype;
5312
+ let descriptors;
5313
+ try {
5314
+ prototype = Object.getPrototypeOf(value);
5315
+ descriptors = Object.getOwnPropertyDescriptors(value);
5316
+ Object.setPrototypeOf(descriptors, null);
5317
+ } catch (cause) {
5318
+ throw invalid(
5319
+ path,
5320
+ `object inspection failed (${cause instanceof Error ? cause.name : "error"})`
5321
+ );
5322
+ }
5323
+ if (stack.has(value)) throw invalid(path, "cyclic reference");
5324
+ for (const key of Reflect.ownKeys(descriptors)) {
5325
+ const descriptor = descriptors[key];
5326
+ if (descriptor && !("value" in descriptor)) {
5327
+ throw invalid(
5328
+ typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
5329
+ "accessor properties are not supported"
5330
+ );
5331
+ }
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
+ };
5486
+ }
5487
+ }
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);
4834
5544
  } catch {
5545
+ return void 0;
4835
5546
  }
4836
- }
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" };
5563
+ }
5564
+ }
5565
+ function isToolFailure(error) {
5566
+ try {
5567
+ return error instanceof ToolFailure;
5568
+ } catch {
5569
+ return false;
5570
+ }
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 {
5577
+ }
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)
4837
5583
  };
4838
- walk(schema, "");
4839
- const result = { count, paths: [...paths] };
4840
- refinementCache.set(schema, result);
4841
- return result;
4842
5584
  }
4843
- var DOCS_URL = "docs/observability.md#schema-diagnostics";
4844
- var MAX_WARNED_KEYS = 2048;
4845
- var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
4846
- function warnSchemaDiagnosticOnce(key, message, silent) {
4847
- if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
4848
- if (warnedDiagnosticKeys.has(key)) return;
4849
- if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
4850
- warnedDiagnosticKeys.add(key);
4851
- console.warn(`[axl] ${message} See ${DOCS_URL}.`);
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
+ }
5592
+ }
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
+ };
4852
5607
  }
4853
-
4854
- // src/config.ts
4855
- function defineConfig(config) {
4856
- return config;
5608
+ function mcpContent(result) {
5609
+ return result.content.map((content) => content.type === "text" ? content.text : `[${content.type}]`).join("\n");
4857
5610
  }
4858
- function parseDuration(duration) {
4859
- const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/);
4860
- if (!match) throw new Error(`Invalid duration format: "${duration}"`);
4861
- const value = parseFloat(match[1]);
4862
- const unit = match[2];
4863
- switch (unit) {
4864
- case "ms":
4865
- return value;
4866
- case "s":
4867
- return value * 1e3;
4868
- case "m":
4869
- return value * 6e4;
4870
- case "h":
4871
- return value * 36e5;
4872
- default:
4873
- throw new Error(`Unknown duration unit: "${unit}"`);
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;
4874
5616
  }
5617
+ return `MCP tool error: ${normalized.slice(0, MAX_MCP_MODEL_ERROR_LENGTH)}`;
4875
5618
  }
4876
- function parseCost(cost) {
4877
- const match = cost.match(/^\$?([\d.]+)$/);
4878
- if (!match) throw new Error(`Invalid cost format: "${cost}"`);
4879
- return parseFloat(match[1]);
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;
5658
+ try {
5659
+ signal?.throwIfAborted();
5660
+ switch (invocation.source.kind) {
5661
+ case "override": {
5662
+ const executeOverride = invocation.source.execute;
5663
+ result = await executeOverride(effectiveArgs);
5664
+ break;
5665
+ }
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
+ };
5696
+ }
5697
+ result = mcpResult;
5698
+ break;
5699
+ }
5700
+ }
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 });
5718
+ }
5719
+ }
5720
+ return { kind: "succeeded", result };
4880
5721
  }
4881
- function resolveConfig(config) {
4882
- const resolved = { ...config };
4883
- if (process.env.AXL_DEFAULT_PROVIDER) {
4884
- resolved.defaultProvider = process.env.AXL_DEFAULT_PROVIDER;
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
+ };
4885
5729
  }
4886
- if (process.env.AXL_STATE_STORE) {
4887
- const envStore = process.env.AXL_STATE_STORE;
4888
- if (envStore === "memory" || envStore === "sqlite") {
4889
- resolved.state = {
4890
- ...resolved.state,
4891
- store: envStore
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;
5762
+ }
5763
+ return {
5764
+ ok: false,
5765
+ phase: "projection",
5766
+ error: error instanceof ToolModelOutputError && error.toolName === invocation.requestedTool ? error : new ToolModelOutputError(invocation.requestedTool, error)
4892
5767
  };
4893
5768
  }
4894
5769
  }
4895
- if (process.env.AXL_TRACE_ENABLED !== void 0) {
4896
- resolved.trace = { ...resolved.trace, enabled: process.env.AXL_TRACE_ENABLED === "true" };
5770
+ try {
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 };
4897
5777
  }
4898
- if (process.env.AXL_TRACE_LEVEL) {
4899
- resolved.trace = {
4900
- ...resolved.trace,
4901
- level: process.env.AXL_TRACE_LEVEL
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
4902
5785
  };
4903
5786
  }
4904
- if (process.env.OPENAI_API_KEY) {
4905
- if (!resolved.providers) resolved.providers = {};
4906
- resolved.providers.openai = {
4907
- ...resolved.providers.openai ?? {},
4908
- apiKey: process.env.OPENAI_API_KEY
5787
+ if (internal.kind === "failed" && internal.failure.disposition === "abort") {
5788
+ return {
5789
+ outcome: { status: "failed", failure: internal.failure },
5790
+ abortError: internal.error
4909
5791
  };
4910
5792
  }
4911
- if (process.env.ANTHROPIC_API_KEY) {
4912
- if (!resolved.providers) resolved.providers = {};
4913
- resolved.providers.anthropic = {
4914
- ...resolved.providers.anthropic ?? {},
4915
- apiKey: process.env.ANTHROPIC_API_KEY
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)
4916
5802
  };
4917
5803
  }
4918
- const googleKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
4919
- if (googleKey) {
4920
- if (!resolved.providers) resolved.providers = {};
4921
- resolved.providers.google = {
4922
- ...resolved.providers.google ?? {},
4923
- apiKey: googleKey
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)
4924
5812
  };
4925
5813
  }
4926
- return resolved;
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
+ };
5824
+ }
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");
5871
+ }
4927
5872
  }
4928
5873
 
4929
5874
  // src/context.ts
4930
5875
  var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
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;
5886
+ }
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;
5896
+ try {
5897
+ prototype = Object.getPrototypeOf(value);
5898
+ } catch {
5899
+ return invalid2();
5900
+ }
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;
5915
+ try {
5916
+ descriptor = Object.getOwnPropertyDescriptor(value, key);
5917
+ } catch {
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 } };
5934
+ }
5935
+ }
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
+ }
5943
+ }
5944
+ return invalid2();
5945
+ }
4931
5946
  var askStorage = new import_node_async_hooks.AsyncLocalStorage();
4932
5947
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
4933
5948
  function zodToJsonSchema(schema) {
4934
5949
  const cached = inlineSchemaCache.get(schema);
4935
5950
  if (cached !== void 0) return cached;
4936
- const result = import_zod.z.toJSONSchema(schema, { unrepresentable: "any" });
5951
+ const result = import_zod2.z.toJSONSchema(schema, { unrepresentable: "any" });
4937
5952
  delete result.$schema;
4938
5953
  Object.freeze(result);
4939
5954
  inlineSchemaCache.set(schema, result);
@@ -4952,7 +5967,7 @@ var nativeSchemaCache = /* @__PURE__ */ new WeakMap();
4952
5967
  function deriveNativeSchema(schema) {
4953
5968
  const cached = nativeSchemaCache.get(schema);
4954
5969
  if (cached !== void 0) return cached;
4955
- const json = import_zod.z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
5970
+ const json = import_zod2.z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
4956
5971
  delete json.$schema;
4957
5972
  nativeSchemaCache.set(schema, json);
4958
5973
  return json;
@@ -4960,7 +5975,7 @@ function deriveNativeSchema(schema) {
4960
5975
  function renderSchemaForPrompt(schema) {
4961
5976
  const cached = promptSchemaCache.get(schema);
4962
5977
  if (cached !== void 0) return cached;
4963
- const json = import_zod.z.toJSONSchema(schema, {
5978
+ const json = import_zod2.z.toJSONSchema(schema, {
4964
5979
  unrepresentable: "any",
4965
5980
  io: "input",
4966
5981
  reused: "ref"
@@ -5061,9 +6076,8 @@ var WorkflowContext = class _WorkflowContext {
5061
6076
  providerRegistry;
5062
6077
  sessionHistory;
5063
6078
  onTrace;
5064
- onToken;
5065
- onToolCall;
5066
6079
  pendingDecisions;
6080
+ awaitHumanClaims;
5067
6081
  budgetContext;
5068
6082
  stateStore;
5069
6083
  /** Root step counter for this execution. Inherited by every ctx.ask()
@@ -5084,27 +6098,27 @@ var WorkflowContext = class _WorkflowContext {
5084
6098
  * the caller-supplied key.
5085
6099
  */
5086
6100
  autoCheckpointCounters;
5087
- /** Idempotency guards for `workflow_start` / `workflow_end` emission.
6101
+ branchDrainState;
6102
+ /** Root-shared idempotency guards for workflow lifecycle emission.
5088
6103
  * Both `runtime.execute()` and `runtime.stream()` have paths where a
5089
6104
  * post-emit side-effect (checkpoint deletion, state-store persistence)
5090
6105
  * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
5091
6106
  * fired — the outer catch would then fire `_emitWorkflowEnd({status:
5092
6107
  * 'failed'})` for a second time. These flags make both emitters
5093
6108
  * single-fire so consumers never see paired-then-conflicting terminal
5094
- * events for one execution. */
5095
- _workflowStartEmitted = false;
5096
- _workflowEndEmitted = false;
6109
+ * events for one execution. Sharing the state with child contexts also
6110
+ * suppresses late branch events after bounded finalization. */
6111
+ workflowLifecycleState;
5097
6112
  signal;
5098
6113
  summaryCache;
5099
6114
  workflowName;
5100
6115
  mcpManager;
5101
6116
  spanManager;
5102
6117
  memoryManager;
5103
- resumeMode;
5104
6118
  toolOverrides;
5105
6119
  awaitHumanHandler;
5106
- onAgentStart;
5107
6120
  onAgentCallComplete;
6121
+ _onDecisionCleanupFailed;
5108
6122
  /** Shared mutable slot for the lazy `AxlEventBus`. The slot is shared
5109
6123
  * by reference across parent/child contexts so a late allocation
5110
6124
  * (consumer subscribes after a child context exists) is visible
@@ -5113,6 +6127,7 @@ var WorkflowContext = class _WorkflowContext {
5113
6127
  * contexts that never observe). */
5114
6128
  _busRef;
5115
6129
  _eventStreamOptions;
6130
+ _forceStreaming;
5116
6131
  /** Removes the constructor-registered `'abort'` listener from
5117
6132
  * `this.signal` so a long-lived signal (e.g., a request-scoped or
5118
6133
  * process-wide signal reused across many `runtime.createContext()` /
@@ -5132,8 +6147,7 @@ var WorkflowContext = class _WorkflowContext {
5132
6147
  * *between* asks. `ctx.events.partialObjects` is the coalescing view
5133
6148
  * designed for streaming-structured-output UIs.
5134
6149
  * - **Ad-hoc contexts** from `runtime.createContext()`: the same
5135
- * iterable replaces the legacy `onToken` / `onToolCall` /
5136
- * `onAgentStart` callbacks.
6150
+ * iterable provides typed per-context observation.
5137
6151
  * - **Cross-execution**: prefer `runtime.on('trace', …)` instead — that
5138
6152
  * fans out across all executions, while `ctx.events` is per-context.
5139
6153
  *
@@ -5148,9 +6162,9 @@ var WorkflowContext = class _WorkflowContext {
5148
6162
  *
5149
6163
  * **Subscribe before the first `ctx.ask()`.** The streaming code
5150
6164
  * path inside `ctx.ask()` activates only when an observer is
5151
- * present at the time the ask starts (`_streamingEnabled` — either
5152
- * the legacy `onToken` callback is set or `ctx.events` has been
5153
- * 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
5154
6168
  * begun, that in-flight ask will not stream `token` /
5155
6169
  * `partial_object` events at all (the agent loop went through
5156
6170
  * `provider.chat` instead of `provider.stream`). Subsequent asks
@@ -5189,18 +6203,16 @@ var WorkflowContext = class _WorkflowContext {
5189
6203
  this._busRef.current?._finish();
5190
6204
  this.abortListenerCleanup?.();
5191
6205
  }
5192
- /** True if any observer wants per-token streaming for asks started
5193
- * from this context: either the legacy `onToken` callback is set
5194
- * (e.g., `runtime.stream()` plumbs a sentinel) OR `ctx.events` has
5195
- * been allocated (a workflow-handler consumer subscribed before
5196
- * 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
5197
6209
  * streaming code path; the gate is re-checked per ask, so a
5198
6210
  * consumer subscribing AFTER the first ask started still gets
5199
6211
  * streaming on the next one. The check `_busRef.current !== undefined`
5200
6212
  * is a one-way flag — `_finish` does not unset the reference, so
5201
6213
  * once an observer was present, every subsequent ask streams. */
5202
6214
  get _streamingEnabled() {
5203
- return this.onToken !== void 0 || this._busRef.current !== void 0;
6215
+ return this._forceStreaming || this._busRef.current !== void 0;
5204
6216
  }
5205
6217
  constructor(init) {
5206
6218
  this.input = init.input;
@@ -5210,9 +6222,8 @@ var WorkflowContext = class _WorkflowContext {
5210
6222
  this.providerRegistry = init.providerRegistry;
5211
6223
  this.sessionHistory = init.sessionHistory ?? [];
5212
6224
  this.onTrace = init.onTrace;
5213
- this.onToken = init.onToken;
5214
- this.onToolCall = init.onToolCall;
5215
6225
  this.pendingDecisions = init.pendingDecisions;
6226
+ this.awaitHumanClaims = init.awaitHumanClaims ?? /* @__PURE__ */ new Map();
5216
6227
  this.budgetContext = init.budgetContext;
5217
6228
  this.stateStore = init.stateStore;
5218
6229
  this.signal = init.signal;
@@ -5220,13 +6231,13 @@ var WorkflowContext = class _WorkflowContext {
5220
6231
  this.mcpManager = init.mcpManager;
5221
6232
  this.spanManager = init.spanManager;
5222
6233
  this.memoryManager = init.memoryManager;
5223
- this.resumeMode = init.resumeMode ?? false;
5224
6234
  this.toolOverrides = init.toolOverrides;
5225
6235
  this.awaitHumanHandler = init.awaitHumanHandler;
5226
- this.onAgentStart = init.onAgentStart;
5227
6236
  this.onAgentCallComplete = init.onAgentCallComplete;
6237
+ this._onDecisionCleanupFailed = init._onDecisionCleanupFailed;
5228
6238
  this._busRef = init._busRef ?? { current: void 0 };
5229
6239
  this._eventStreamOptions = init.eventStreamOptions;
6240
+ this._forceStreaming = init._forceStreaming ?? false;
5230
6241
  if (init._busRef === void 0 && this.signal && !this.signal.aborted) {
5231
6242
  const signal = this.signal;
5232
6243
  const onAbort = () => {
@@ -5242,6 +6253,15 @@ var WorkflowContext = class _WorkflowContext {
5242
6253
  byAgent: /* @__PURE__ */ new Map(),
5243
6254
  root: 0
5244
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
+ };
5245
6265
  if (init.metadata?.summaryCache) {
5246
6266
  this.summaryCache = init.metadata.summaryCache;
5247
6267
  }
@@ -5250,16 +6270,9 @@ var WorkflowContext = class _WorkflowContext {
5250
6270
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
5251
6271
  * Shares: budget tracking, abort signals, trace emission, provider registry,
5252
6272
  * state store, span manager, memory manager, MCP manager, config,
5253
- * awaitHuman handler, pending decisions, tool overrides, AND the
5254
- * streaming callbacks (onToken / onAgentStart / onToolCall).
6273
+ * awaitHuman handler, pending decisions, tool overrides, and event bus.
5255
6274
  * Isolates: session history.
5256
6275
  *
5257
- * Streaming callbacks now propagate into nested asks because every callback
5258
- * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
5259
- * consumers that want root-only behavior can filter on `meta.depth === 0`
5260
- * instead of relying on the runtime to drop nested events. This is the
5261
- * "nested ask visibility" fix from spec §3.2.
5262
- *
5263
6276
  * The shared step counter lives in `askStorage` (ALS), so there is no
5264
6277
  * per-context counter to pass through anymore. Nested events are
5265
6278
  * correlated to their parent via `parentAskId` (set on every nested ask
@@ -5280,12 +6293,11 @@ var WorkflowContext = class _WorkflowContext {
5280
6293
  spanManager: this.spanManager,
5281
6294
  memoryManager: this.memoryManager,
5282
6295
  onTrace: this.onTrace,
5283
- onToken: this.onToken,
5284
- onToolCall: this.onToolCall,
5285
- onAgentStart: this.onAgentStart,
5286
6296
  onAgentCallComplete: this.onAgentCallComplete,
6297
+ _onDecisionCleanupFailed: this._onDecisionCleanupFailed,
5287
6298
  awaitHumanHandler: this.awaitHumanHandler,
5288
6299
  pendingDecisions: this.pendingDecisions,
6300
+ awaitHumanClaims: this.awaitHumanClaims,
5289
6301
  toolOverrides: this.toolOverrides,
5290
6302
  signal: this.signal,
5291
6303
  workflowName: this.workflowName,
@@ -5293,12 +6305,15 @@ var WorkflowContext = class _WorkflowContext {
5293
6305
  // emitted from `ctx.ask` / spawn / race / parallel / map in this
5294
6306
  // child context don't collide with the parent's in the state store.
5295
6307
  autoCheckpointCounters: this.autoCheckpointCounters,
6308
+ branchDrainState: this.branchDrainState,
6309
+ workflowLifecycleState: this.workflowLifecycleState,
5296
6310
  // Share the parent's bus slot (mutable ref). Late allocation in
5297
6311
  // either parent or child propagates because both read the same
5298
6312
  // slot. Nested-ask events (agent-as-tool pattern) bubble up to
5299
6313
  // the parent's iterator with `parentAskId`/`depth` correlation
5300
6314
  // intact.
5301
6315
  _busRef: this._busRef,
6316
+ _forceStreaming: this._forceStreaming,
5302
6317
  eventStreamOptions: this._eventStreamOptions
5303
6318
  // Isolated: sessionHistory (empty)
5304
6319
  });
@@ -5311,28 +6326,194 @@ var WorkflowContext = class _WorkflowContext {
5311
6326
  get currentSignal() {
5312
6327
  return signalStorage.getStore() ?? this.signal;
5313
6328
  }
5314
- /**
5315
- * Build a `CallbackMeta` for the current ask frame. Used at every
5316
- * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
5317
- * route or filter by ask correlation (`meta.depth === 0` for root
5318
- * chat UIs, etc.).
5319
- *
5320
- * If the call is somehow outside an ask frame (e.g., a programmatic
5321
- * `tool.run()` test harness), falls back to a synthetic meta keyed off
5322
- * `executionId` so the type contract holds — consumers see `depth: 0`
5323
- * and can ignore those events.
5324
- */
5325
- currentCallbackMeta(agentName) {
5326
- const frame = askStorage.getStore();
5327
- if (frame) {
5328
- return {
5329
- askId: frame.askId,
5330
- ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
5331
- depth: frame.depth,
5332
- 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" };
5333
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
+ });
5334
6515
  }
5335
- return { askId: this.executionId, depth: 0, agent: agentName };
6516
+ if (settlement.abortError !== void 0) throw settlement.abortError;
5336
6517
  }
5337
6518
  // ── ctx.ask() ─────────────────────────────────────────────────────────
5338
6519
  async ask(agent2, prompt, options) {
@@ -5361,18 +6542,21 @@ var WorkflowContext = class _WorkflowContext {
5361
6542
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
5362
6543
  const usageCapture = {};
5363
6544
  const doCall = async () => {
5364
- const result = await this.executeAgentCall(
6545
+ const result2 = await this.executeAgentCall(
5365
6546
  agent2,
5366
6547
  prompt,
5367
6548
  options,
5368
6549
  void 0,
5369
6550
  usageCapture
5370
6551
  );
5371
- return result;
6552
+ return result2;
5372
6553
  };
5373
6554
  let outcome;
6555
+ let askFailure;
6556
+ let askFailed = false;
6557
+ let result;
5374
6558
  try {
5375
- const result = this.spanManager ? await this.spanManager.withSpanAsync(
6559
+ result = this.spanManager ? await this.spanManager.withSpanAsync(
5376
6560
  "axl.agent.ask",
5377
6561
  {
5378
6562
  "axl.agent.name": agent2._name,
@@ -5431,36 +6615,34 @@ var WorkflowContext = class _WorkflowContext {
5431
6615
  );
5432
6616
  }
5433
6617
  }
5434
- return result;
5435
6618
  } catch (err) {
6619
+ askFailed = true;
6620
+ askFailure = err;
5436
6621
  outcome = {
5437
6622
  ok: false,
5438
6623
  error: err instanceof Error ? err.message : String(err)
5439
6624
  };
5440
- throw err;
5441
- } finally {
5442
- try {
5443
- this.emitEvent({
5444
- type: "ask_end",
5445
- outcome: outcome ?? {
5446
- ok: false,
5447
- error: "ask_end emitted without outcome \u2014 internal bug"
5448
- },
5449
- cost: frame.askCost.value,
5450
- ...frame.askUnpriced ? { unpriced: true } : {},
5451
- duration: Date.now() - askStart
5452
- });
5453
- } catch (emitErr) {
5454
- if (emitErr instanceof EventStreamOverflowError && outcome && !outcome.ok) {
5455
- console.error(
5456
- "[axl] ask_end emit overflowed during error path; preserving original error:",
5457
- emitErr.message
5458
- );
5459
- } else {
5460
- throw emitErr;
5461
- }
5462
- }
5463
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;
6642
+ }
6643
+ if (terminalFailed) throw terminalFailure;
6644
+ if (askFailed) throw askFailure;
6645
+ return result;
5464
6646
  });
5465
6647
  },
5466
6648
  { agent: agentName }
@@ -5622,6 +6804,7 @@ ${appendedSchemaText}`;
5622
6804
  let lastStartAttempt = 1;
5623
6805
  let lastStartMaxAttempts = 1;
5624
6806
  while (turns < maxTurns) {
6807
+ this.currentSignal?.throwIfAborted();
5625
6808
  if (Date.now() - startTime > timeoutMs) {
5626
6809
  throw new TimeoutError("ctx.ask()", timeoutMs);
5627
6810
  }
@@ -5681,7 +6864,6 @@ ${appendedSchemaText}`;
5681
6864
  }
5682
6865
  } : { type: "json_object" };
5683
6866
  }
5684
- const callbackMeta = this.currentCallbackMeta(agent2._name);
5685
6867
  const callParams = {
5686
6868
  ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
5687
6869
  maxTokens: chatOptions.maxTokens,
@@ -5720,16 +6902,16 @@ ${appendedSchemaText}`;
5720
6902
  ...messagesSnapshot ? { messages: messagesSnapshot } : {}
5721
6903
  }
5722
6904
  });
5723
- this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
5724
6905
  let response;
5725
6906
  try {
6907
+ this.currentSignal?.throwIfAborted();
5726
6908
  if (this._streamingEnabled) {
5727
6909
  let content2 = "";
5728
6910
  const toolCalls = [];
5729
6911
  const toolCallBuffers = /* @__PURE__ */ new Map();
5730
6912
  let streamProviderMetadata;
5731
6913
  let thinkingContent = "";
5732
- 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;
5733
6915
  const currentAttempt = schemaRetries + 1;
5734
6916
  const walker = streamingObjectEnabled ? new StreamingWalker({
5735
6917
  onStringDelta: (path, delta) => {
@@ -5745,7 +6927,6 @@ ${appendedSchemaText}`;
5745
6927
  if (chunk.type === "text_delta") {
5746
6928
  content2 += chunk.content;
5747
6929
  this.emitEvent({ type: "token", data: chunk.content });
5748
- this.onToken?.(chunk.content, callbackMeta);
5749
6930
  if (walker) {
5750
6931
  walker.processChunk(chunk.content);
5751
6932
  if (walker.consumeBoundary()) {
@@ -5855,6 +7036,7 @@ ${appendedSchemaText}`;
5855
7036
  ...retryReason ? { retryReason } : {}
5856
7037
  }
5857
7038
  });
7039
+ this.currentSignal?.throwIfAborted();
5858
7040
  if (response.tool_calls && response.tool_calls.length > 0) {
5859
7041
  currentMessages.push({
5860
7042
  role: "assistant",
@@ -6024,232 +7206,12 @@ ${appendedSchemaText}`;
6024
7206
  return await handoffFn();
6025
7207
  }
6026
7208
  }
6027
- const toolOverride = this.toolOverrides?.get(toolName);
6028
- if (toolOverride) {
6029
- let toolArgs2;
6030
- try {
6031
- toolArgs2 = JSON.parse(toolCall.function.arguments);
6032
- } catch {
6033
- currentMessages.push({
6034
- role: "tool",
6035
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6036
- tool_call_id: toolCall.id
6037
- });
6038
- continue;
6039
- }
6040
- this.emitEvent({
6041
- type: "tool_call_start",
6042
- tool: toolName,
6043
- callId: toolCall.id,
6044
- data: { args: toolArgs2 }
6045
- });
6046
- this.onToolCall?.(
6047
- { name: toolName, args: toolArgs2, callId: toolCall.id },
6048
- callbackMeta
6049
- );
6050
- const toolStart2 = Date.now();
6051
- const executeOverride = async () => {
6052
- let toolResult3;
6053
- try {
6054
- toolResult3 = await toolOverride(toolArgs2);
6055
- } catch (err) {
6056
- toolResult3 = { error: err instanceof Error ? err.message : String(err) };
6057
- }
6058
- return toolResult3;
6059
- };
6060
- const toolResult2 = this.spanManager ? await this.spanManager.withSpanAsync(
6061
- "axl.tool.call",
6062
- {
6063
- "axl.tool.name": toolName,
6064
- "axl.agent.name": agent2._name
6065
- },
6066
- async (span) => {
6067
- const r = await executeOverride();
6068
- span.setAttribute("axl.tool.duration", Date.now() - toolStart2);
6069
- const isError = r && typeof r === "object" && "error" in r;
6070
- span.setAttribute("axl.tool.success", !isError);
6071
- if (isError)
6072
- span.setStatus("error", r.error);
6073
- return r;
6074
- }
6075
- ) : await executeOverride();
6076
- const resultContent2 = JSON.stringify(toolResult2);
6077
- this.emitEvent({
6078
- type: "tool_call_end",
6079
- agent: agent2._name,
6080
- tool: toolName,
6081
- duration: Date.now() - toolStart2,
6082
- data: { args: toolArgs2, result: toolResult2, callId: toolCall.id }
6083
- });
6084
- currentMessages.push({
6085
- role: "tool",
6086
- content: resultContent2,
6087
- tool_call_id: toolCall.id
6088
- });
6089
- continue;
6090
- }
6091
- const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
6092
- const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
6093
- if (!tool2 && !isMcpTool) {
6094
- this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
6095
- currentMessages.push({
6096
- role: "tool",
6097
- content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
6098
- tool_call_id: toolCall.id
6099
- });
6100
- continue;
6101
- }
6102
- let toolArgs;
6103
- try {
6104
- toolArgs = JSON.parse(toolCall.function.arguments);
6105
- } catch {
6106
- currentMessages.push({
6107
- role: "tool",
6108
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
6109
- tool_call_id: toolCall.id
6110
- });
6111
- continue;
6112
- }
6113
- this.emitEvent({
6114
- type: "tool_call_start",
6115
- tool: toolName,
6116
- callId: toolCall.id,
6117
- data: { args: toolArgs }
6118
- });
6119
- this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
6120
- const toolStart = Date.now();
6121
- if (tool2 && tool2.requireApproval) {
6122
- const approvalFn = async () => {
6123
- const decision = await this.awaitHuman({
6124
- channel: "tool_approval",
6125
- prompt: `Tool "${toolName}" wants to execute with args: ${JSON.stringify(toolArgs)}`,
6126
- metadata: { toolName, args: toolArgs, agent: agent2._name }
6127
- });
6128
- if (!decision.approved) {
6129
- const reason = decision.reason ?? "Denied by human";
6130
- currentMessages.push({
6131
- role: "tool",
6132
- content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
6133
- tool_call_id: toolCall.id
6134
- });
6135
- return { approved: false, reason };
6136
- }
6137
- return { approved: true };
6138
- };
6139
- let approvalOutcome;
6140
- if (this.spanManager) {
6141
- approvalOutcome = await this.spanManager.withSpanAsync(
6142
- "axl.tool.approval",
6143
- {
6144
- "axl.tool.name": toolName,
6145
- "axl.agent.name": agent2._name
6146
- },
6147
- async (span) => {
6148
- const result = await approvalFn();
6149
- span.setAttribute("axl.tool.approval.approved", result.approved);
6150
- return result;
6151
- }
6152
- );
6153
- } else {
6154
- approvalOutcome = await approvalFn();
6155
- }
6156
- this.emitEvent({
6157
- type: "tool_approval",
6158
- agent: agent2._name,
6159
- tool: toolName,
6160
- data: {
6161
- approved: approvalOutcome.approved,
6162
- args: toolArgs,
6163
- ...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
6164
- }
6165
- });
6166
- if (!approvalOutcome.approved) continue;
6167
- }
6168
- if (tool2 && tool2.hooks?.before) {
6169
- try {
6170
- toolArgs = await tool2.hooks.before(toolArgs, this);
6171
- } catch (err) {
6172
- const errorMsg = err instanceof Error ? err.message : String(err);
6173
- currentMessages.push({
6174
- role: "tool",
6175
- content: JSON.stringify({ error: `Before hook error: ${errorMsg}` }),
6176
- tool_call_id: toolCall.id
6177
- });
6178
- continue;
6179
- }
6180
- }
6181
- const executeTool = async () => {
6182
- let toolResult2;
6183
- let resultContent2;
6184
- if (isMcpTool && this.mcpManager) {
6185
- try {
6186
- const mcpResult = await this.mcpManager.callTool(toolName, toolArgs);
6187
- toolResult2 = mcpResult;
6188
- resultContent2 = mcpResult.content.map(
6189
- (c) => c.type === "text" ? c.text : `[${c.type}]`
6190
- ).join("\n");
6191
- if (mcpResult.isError) {
6192
- resultContent2 = `Error: ${resultContent2}`;
6193
- }
6194
- } catch (err) {
6195
- toolResult2 = { error: err instanceof Error ? err.message : String(err) };
6196
- resultContent2 = JSON.stringify(toolResult2);
6197
- }
6198
- } else if (tool2) {
6199
- const childCtx = this.createChildContext();
6200
- try {
6201
- toolResult2 = await tool2._execute(toolArgs, childCtx);
6202
- } catch (err) {
6203
- toolResult2 = { error: err instanceof Error ? err.message : String(err) };
6204
- }
6205
- if (tool2.hooks?.after && !(toolResult2 && typeof toolResult2 === "object" && "error" in toolResult2)) {
6206
- try {
6207
- toolResult2 = await tool2.hooks.after(toolResult2, this);
6208
- } catch (err) {
6209
- toolResult2 = {
6210
- error: `After hook error: ${err instanceof Error ? err.message : String(err)}`
6211
- };
6212
- }
6213
- }
6214
- resultContent2 = tool2.sensitive ? "[REDACTED - sensitive tool output]" : JSON.stringify(toolResult2);
6215
- } else {
6216
- toolResult2 = void 0;
6217
- resultContent2 = "Tool execution error";
6218
- }
6219
- return { toolResult: toolResult2, resultContent: resultContent2 };
6220
- };
6221
- const traceName = isMcpTool && this.mcpManager ? this.mcpManager.getQualifiedName(toolName) ?? toolName : toolName;
6222
- const { toolResult, resultContent } = this.spanManager ? await this.spanManager.withSpanAsync(
6223
- "axl.tool.call",
6224
- {
6225
- "axl.tool.name": traceName,
6226
- "axl.agent.name": agent2._name
6227
- },
6228
- async (span) => {
6229
- const r = await executeTool();
6230
- span.setAttribute("axl.tool.duration", Date.now() - toolStart);
6231
- const isError = r.toolResult && typeof r.toolResult === "object" && "error" in r.toolResult;
6232
- span.setAttribute("axl.tool.success", !isError);
6233
- if (isError)
6234
- span.setStatus(
6235
- "error",
6236
- r.toolResult.error
6237
- );
6238
- return r;
6239
- }
6240
- ) : await executeTool();
6241
- this.emitEvent({
6242
- type: "tool_call_end",
6243
- agent: agent2._name,
6244
- tool: traceName,
6245
- duration: Date.now() - toolStart,
6246
- data: { args: toolArgs, result: toolResult, callId: toolCall.id }
6247
- });
6248
- currentMessages.push({
6249
- role: "tool",
6250
- content: resultContent,
6251
- tool_call_id: toolCall.id
6252
- });
7209
+ await this.observeToolInvocation(
7210
+ agent2,
7211
+ toolCall,
7212
+ currentMessages,
7213
+ toolDefs.map((tool2) => tool2.function.name)
7214
+ );
6253
7215
  }
6254
7216
  continue;
6255
7217
  }
@@ -6369,7 +7331,7 @@ ${appendedSchemaText}`;
6369
7331
  pendingRetryReason = "schema";
6370
7332
  continue;
6371
7333
  }
6372
- 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([
6373
7335
  {
6374
7336
  code: "custom",
6375
7337
  path: [],
@@ -6386,6 +7348,7 @@ ${appendedSchemaText}`;
6386
7348
  metadata: this.metadata
6387
7349
  });
6388
7350
  } catch (err) {
7351
+ rethrowEventStreamOverflow(err);
6389
7352
  const reason = err instanceof Error ? err.message : String(err);
6390
7353
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
6391
7354
  }
@@ -6554,7 +7517,7 @@ ${appendedSchemaText}`;
6554
7517
  );
6555
7518
  }
6556
7519
  }
6557
- const rootIsObject = schema instanceof import_zod.z.ZodObject;
7520
+ const rootIsObject = schema instanceof import_zod2.z.ZodObject;
6558
7521
  const hasTools = toolDefs.length > 0;
6559
7522
  if (this._streamingEnabled && (!rootIsObject || hasTools)) {
6560
7523
  const cause = !rootIsObject ? "non-object" : "tools";
@@ -6722,12 +7685,12 @@ ${summaryResponse.content}`
6722
7685
  * Execute a function with checkpoint-replay semantics.
6723
7686
  *
6724
7687
  * On first execution, runs `fn()`, saves the result under `name`, and
6725
- * returns it. On replay (resume after restart), returns the saved result
6726
- * without re-executing. This prevents duplicate side effects (double API
6727
- * 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.
6728
7691
  *
6729
7692
  * `name` must be a stable, caller-supplied identifier — the same call
6730
- * 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
6731
7694
  * replay to work. Names are scoped to a single execution and must be
6732
7695
  * unique within it; reusing a name (e.g. inside a loop) gives
6733
7696
  * last-write-wins behavior. For loops, compose names from the iterator:
@@ -6850,6 +7813,7 @@ ${summaryResponse.content}`
6850
7813
  for (let i = 0; i < n; i++) {
6851
7814
  const index = i;
6852
7815
  const p = signalStorage.run(composedSignal, () => fn(index));
7816
+ this._registerBranchDrain(p);
6853
7817
  p.then((value) => {
6854
7818
  if (settled) return;
6855
7819
  results[index] = { ok: true, value };
@@ -6864,6 +7828,14 @@ ${summaryResponse.content}`
6864
7828
  reject(new QuorumNotMet(quorum, successCount, results));
6865
7829
  }
6866
7830
  }).catch((err) => {
7831
+ if (isEventStreamOverflowError(err)) {
7832
+ if (!settled) {
7833
+ settled = true;
7834
+ controller.abort();
7835
+ reject(err);
7836
+ }
7837
+ return;
7838
+ }
6867
7839
  if (settled) return;
6868
7840
  const isAbort = err instanceof DOMException && err.name === "AbortError";
6869
7841
  if (isAbort) {
@@ -6886,14 +7858,16 @@ ${summaryResponse.content}`
6886
7858
  }
6887
7859
  const parentSignal = this.currentSignal;
6888
7860
  const promises = Array.from({ length: n }, (_, i) => {
6889
- const run = () => fn(i).then((value) => ({ ok: true, value })).catch(
6890
- (err) => ({
7861
+ const run = () => fn(i).then((value) => ({ ok: true, value })).catch((err) => {
7862
+ rethrowEventStreamOverflow(err);
7863
+ return {
6891
7864
  ok: false,
6892
7865
  error: err instanceof Error ? err.message : String(err)
6893
- })
6894
- );
7866
+ };
7867
+ });
6895
7868
  return parentSignal ? signalStorage.run(parentSignal, run) : run();
6896
7869
  });
7870
+ for (const promise of promises) this._registerBranchDrain(promise);
6897
7871
  return Promise.all(promises);
6898
7872
  }
6899
7873
  // ── ctx.vote() ────────────────────────────────────────────────────────
@@ -7031,6 +8005,7 @@ ${summaryResponse.content}`
7031
8005
  try {
7032
8006
  validateResult = await options.validate(parsed, { metadata: this.metadata });
7033
8007
  } catch (err) {
8008
+ rethrowEventStreamOverflow(err);
7034
8009
  const reason = err instanceof Error ? err.message : String(err);
7035
8010
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
7036
8011
  }
@@ -7048,6 +8023,7 @@ ${summaryResponse.content}`
7048
8023
  emitVerifyOutcome(true, attempt + 1);
7049
8024
  return parsed;
7050
8025
  } catch (err) {
8026
+ rethrowEventStreamOverflow(err);
7051
8027
  if (err instanceof ValidationError) {
7052
8028
  lastRetry = {
7053
8029
  error: err.reason,
@@ -7070,12 +8046,12 @@ ${summaryResponse.content}`
7070
8046
  }
7071
8047
  continue;
7072
8048
  }
7073
- 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);
7074
8050
  lastRetry = { error: errorMsg, output: rawOutput };
7075
8051
  if (attempt === maxRetries) {
7076
8052
  emitVerifyOutcome(false, attempt + 1, errorMsg);
7077
8053
  if (options?.fallback !== void 0) return options.fallback;
7078
- 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 }]);
7079
8055
  throw new VerifyError(rawOutput, zodErr, maxRetries);
7080
8056
  }
7081
8057
  }
@@ -7084,7 +8060,7 @@ ${summaryResponse.content}`
7084
8060
  if (options?.fallback !== void 0) return options.fallback;
7085
8061
  throw new VerifyError(
7086
8062
  lastRetry?.output,
7087
- new import_zod.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
8063
+ new import_zod2.ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
7088
8064
  maxRetries
7089
8065
  );
7090
8066
  }
@@ -7114,6 +8090,7 @@ ${summaryResponse.content}`
7114
8090
  const unpriced = this.budgetContext.unpriced;
7115
8091
  return { value, budgetExceeded: exceeded, totalCost, unpriced };
7116
8092
  } catch (err) {
8093
+ rethrowEventStreamOverflow(err);
7117
8094
  if (this.budgetContext.exceeded) {
7118
8095
  return {
7119
8096
  value: null,
@@ -7214,7 +8191,8 @@ ${summaryResponse.content}`
7214
8191
  let remaining = fns.length;
7215
8192
  for (const fn of fns) {
7216
8193
  const p = signalStorage.run(composedSignal, fn);
7217
- p.then(async (value) => {
8194
+ this._registerBranchDrain(p);
8195
+ const continuation = p.then(async (value) => {
7218
8196
  if (settled) return;
7219
8197
  if (schema) {
7220
8198
  const parsed = schema.safeParse(value);
@@ -7244,6 +8222,7 @@ ${summaryResponse.content}`
7244
8222
  return;
7245
8223
  }
7246
8224
  } catch (err) {
8225
+ rethrowEventStreamOverflow(err);
7247
8226
  remaining--;
7248
8227
  lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
7249
8228
  if (remaining === 0 && !settled) {
@@ -7263,6 +8242,14 @@ ${summaryResponse.content}`
7263
8242
  controller.abort();
7264
8243
  resolve2(value);
7265
8244
  }).catch((err) => {
8245
+ if (isEventStreamOverflowError(err)) {
8246
+ if (!settled) {
8247
+ settled = true;
8248
+ controller.abort();
8249
+ reject(err);
8250
+ }
8251
+ throw err;
8252
+ }
7266
8253
  if (settled) return;
7267
8254
  if (err instanceof DOMException && err.name === "AbortError") {
7268
8255
  remaining--;
@@ -7279,6 +8266,7 @@ ${summaryResponse.content}`
7279
8266
  reject(lastError);
7280
8267
  }
7281
8268
  });
8269
+ this._registerBranchDrain(continuation);
7282
8270
  }
7283
8271
  });
7284
8272
  }
@@ -7317,9 +8305,19 @@ ${summaryResponse.content}`
7317
8305
  const idx = nextIndex++;
7318
8306
  try {
7319
8307
  const value = mapSignal ? await signalStorage.run(mapSignal, () => fn(items[idx], idx)) : await fn(items[idx], idx);
8308
+ if (settled) return;
7320
8309
  results[idx] = { ok: true, value };
7321
8310
  successCount++;
7322
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
+ }
7323
8321
  if (err instanceof DOMException && err.name === "AbortError" && controller?.signal.aborted) {
7324
8322
  completedCount++;
7325
8323
  return;
@@ -7345,7 +8343,9 @@ ${summaryResponse.content}`
7345
8343
  };
7346
8344
  const workers = Math.min(concurrency, items.length);
7347
8345
  for (let i = 0; i < workers; i++) {
7348
- runNext().catch((err) => {
8346
+ const worker = runNext();
8347
+ this._registerBranchDrain(worker);
8348
+ worker.catch((err) => {
7349
8349
  if (!settled) reject(err);
7350
8350
  });
7351
8351
  }
@@ -7371,12 +8371,44 @@ ${summaryResponse.content}`
7371
8371
  return this._awaitHumanImpl(options);
7372
8372
  }
7373
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
+ };
7374
8402
  if (this.awaitHumanHandler) {
8403
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
7375
8404
  this.emitEvent({
7376
8405
  type: "await_human",
7377
8406
  data: { channel: options.channel, prompt: options.prompt }
7378
8407
  });
7379
- 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
+ );
7380
8412
  this.emitEvent({
7381
8413
  type: "await_human_resolved",
7382
8414
  data: { channel: options.channel, decision: decision2 }
@@ -7388,68 +8420,126 @@ ${summaryResponse.content}`
7388
8420
  "Tool requires approval but no approval handler is configured. Provide awaitHumanHandler to createContext() or use runtime.execute() with workflow infrastructure."
7389
8421
  );
7390
8422
  }
7391
- if (this.stateStore) {
7392
- await this.stateStore.savePendingDecision(this.executionId, {
7393
- executionId: this.executionId,
7394
- channel: options.channel,
7395
- prompt: options.prompt,
7396
- metadata: options.metadata,
7397
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7398
- });
7399
- await this.stateStore.saveExecutionState(this.executionId, {
7400
- workflow: this.workflowName ?? "unknown",
7401
- input: this.input,
7402
- step: this.stepRefRoot.value,
7403
- status: "waiting",
7404
- metadata: {
7405
- ...this.metadata,
7406
- awaitHumanChannel: options.channel,
7407
- awaitHumanPrompt: options.prompt
7408
- }
7409
- });
7410
- }
7411
- this.emitEvent({
7412
- type: "await_human",
7413
- 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;
7414
8439
  });
7415
- const makeAbortError = (reason) => {
7416
- if (typeof DOMException !== "undefined") {
7417
- return new DOMException(
7418
- typeof reason === "string" ? reason : "awaitHuman aborted",
7419
- "AbortError"
7420
- );
7421
- }
7422
- const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
7423
- err.name = "AbortError";
7424
- 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
7425
8458
  };
7426
- const decision = await new Promise((resolve2, reject) => {
7427
- if (this.signal?.aborted) {
7428
- reject(makeAbortError(this.signal.reason));
7429
- 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
+ });
7430
8478
  }
7431
- this.pendingDecisions.set(this.executionId, resolve2);
7432
- const onAbort = () => {
7433
- const current = this.pendingDecisions.get(this.executionId);
7434
- if (current === resolve2) {
7435
- 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
+ }
7436
8526
  }
7437
- reject(makeAbortError(this.signal?.reason));
7438
- };
7439
- this.signal?.addEventListener("abort", onAbort, { once: true });
7440
- });
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
+ }
7441
8539
  this.emitEvent({
7442
8540
  type: "await_human_resolved",
7443
8541
  data: { channel: options.channel, decision }
7444
8542
  });
7445
- if (this.stateStore) {
7446
- await this.stateStore.saveExecutionState(this.executionId, {
7447
- workflow: this.workflowName ?? "unknown",
7448
- input: this.input,
7449
- step: this.stepRefRoot.value,
7450
- status: "running"
7451
- });
7452
- }
7453
8543
  return decision;
7454
8544
  }
7455
8545
  /**
@@ -7501,8 +8591,8 @@ ${summaryResponse.content}`
7501
8591
  // Internal: the runtime is the only caller, user workflows never call these.
7502
8592
  /** @internal — idempotent; no-ops on second+ call within one ctx. */
7503
8593
  _emitWorkflowStart(input) {
7504
- if (this._workflowStartEmitted) return;
7505
- this._workflowStartEmitted = true;
8594
+ if (this.workflowLifecycleState.startEmitted) return;
8595
+ this.workflowLifecycleState.startEmitted = true;
7506
8596
  this.emitEvent({
7507
8597
  type: "workflow_start",
7508
8598
  workflow: this.workflowName,
@@ -7518,8 +8608,8 @@ ${summaryResponse.content}`
7518
8608
  * status. First-wins semantics: the completed event stands, the
7519
8609
  * inner side-effect failure propagates as a normal thrown error. */
7520
8610
  _emitWorkflowEnd(info) {
7521
- if (this._workflowEndEmitted) return;
7522
- this._workflowEndEmitted = true;
8611
+ if (this.workflowLifecycleState.endEmitted) return;
8612
+ this.workflowLifecycleState.endEmitted = true;
7523
8613
  this.emitEvent({
7524
8614
  type: "workflow_end",
7525
8615
  workflow: this.workflowName,
@@ -7529,7 +8619,8 @@ ${summaryResponse.content}`
7529
8619
  duration: info.duration,
7530
8620
  ...info.result !== void 0 ? { result: info.result } : {},
7531
8621
  ...info.error !== void 0 ? { error: info.error } : {},
7532
- ...info.aborted ? { aborted: true } : {}
8622
+ ...info.aborted ? { aborted: true } : {},
8623
+ ...info.observation ? { observation: info.observation } : {}
7533
8624
  }
7534
8625
  });
7535
8626
  }
@@ -7818,6 +8909,7 @@ ${summaryResponse.content}`
7818
8909
  * consumer sites via the `type` discriminator.
7819
8910
  */
7820
8911
  emitEvent(partial) {
8912
+ if (this.workflowLifecycleState.endEmitted && partial.type !== "workflow_end") return;
7821
8913
  const data = partial.data;
7822
8914
  const frame = askStorage.getStore();
7823
8915
  const step = (frame?.stepRef ?? this.stepRefRoot).value++;
@@ -7843,6 +8935,7 @@ ${summaryResponse.content}`
7843
8935
  }
7844
8936
  }
7845
8937
  const event = {
8938
+ schemaVersion: 2,
7846
8939
  executionId: this.executionId,
7847
8940
  step,
7848
8941
  timestamp: Date.now(),
@@ -7863,13 +8956,13 @@ ${summaryResponse.content}`
7863
8956
  ...partial,
7864
8957
  data
7865
8958
  };
7866
- const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
8959
+ const finalEvent = this.config.trace?.redact ? redactHistoricalEvent(event) : event;
7867
8960
  let overflowErr;
7868
8961
  if (this.onTrace) {
7869
8962
  try {
7870
8963
  this.onTrace(finalEvent);
7871
8964
  } catch (err) {
7872
- if (err instanceof EventStreamOverflowError) {
8965
+ if (isEventStreamOverflowError(err)) {
7873
8966
  overflowErr = err;
7874
8967
  } else {
7875
8968
  console.error(
@@ -7884,7 +8977,7 @@ ${summaryResponse.content}`
7884
8977
  try {
7885
8978
  bus._push(finalEvent);
7886
8979
  } catch (err) {
7887
- if (err instanceof EventStreamOverflowError) {
8980
+ if (isEventStreamOverflowError(err)) {
7888
8981
  if (!overflowErr) {
7889
8982
  overflowErr = err;
7890
8983
  } else {
@@ -8071,26 +9164,31 @@ var MemoryStore = class {
8071
9164
  }
8072
9165
  // ── Execution History ──────────────────────────────────────────────
8073
9166
  async saveExecution(execution) {
8074
- 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));
8075
9172
  }
8076
9173
  async getExecution(executionId) {
8077
9174
  const exec = this.executionHistory.get(executionId);
8078
- return exec ? structuredClone(exec) : null;
9175
+ return exec ? normalizeStoredExecution(structuredClone(exec)) : null;
8079
9176
  }
8080
9177
  async listExecutions(limit) {
8081
9178
  const sorted = [...this.executionHistory.values()].sort((a, b) => b.startedAt - a.startedAt);
8082
9179
  const result = limit ? sorted.slice(0, limit) : sorted;
8083
- return result.map((e) => structuredClone(e));
9180
+ return result.map((e) => normalizeStoredExecution(structuredClone(e)));
8084
9181
  }
8085
9182
  async deleteExecution(executionId) {
8086
- const removed = this.executionHistory.delete(executionId);
8087
- this.checkpoints.delete(executionId);
8088
- this.executionStates.delete(executionId);
8089
- this.streamingEvents.delete(executionId);
8090
- 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) {
8091
9189
  this.persistAwaitHumanState();
8092
9190
  }
8093
- return removed;
9191
+ return removedHistory || removedCheckpoints || removedState || removedStreaming || removedDecision;
8094
9192
  }
8095
9193
  // ── Eval History ──────────────────────────────────────────────────
8096
9194
  async saveEvalResult(entry) {
@@ -8196,16 +9294,23 @@ function rowToExecutionInfo(row) {
8196
9294
  startedAt: row.started_at,
8197
9295
  completedAt: row.completed_at ?? void 0,
8198
9296
  duration: row.duration,
8199
- error: row.error ?? void 0,
8200
- events: safeJsonParse(row.events) ?? []
9297
+ events: safeJsonParse(row.events) ?? [],
9298
+ ...row.event_schema_version != null ? { eventSchemaVersion: row.event_schema_version } : {}
8201
9299
  };
9300
+ if (row.error != null) info.error = row.error;
8202
9301
  if (row.metadata != null) {
8203
9302
  const parsed = safeJsonParse(row.metadata);
8204
9303
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8205
9304
  info.metadata = parsed;
8206
9305
  }
8207
9306
  }
8208
- 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);
8209
9314
  }
8210
9315
  var SQLiteStore = class {
8211
9316
  db;
@@ -8245,6 +9350,10 @@ var SQLiteStore = class {
8245
9350
  * column (TEXT, nullable, JSON-serialized) so caller-supplied
8246
9351
  * `ExecuteOptions.metadata` round-trips through history. Existing
8247
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.
8248
9357
  *
8249
9358
  * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
8250
9359
  * IF NOT EXISTS` runs against the post-migration column name. Fresh
@@ -8262,7 +9371,7 @@ var SQLiteStore = class {
8262
9371
  * transactional re-read costs nothing.
8263
9372
  */
8264
9373
  migrate() {
8265
- const TARGET_VERSION = 3;
9374
+ const TARGET_VERSION = 5;
8266
9375
  const current = this.db.pragma("user_version", { simple: true });
8267
9376
  if (current >= TARGET_VERSION) return;
8268
9377
  this.db.exec("BEGIN IMMEDIATE");
@@ -8290,6 +9399,18 @@ var SQLiteStore = class {
8290
9399
  this.db.exec("ALTER TABLE execution_history ADD COLUMN metadata TEXT");
8291
9400
  }
8292
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
+ }
8293
9414
  this.db.pragma(`user_version = ${TARGET_VERSION}`);
8294
9415
  this.db.exec("COMMIT");
8295
9416
  } catch (err) {
@@ -8353,7 +9474,9 @@ var SQLiteStore = class {
8353
9474
  duration INTEGER NOT NULL DEFAULT 0,
8354
9475
  error TEXT,
8355
9476
  events TEXT NOT NULL,
8356
- metadata TEXT
9477
+ metadata TEXT,
9478
+ event_schema_version INTEGER,
9479
+ observation TEXT
8357
9480
  );
8358
9481
 
8359
9482
  CREATE TABLE IF NOT EXISTS eval_history (
@@ -8438,7 +9561,7 @@ var SQLiteStore = class {
8438
9561
  const stmt = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?");
8439
9562
  stmt.run(executionId);
8440
9563
  }
8441
- // ── Execution State (for awaitHuman suspend/resume) ────────────────────
9564
+ // ── Legacy application-managed execution state ────────────────────────
8442
9565
  async saveExecutionState(executionId, state) {
8443
9566
  const stmt = this.db.prepare(
8444
9567
  "INSERT OR REPLACE INTO execution_state (execution_id, workflow, input, step, status, metadata) VALUES (?, ?, ?, ?, ?, ?)"
@@ -8473,19 +9596,25 @@ var SQLiteStore = class {
8473
9596
  }
8474
9597
  // ── Execution History ────────────────────────────────────────────────────
8475
9598
  async saveExecution(execution) {
9599
+ const normalized = normalizeStoredExecution({
9600
+ ...execution,
9601
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
9602
+ });
8476
9603
  this.db.prepare(
8477
- "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8478
9605
  ).run(
8479
- execution.executionId,
8480
- execution.workflow,
8481
- execution.status,
8482
- execution.totalCost,
8483
- execution.startedAt,
8484
- execution.completedAt ?? null,
8485
- execution.duration,
8486
- execution.error ?? null,
8487
- JSON.stringify(execution.events),
8488
- 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
8489
9618
  );
8490
9619
  }
8491
9620
  async getExecution(executionId) {
@@ -8499,11 +9628,11 @@ var SQLiteStore = class {
8499
9628
  }
8500
9629
  async deleteExecution(executionId) {
8501
9630
  const tx = this.db.transaction(() => {
8502
- const result = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
8503
- this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
8504
- this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
8505
- this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
8506
- 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;
8507
9636
  });
8508
9637
  return tx();
8509
9638
  }
@@ -8829,6 +9958,11 @@ var AxlStream = class extends import_node_stream.Readable {
8829
9958
  get lifecycle() {
8830
9959
  return this.events.lifecycle;
8831
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
+ }
8832
9966
  /** Coalescing iterator over `partial_object` events — yields the latest
8833
9967
  * object payload per `askId`. Designed for streaming-structured-output
8834
9968
  * UIs: when the producer outpaces the consumer, intermediate snapshots
@@ -8927,6 +10061,7 @@ var AxlStream = class extends import_node_stream.Readable {
8927
10061
  if (this.events.isFinished) return;
8928
10062
  this.result = result;
8929
10063
  const doneEvent = {
10064
+ schemaVersion: 2,
8930
10065
  type: "done",
8931
10066
  executionId,
8932
10067
  step: Number.MAX_SAFE_INTEGER,
@@ -8945,6 +10080,7 @@ var AxlStream = class extends import_node_stream.Readable {
8945
10080
  _error(error, executionId) {
8946
10081
  if (this.events.isFinished) return;
8947
10082
  const errorEvent = {
10083
+ schemaVersion: 2,
8948
10084
  type: "error",
8949
10085
  executionId,
8950
10086
  step: Number.MAX_SAFE_INTEGER,
@@ -9545,11 +10681,7 @@ var STREAMING_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
9545
10681
  "partial_object",
9546
10682
  "string_delta"
9547
10683
  ]);
9548
- var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set([
9549
- "sessionId",
9550
- "sessionHistory",
9551
- "resumeMode"
9552
- ]);
10684
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["sessionId", "sessionHistory"]);
9553
10685
  function liftPersistedMetadata(metadata) {
9554
10686
  if (!metadata) return void 0;
9555
10687
  const filtered = {};
@@ -9667,7 +10799,7 @@ var StreamingFlusher = class {
9667
10799
  await Promise.allSettled(pendings);
9668
10800
  }
9669
10801
  };
9670
- function isAbortError2(err) {
10802
+ function isAbortError4(err) {
9671
10803
  return typeof err === "object" && err !== null && err.name === "AbortError";
9672
10804
  }
9673
10805
  function pushEventBounded(execInfo, event, cap) {
@@ -9679,7 +10811,18 @@ function pushEventBounded(execInfo, event, cap) {
9679
10811
  const last = execInfo.events[execInfo.events.length - 1];
9680
10812
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9681
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
+ }
9682
10824
  execInfo.events[execInfo.events.length - 1] = {
10825
+ schemaVersion: 2,
9683
10826
  type: "log",
9684
10827
  executionId: event.executionId,
9685
10828
  step: event.step,
@@ -9708,7 +10851,9 @@ function boundRecoveredEvents(events, cap) {
9708
10851
  const last = truncated[truncated.length - 1];
9709
10852
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9710
10853
  if (!alreadyTruncated) {
10854
+ const version = getEventSchemaVersion(events[0] ?? {});
9711
10855
  truncated[truncated.length - 1] = {
10856
+ ...version === 2 ? { schemaVersion: 2 } : {},
9712
10857
  type: "log",
9713
10858
  executionId: events[0]?.executionId ?? "",
9714
10859
  step: last?.step ?? 0,
@@ -9722,6 +10867,16 @@ function boundRecoveredEvents(events, cap) {
9722
10867
  }
9723
10868
  return truncated;
9724
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
+ }
9725
10880
  var costScopeStorage = new import_node_async_hooks2.AsyncLocalStorage();
9726
10881
  function forwardAbortSignal(external, internal) {
9727
10882
  if (!external) return () => {
@@ -9883,9 +11038,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
9883
11038
  * → parse output → emit `workflow_end` → delete checkpoints → persist.
9884
11039
  *
9885
11040
  * Shared between `execute()` and `stream()`. Centralizes the
9886
- * "start iff end" pairing invariant so a future emit path
9887
- * (e.g., `resumeExecution`) can inherit correct behavior by routing
9888
- * through this helper. Also factors out the AbortError detection.
11041
+ * "start iff end" pairing invariant and AbortError detection.
9889
11042
  *
9890
11043
  * Side effects on `execInfo`: sets `status` / `completedAt` /
9891
11044
  * `duration` / `result` (or `error`) before emitting `workflow_end`.
@@ -9909,18 +11062,23 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
9909
11062
  */
9910
11063
  async runWorkflowBody(args) {
9911
11064
  const { workflow: workflow2, ctx, execInfo, validated, span, cleanupAbortForwarder } = args;
11065
+ let observation = execInfo.observation ?? { complete: true };
9912
11066
  try {
9913
11067
  ctx._emitWorkflowStart(validated);
9914
11068
  const raw = await workflow2.handler(ctx);
11069
+ const drainedObservation = await ctx._drainBranchWork();
11070
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
9915
11071
  const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
9916
11072
  execInfo.status = "completed";
9917
11073
  execInfo.completedAt = Date.now();
9918
11074
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9919
11075
  execInfo.result = result;
11076
+ execInfo.observation = observation;
9920
11077
  ctx._emitWorkflowEnd({
9921
11078
  status: "completed",
9922
11079
  duration: execInfo.duration,
9923
- result
11080
+ result,
11081
+ observation
9924
11082
  });
9925
11083
  if (this.stateStore.deleteCheckpoints) {
9926
11084
  await this.stateStore.deleteCheckpoints(execInfo.executionId);
@@ -9930,19 +11088,28 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
9930
11088
  this.persistExecution(execInfo);
9931
11089
  return result;
9932
11090
  } catch (err) {
9933
- 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);
9934
11099
  execInfo.status = "failed";
9935
11100
  execInfo.completedAt = Date.now();
9936
11101
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9937
- execInfo.error = err instanceof Error ? err.message : String(err);
11102
+ execInfo.error = terminalError instanceof Error ? terminalError.message : String(terminalError);
11103
+ execInfo.observation = observation;
9938
11104
  ctx._emitWorkflowEnd({
9939
11105
  status: "failed",
9940
11106
  duration: execInfo.duration,
9941
11107
  error: execInfo.error,
9942
- ...aborted ? { aborted: true } : {}
11108
+ ...aborted ? { aborted: true } : {},
11109
+ observation
9943
11110
  });
9944
11111
  this.persistExecution(execInfo);
9945
- throw err;
11112
+ throw terminalError;
9946
11113
  } finally {
9947
11114
  this.abortControllers.delete(execInfo.executionId);
9948
11115
  cleanupAbortForwarder?.();
@@ -10130,13 +11297,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10130
11297
  return result;
10131
11298
  }
10132
11299
  /** Get all execution info (running + completed + historical). */
10133
- /** Normalize an `ExecutionInfo` loaded from a `StateStore` so the
10134
- * `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
10135
11302
  * (Studio aggregators, REST routes, redaction). Custom or legacy stores
10136
11303
  * may persist rows without `events` (or with a non-array value) — without
10137
11304
  * this guard, a single malformed row crashes every iterator over
10138
- * `exec.events` and takes down dependent features. Mutates the row
10139
- * 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`. */
10140
11307
  normalizeStoredExecution(exec) {
10141
11308
  if (!Array.isArray(exec.events)) {
10142
11309
  if (!this.warnedMalformedExecutions.has(exec.executionId)) {
@@ -10145,9 +11312,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10145
11312
  `[axl] StateStore returned execution ${exec.executionId} with non-array events (got ${exec.events === null ? "null" : typeof exec.events}); coercing to []`
10146
11313
  );
10147
11314
  }
10148
- exec.events = [];
11315
+ return normalizeStoredExecution({ ...exec, events: [] });
10149
11316
  }
10150
- return exec;
11317
+ return normalizeStoredExecution(exec);
10151
11318
  }
10152
11319
  async getExecutions() {
10153
11320
  if (!this.historicalExecutionsLoadPromise && this.stateStore.listExecutions) {
@@ -10157,8 +11324,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10157
11324
  this.historicalExecutions.set(exec.executionId, this.normalizeStoredExecution(exec));
10158
11325
  }
10159
11326
  }
10160
- }).catch(() => {
11327
+ }).catch((error) => {
10161
11328
  this.historicalExecutionsLoadPromise = null;
11329
+ throw error;
10162
11330
  });
10163
11331
  }
10164
11332
  if (this.historicalExecutionsLoadPromise) {
@@ -10176,6 +11344,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10176
11344
  * and automatically emits trace events and tracks cost.
10177
11345
  */
10178
11346
  createContext(options) {
11347
+ assertNoRemovedObservationCallbacks(options);
10179
11348
  const executionId = (0, import_node_crypto3.randomUUID)();
10180
11349
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
10181
11350
  this.registerWithCostScope(executionId);
@@ -10191,10 +11360,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10191
11360
  memoryManager: this.memoryManager,
10192
11361
  sessionHistory: options?.sessionHistory,
10193
11362
  signal: options?.signal,
10194
- onToken: options?.onToken,
10195
- onToolCall: options?.onToolCall,
10196
- onAgentStart: options?.onAgentStart,
10197
11363
  awaitHumanHandler: options?.awaitHumanHandler,
11364
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10198
11365
  eventStreamOptions: options?.events,
10199
11366
  onTrace: (event) => {
10200
11367
  this.emit("trace", event);
@@ -10228,6 +11395,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10228
11395
  );
10229
11396
  }
10230
11397
  const validated = workflow2.inputSchema.parse(input);
11398
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10231
11399
  const executionId = (0, import_node_crypto3.randomUUID)();
10232
11400
  const controller = new AbortController();
10233
11401
  this.abortControllers.set(executionId, controller);
@@ -10240,11 +11408,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10240
11408
  executionId,
10241
11409
  workflow: name,
10242
11410
  status: "running",
11411
+ eventSchemaVersion: 2,
10243
11412
  events: [],
10244
11413
  totalCost: 0,
10245
11414
  unpriced: false,
10246
11415
  startedAt: Date.now(),
10247
11416
  duration: 0,
11417
+ observation: { complete: true },
10248
11418
  metadata: liftPersistedMetadata(options?.metadata)
10249
11419
  };
10250
11420
  this.executions.set(executionId, execInfo);
@@ -10258,6 +11428,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10258
11428
  sessionHistory,
10259
11429
  signal: controller.signal,
10260
11430
  eventStreamOptions: options?.events,
11431
+ branchDrainTimeoutMs,
11432
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10261
11433
  onTrace: (event) => {
10262
11434
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10263
11435
  execInfo.totalCost += eventCostContribution(event);
@@ -10302,7 +11474,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10302
11474
  workflowName: name,
10303
11475
  mcpManager: this.mcpManager,
10304
11476
  memoryManager: this.memoryManager,
10305
- resumeMode: !!options?.metadata?.resumeMode,
10306
11477
  spanManager: this.spanManager,
10307
11478
  budgetContext: {
10308
11479
  totalCost: 0,
@@ -10333,6 +11504,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10333
11504
  }
10334
11505
  /** Execute a workflow and return a stream. */
10335
11506
  stream(name, input, options) {
11507
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10336
11508
  const axlStream = new AxlStream(options?.events);
10337
11509
  const controller = new AbortController();
10338
11510
  axlStream.on("close", () => controller.abort());
@@ -10353,11 +11525,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10353
11525
  executionId,
10354
11526
  workflow: name,
10355
11527
  status: "running",
11528
+ eventSchemaVersion: 2,
10356
11529
  events: [],
10357
11530
  totalCost: 0,
10358
11531
  unpriced: false,
10359
11532
  startedAt: Date.now(),
10360
11533
  duration: 0,
11534
+ observation: { complete: true },
10361
11535
  metadata: liftPersistedMetadata(options?.metadata)
10362
11536
  };
10363
11537
  this.executions.set(executionId, execInfo);
@@ -10370,12 +11544,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10370
11544
  sessionHistory,
10371
11545
  signal: controller.signal,
10372
11546
  eventStreamOptions: options?.events,
10373
- // Sentinel `onToken` so the streaming code path in WorkflowContext
10374
- // is entered (it's gated on `this.onToken` being defined). The
10375
- // actual `token` AxlEvents are emitted via `emitEvent` and reach
10376
- // the wire through `onTrace` — this callback is just the gate.
10377
- onToken: () => {
10378
- },
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,
10379
11552
  onTrace: (event) => {
10380
11553
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10381
11554
  execInfo.totalCost += eventCostContribution(event);
@@ -10421,7 +11594,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10421
11594
  workflowName: name,
10422
11595
  mcpManager: this.mcpManager,
10423
11596
  memoryManager: this.memoryManager,
10424
- resumeMode: !!options?.metadata?.resumeMode,
10425
11597
  spanManager: this.spanManager,
10426
11598
  budgetContext: {
10427
11599
  totalCost: 0,
@@ -10585,6 +11757,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10585
11757
  await this.stateStore.finalizeStreamingEvents?.(id);
10586
11758
  continue;
10587
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
+ }
10588
11766
  const boundedEvents = boundRecoveredEvents(events, this.maxEventsPerExecution);
10589
11767
  const workflowStartEvent = boundedEvents.find((e) => e.type === "workflow_start");
10590
11768
  const lastEvent = boundedEvents[boundedEvents.length - 1];
@@ -10593,7 +11771,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10593
11771
  const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
10594
11772
  const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
10595
11773
  const unpriced = boundedEvents.some(isUnpricedLeaf);
10596
- const synthesized = {
11774
+ const executionBase = {
10597
11775
  executionId: id,
10598
11776
  workflow: workflowName,
10599
11777
  status: "failed",
@@ -10605,6 +11783,16 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10605
11783
  duration: completedAt - startedAt,
10606
11784
  error: "process terminated (recovered from streaming buffer)"
10607
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
+ };
10608
11796
  if (this.stateStore.saveExecution) {
10609
11797
  try {
10610
11798
  await this.stateStore.saveExecution(synthesized);
@@ -10685,7 +11873,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10685
11873
  */
10686
11874
  async deleteExecution(id) {
10687
11875
  const wasActive = this.executions.has(id);
10688
- const hadPendingDecision = this.pendingDecisionResolvers.has(id);
11876
+ const pendingDecisionResolver = this.pendingDecisionResolvers.get(id);
11877
+ const hadPendingDecision = pendingDecisionResolver !== void 0;
10689
11878
  if (wasActive) {
10690
11879
  this.pendingDeletedExecutions.add(id);
10691
11880
  this.streamableExecutionIds.delete(id);
@@ -10694,8 +11883,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10694
11883
  controller.abort();
10695
11884
  }
10696
11885
  }
10697
- if (hadPendingDecision) {
10698
- 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
+ }
10699
11892
  }
10700
11893
  const existing = await this.getExecution(id);
10701
11894
  const workflow2 = existing?.workflow;
@@ -10773,60 +11966,68 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
10773
11966
  }
10774
11967
  /** Resolve a pending human decision. */
10775
11968
  async resolveDecision(executionId, decision) {
10776
- const resolver = this.pendingDecisionResolvers.get(executionId);
11969
+ decision = parseHumanDecision(decision);
11970
+ let resolver = this.pendingDecisionResolvers.get(executionId);
10777
11971
  if (resolver) {
10778
- resolver(decision);
10779
- 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
+ }
10780
11985
  }
10781
- await this.stateStore.resolveDecision(executionId, decision);
10782
11986
  if (!resolver) {
10783
- const state = await this.stateStore.getExecutionState(executionId);
10784
- if (state && state.status === "waiting") {
10785
- 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
+ );
10786
12013
  }
10787
12014
  }
10788
- }
10789
- /**
10790
- * Resume a specific execution that was waiting for a human decision.
10791
- * Re-runs the workflow from scratch; the workflow should use checkpoint-replay
10792
- * to skip already-completed steps and pick up from the awaitHuman point.
10793
- */
10794
- async resumeExecution(executionId) {
10795
- const state = await this.stateStore.getExecutionState(executionId);
10796
- if (!state) {
10797
- throw new Error(`No execution state found for "${executionId}"`);
10798
- }
10799
- const workflow2 = this.workflows.get(state.workflow);
10800
- if (!workflow2) {
10801
- throw new Error(
10802
- `Workflow "${state.workflow}" not registered. Cannot resume execution "${executionId}".`
10803
- );
10804
- }
10805
- return this.execute(state.workflow, state.input, {
10806
- metadata: { ...state.metadata, resumedFrom: executionId, resumeMode: true }
12015
+ let finishResolution;
12016
+ const resolutionFinished = new Promise((resolve2) => {
12017
+ finishResolution = resolve2;
10807
12018
  });
10808
- }
10809
- /**
10810
- * Resume all pending executions that were waiting for human decisions.
10811
- * Call this on startup to resume workflows that were interrupted by a restart.
10812
- * Returns the execution IDs that were resumed.
10813
- */
10814
- async resumePending() {
10815
- const pendingIds = await this.stateStore.listPendingExecutions();
10816
- const resumed = [];
10817
- for (const executionId of pendingIds) {
10818
- try {
10819
- await this.resumeExecution(executionId);
10820
- resumed.push(executionId);
10821
- } catch (err) {
10822
- this.emit("error", {
10823
- type: "resume_failed",
10824
- executionId,
10825
- error: err instanceof Error ? err.message : String(err)
10826
- });
10827
- }
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;
10828
12030
  }
10829
- return resumed;
10830
12031
  }
10831
12032
  /**
10832
12033
  * Summarize a list of chat messages into a concise summary string.
@@ -11728,28 +12929,32 @@ var RedisStore = class _RedisStore {
11728
12929
  async saveExecution(execution) {
11729
12930
  const ttl = this.ttlFor("executionHistory");
11730
12931
  const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
11731
- 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(), {
11732
12937
  score: execution.startedAt,
11733
12938
  value: execution.executionId
11734
12939
  }).exec();
11735
12940
  }
11736
12941
  async getExecution(executionId) {
11737
12942
  const raw = await this.client.get(this.execHistoryKey(executionId));
11738
- return raw ? JSON.parse(raw) : null;
12943
+ return raw ? normalizeStoredExecution(JSON.parse(raw)) : null;
11739
12944
  }
11740
12945
  async listExecutions(limit) {
11741
- return this.listHistoryByZset(
12946
+ const executions = await this.listHistoryByZset(
11742
12947
  this.execHistorySetKey(),
11743
12948
  this.execHistoryZsetKey(),
11744
12949
  (id) => this.execHistoryKey(id),
11745
12950
  (a, b) => b.startedAt - a.startedAt,
11746
12951
  limit
11747
12952
  );
12953
+ return executions.map(normalizeStoredExecution);
11748
12954
  }
11749
12955
  async deleteExecution(executionId) {
11750
- 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();
11751
- const deleted = typeof deletedCount === "number" ? deletedCount : 0;
11752
- 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);
11753
12958
  }
11754
12959
  // ── Eval History ────────────────────────────────────────────────────
11755
12960
  async saveEvalResult(entry) {
@@ -11841,14 +13046,15 @@ var RedisStore = class _RedisStore {
11841
13046
  async getStreamingEvents(executionId) {
11842
13047
  const raws = await this.client.lRange(this.streamingEventsKey(executionId), 0, -1);
11843
13048
  if (raws.length === 0) return [];
11844
- const events = [];
11845
- for (const raw of raws) {
13049
+ return raws.map((raw, index) => {
11846
13050
  try {
11847
- events.push(JSON.parse(raw));
11848
- } 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
+ });
11849
13056
  }
11850
- }
11851
- return events;
13057
+ });
11852
13058
  }
11853
13059
  /** Close the Redis connection. */
11854
13060
  async close() {
@@ -12060,6 +13266,8 @@ function cosineSimilarity2(a, b) {
12060
13266
  // Annotate the CommonJS export names for ESM import in node:
12061
13267
  0 && (module.exports = {
12062
13268
  AXL_EVENT_TYPES,
13269
+ AXL_EVENT_TYPES_V2,
13270
+ AXL_TOOL_LIFECYCLE_TYPES_V2,
12063
13271
  AZURE_PROFILE,
12064
13272
  AnthropicProvider,
12065
13273
  AxlError,
@@ -12107,7 +13315,9 @@ function cosineSimilarity2(a, b) {
12107
13315
  SqliteVectorStore,
12108
13316
  ThinkTagScanner,
12109
13317
  TimeoutError,
12110
- ToolDenied,
13318
+ ToolFailure,
13319
+ ToolModelOutputError,
13320
+ UnsupportedEventSchemaVersionError,
12111
13321
  VLLM_PROFILE,
12112
13322
  ValidationError,
12113
13323
  VerifyError,
@@ -12122,6 +13332,8 @@ function cosineSimilarity2(a, b) {
12122
13332
  extractJson,
12123
13333
  extractThinkTags,
12124
13334
  findConfig,
13335
+ getEventSchemaVersion,
13336
+ getExecutionEventSchemaVersion,
12125
13337
  hasPositiveTokens,
12126
13338
  importModule,
12127
13339
  isCostBearingLeaf,
@@ -12130,6 +13342,7 @@ function cosineSimilarity2(a, b) {
12130
13342
  isUnpricedLeaf,
12131
13343
  isUsableCost,
12132
13344
  needsTsxLoader,
13345
+ normalizeStoredExecution,
12133
13346
  openaiReasoningEmit,
12134
13347
  parsePartialJson,
12135
13348
  pickDefault,
@@ -12138,6 +13351,7 @@ function cosineSimilarity2(a, b) {
12138
13351
  reasoningEffortEmit,
12139
13352
  reasoningObjectEmit,
12140
13353
  redactEvent,
13354
+ redactHistoricalEvent,
12141
13355
  registerConditions,
12142
13356
  resolveApiKey,
12143
13357
  resolvePerModel,