@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.js CHANGED
@@ -1,28 +1,258 @@
1
1
  import {
2
2
  __require
3
- } from "./chunk-JBLQKU6X.js";
3
+ } from "./chunk-NVPA4RY6.js";
4
+
5
+ // src/tool.ts
6
+ import { ZodError } from "zod";
7
+
8
+ // src/errors.ts
9
+ var AxlError = class extends Error {
10
+ code;
11
+ constructor(code, message) {
12
+ super(message);
13
+ this.name = "AxlError";
14
+ this.code = code;
15
+ }
16
+ };
17
+ var EventStreamOverflowError = class extends Error {
18
+ maxQueued;
19
+ eventType;
20
+ constructor(maxQueued, eventType, cause) {
21
+ super(
22
+ `AxlEventBus queue exceeded maxQueued=${maxQueued} (event type: ${eventType}). Consumer is too slow or the producer is unbounded. Configure \`maxQueued\`/\`onOverflow\` on the runtime, or set maxQueued: Infinity to disable.`
23
+ );
24
+ this.name = "EventStreamOverflowError";
25
+ this.maxQueued = maxQueued;
26
+ this.eventType = eventType;
27
+ if (cause !== void 0) {
28
+ Object.defineProperty(this, "cause", {
29
+ configurable: true,
30
+ enumerable: false,
31
+ value: cause
32
+ });
33
+ }
34
+ }
35
+ };
36
+ function isEventStreamOverflowError(error) {
37
+ return error instanceof EventStreamOverflowError;
38
+ }
39
+ function rethrowEventStreamOverflow(error) {
40
+ if (isEventStreamOverflowError(error)) throw error;
41
+ }
42
+ function preserveErrorCause(error, cause) {
43
+ if (cause === void 0 || cause === error || "cause" in error) return error;
44
+ Object.defineProperty(error, "cause", {
45
+ configurable: true,
46
+ enumerable: false,
47
+ value: cause
48
+ });
49
+ return error;
50
+ }
51
+ var ToolFailure = class extends AxlError {
52
+ modelMessage;
53
+ constructor(options) {
54
+ super(options.code ?? "TOOL_FAILURE", options.message);
55
+ this.name = "ToolFailure";
56
+ this.modelMessage = options.modelMessage;
57
+ if (options.cause !== void 0) {
58
+ Object.defineProperty(this, "cause", {
59
+ configurable: true,
60
+ enumerable: false,
61
+ value: options.cause
62
+ });
63
+ }
64
+ }
65
+ };
66
+ var ToolModelOutputError = class extends AxlError {
67
+ toolName;
68
+ cause;
69
+ constructor(toolName, cause) {
70
+ super("TOOL_MODEL_OUTPUT_ERROR", `Failed to prepare model output for tool "${toolName}"`);
71
+ this.name = "ToolModelOutputError";
72
+ this.toolName = toolName;
73
+ this.cause = cause;
74
+ Object.defineProperty(this, "cause", { enumerable: false });
75
+ }
76
+ };
77
+ var VerifyError = class extends AxlError {
78
+ lastOutput;
79
+ zodError;
80
+ retries;
81
+ constructor(lastOutput, zodError, retries) {
82
+ super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
83
+ this.name = "VerifyError";
84
+ this.lastOutput = lastOutput;
85
+ this.zodError = zodError;
86
+ this.retries = retries;
87
+ }
88
+ };
89
+ var QuorumNotMet = class extends AxlError {
90
+ results;
91
+ constructor(required, actual, results) {
92
+ super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
93
+ this.name = "QuorumNotMet";
94
+ this.results = results;
95
+ }
96
+ };
97
+ var NoConsensus = class extends AxlError {
98
+ constructor(reason) {
99
+ super("NO_CONSENSUS", `No consensus: ${reason}`);
100
+ this.name = "NoConsensus";
101
+ }
102
+ };
103
+ var TimeoutError = class extends AxlError {
104
+ constructor(operation, timeoutMs) {
105
+ super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
106
+ this.name = "TimeoutError";
107
+ }
108
+ };
109
+ function formatBudgetCost(cost) {
110
+ if (Number.isNaN(cost)) return "$NaN";
111
+ if (cost === Infinity) return "$Infinity";
112
+ if (cost === -Infinity) return "-$Infinity";
113
+ if (cost === 0) return "$0.00";
114
+ const sign = cost < 0 ? "-" : "";
115
+ const abs = Math.abs(cost);
116
+ if (abs < 1e-6) return `${sign}< $0.000001`;
117
+ if (abs < 1e-4) {
118
+ const [mantissa, exponent] = abs.toExponential(2).split("e");
119
+ return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
120
+ }
121
+ if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
122
+ return `${sign}$${abs.toFixed(2)}`;
123
+ }
124
+ var BudgetExceededError = class extends AxlError {
125
+ limit;
126
+ spent;
127
+ policy;
128
+ constructor(limit, spent, policy) {
129
+ super(
130
+ "BUDGET_EXCEEDED",
131
+ `Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
132
+ limit
133
+ )} limit (policy: ${policy})`
134
+ );
135
+ this.name = "BudgetExceededError";
136
+ this.limit = limit;
137
+ this.spent = spent;
138
+ this.policy = policy;
139
+ }
140
+ };
141
+ var MaxTurnsError = class extends AxlError {
142
+ maxTurns;
143
+ constructor(operation, maxTurns) {
144
+ super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
145
+ this.name = "MaxTurnsError";
146
+ this.maxTurns = maxTurns;
147
+ }
148
+ };
149
+ var GuardrailError = class extends AxlError {
150
+ guardrailType;
151
+ reason;
152
+ constructor(guardrailType, reason) {
153
+ super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
154
+ this.name = "GuardrailError";
155
+ this.guardrailType = guardrailType;
156
+ this.reason = reason;
157
+ }
158
+ };
159
+ var ValidationError = class extends AxlError {
160
+ lastOutput;
161
+ reason;
162
+ retries;
163
+ constructor(lastOutput, reason, retries) {
164
+ super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
165
+ this.name = "ValidationError";
166
+ this.lastOutput = lastOutput;
167
+ this.reason = reason;
168
+ this.retries = retries;
169
+ }
170
+ };
4
171
 
5
172
  // src/tool.ts
6
173
  var DEFAULT_MAX_STRING_LENGTH = 1e4;
7
- function validateStringLengths(value, maxLen, path = "") {
174
+ var ToolStringLengthError = class extends Error {
175
+ constructor(path, actual, maximum) {
176
+ const renderedPath = path.reduce(
177
+ (current, part) => typeof part === "number" ? `${current}[${part}]` : current ? `${current}.${part}` : part,
178
+ ""
179
+ );
180
+ super(
181
+ `String argument${renderedPath ? ` at "${renderedPath}"` : ""} exceeds maximum length (${actual} > ${maximum})`
182
+ );
183
+ this.path = path;
184
+ this.actual = actual;
185
+ this.maximum = maximum;
186
+ this.name = "ToolStringLengthError";
187
+ }
188
+ };
189
+ function validateStringLengths(value, maxLen, path = []) {
8
190
  if (typeof value === "string") {
9
191
  if (value.length > maxLen) {
10
- throw new Error(
11
- `String argument${path ? ` at "${path}"` : ""} exceeds maximum length (${value.length} > ${maxLen})`
12
- );
192
+ throw new ToolStringLengthError(path, value.length, maxLen);
13
193
  }
14
194
  } else if (Array.isArray(value)) {
15
195
  for (let i = 0; i < value.length; i++) {
16
- validateStringLengths(value[i], maxLen, path ? `${path}[${i}]` : `[${i}]`);
196
+ validateStringLengths(value[i], maxLen, [...path, i]);
17
197
  }
18
198
  } else if (value !== null && typeof value === "object") {
19
199
  for (const [key, val] of Object.entries(value)) {
20
- validateStringLengths(val, maxLen, path ? `${path}.${key}` : key);
200
+ validateStringLengths(val, maxLen, [...path, key]);
21
201
  }
22
202
  }
23
203
  }
24
- function sleep(ms) {
25
- return new Promise((resolve2) => setTimeout(resolve2, ms));
204
+ function sleep(ms, signal) {
205
+ if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
206
+ signal.throwIfAborted();
207
+ return new Promise((resolve2, reject) => {
208
+ const timeout = setTimeout(() => {
209
+ signal.removeEventListener("abort", onAbort);
210
+ resolve2();
211
+ }, ms);
212
+ const onAbort = () => {
213
+ clearTimeout(timeout);
214
+ reject(signal.reason);
215
+ };
216
+ signal.addEventListener("abort", onAbort, { once: true });
217
+ });
218
+ }
219
+ function isAbortError(error) {
220
+ try {
221
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+ function toolArgumentIssues(error) {
227
+ if (error instanceof ZodError) {
228
+ return error.issues.map((issue) => ({
229
+ path: issue.path.map((part) => typeof part === "symbol" ? String(part) : part),
230
+ code: issue.code,
231
+ message: issue.message
232
+ }));
233
+ }
234
+ if (error instanceof ToolStringLengthError) {
235
+ return [
236
+ {
237
+ path: error.path,
238
+ code: "too_big",
239
+ message: error.message
240
+ }
241
+ ];
242
+ }
243
+ return [{ path: [], code: "invalid_arguments", message: "Tool arguments are invalid." }];
244
+ }
245
+ var TOOL_INTERNALS = /* @__PURE__ */ new WeakMap();
246
+ function getToolInternals(tool2) {
247
+ const internals = TOOL_INTERNALS.get(tool2);
248
+ if (!internals) throw new Error(`Tool "${tool2.name}" was not created by tool()`);
249
+ return internals;
250
+ }
251
+ function prepareToolInput(tool2, input) {
252
+ return getToolInternals(tool2).prepareInput(input);
253
+ }
254
+ function executePreparedTool(tool2, input, ctx, options) {
255
+ return getToolInternals(tool2).executePrepared(input, ctx, options);
26
256
  }
27
257
  function getBackoffMs(attempt, strategy) {
28
258
  switch (strategy) {
@@ -41,17 +271,27 @@ function tool(config) {
41
271
  on: config.retry?.on
42
272
  };
43
273
  const maxStringLen = config.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
44
- const execute = async (input, ctx) => {
274
+ const prepareInput = (input) => {
45
275
  const parsed = config.input.parse(input);
46
276
  if (maxStringLen > 0) {
47
277
  validateStringLengths(parsed, maxStringLen);
48
278
  }
279
+ return parsed;
280
+ };
281
+ const executePrepared = async (parsed, ctx, options) => {
49
282
  const maxAttempts = retryPolicy.attempts ?? 1;
50
283
  let lastError;
51
284
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
285
+ options?.signal?.throwIfAborted();
286
+ options?.onAttempt?.(attempt);
52
287
  try {
53
- return await config.handler(parsed, ctx);
288
+ const result = await config.handler(parsed, ctx);
289
+ if (options?.checkAfterHandlerAbort !== false) options?.signal?.throwIfAborted();
290
+ return result;
54
291
  } catch (err) {
292
+ if (options?.signal?.aborted) options.signal.throwIfAborted();
293
+ if (isAbortError(err)) throw err;
294
+ rethrowEventStreamOverflow(err);
55
295
  lastError = err instanceof Error ? err : new Error(String(err));
56
296
  if (attempt === maxAttempts) break;
57
297
  if (retryPolicy.on && !retryPolicy.on(lastError)) {
@@ -59,13 +299,14 @@ function tool(config) {
59
299
  }
60
300
  const backoffMs = getBackoffMs(attempt, retryPolicy.backoff ?? "exponential");
61
301
  if (backoffMs > 0) {
62
- await sleep(backoffMs);
302
+ await sleep(backoffMs, options?.signal);
63
303
  }
64
304
  }
65
305
  }
66
306
  throw lastError;
67
307
  };
68
- return {
308
+ const execute = async (input, ctx) => executePrepared(prepareInput(input), ctx);
309
+ const instance = {
69
310
  name: config.name,
70
311
  description: config.description,
71
312
  inputSchema: config.input,
@@ -73,6 +314,7 @@ function tool(config) {
73
314
  retry: retryPolicy,
74
315
  requireApproval: config.requireApproval ?? false,
75
316
  hooks: config.hooks,
317
+ ...config.toModelOutput !== void 0 ? { toModelOutput: config.toModelOutput } : {},
76
318
  async run(ctx, input) {
77
319
  const startTime = Date.now();
78
320
  try {
@@ -100,6 +342,11 @@ function tool(config) {
100
342
  },
101
343
  _execute: execute
102
344
  };
345
+ TOOL_INTERNALS.set(instance, {
346
+ prepareInput,
347
+ executePrepared
348
+ });
349
+ return instance;
103
350
  }
104
351
 
105
352
  // src/providers/types.ts
@@ -125,123 +372,6 @@ async function resolveApiKey(source) {
125
372
  return source ?? "";
126
373
  }
127
374
 
128
- // src/errors.ts
129
- var AxlError = class extends Error {
130
- code;
131
- constructor(code, message) {
132
- super(message);
133
- this.name = "AxlError";
134
- this.code = code;
135
- }
136
- };
137
- var VerifyError = class extends AxlError {
138
- lastOutput;
139
- zodError;
140
- retries;
141
- constructor(lastOutput, zodError, retries) {
142
- super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
143
- this.name = "VerifyError";
144
- this.lastOutput = lastOutput;
145
- this.zodError = zodError;
146
- this.retries = retries;
147
- }
148
- };
149
- var QuorumNotMet = class extends AxlError {
150
- results;
151
- constructor(required, actual, results) {
152
- super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
153
- this.name = "QuorumNotMet";
154
- this.results = results;
155
- }
156
- };
157
- var NoConsensus = class extends AxlError {
158
- constructor(reason) {
159
- super("NO_CONSENSUS", `No consensus: ${reason}`);
160
- this.name = "NoConsensus";
161
- }
162
- };
163
- var TimeoutError = class extends AxlError {
164
- constructor(operation, timeoutMs) {
165
- super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
166
- this.name = "TimeoutError";
167
- }
168
- };
169
- function formatBudgetCost(cost) {
170
- if (Number.isNaN(cost)) return "$NaN";
171
- if (cost === Infinity) return "$Infinity";
172
- if (cost === -Infinity) return "-$Infinity";
173
- if (cost === 0) return "$0.00";
174
- const sign = cost < 0 ? "-" : "";
175
- const abs = Math.abs(cost);
176
- if (abs < 1e-6) return `${sign}< $0.000001`;
177
- if (abs < 1e-4) {
178
- const [mantissa, exponent] = abs.toExponential(2).split("e");
179
- return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
180
- }
181
- if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
182
- return `${sign}$${abs.toFixed(2)}`;
183
- }
184
- var BudgetExceededError = class extends AxlError {
185
- limit;
186
- spent;
187
- policy;
188
- constructor(limit, spent, policy) {
189
- super(
190
- "BUDGET_EXCEEDED",
191
- `Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
192
- limit
193
- )} limit (policy: ${policy})`
194
- );
195
- this.name = "BudgetExceededError";
196
- this.limit = limit;
197
- this.spent = spent;
198
- this.policy = policy;
199
- }
200
- };
201
- var MaxTurnsError = class extends AxlError {
202
- maxTurns;
203
- constructor(operation, maxTurns) {
204
- super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
205
- this.name = "MaxTurnsError";
206
- this.maxTurns = maxTurns;
207
- }
208
- };
209
- var GuardrailError = class extends AxlError {
210
- guardrailType;
211
- reason;
212
- constructor(guardrailType, reason) {
213
- super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
214
- this.name = "GuardrailError";
215
- this.guardrailType = guardrailType;
216
- this.reason = reason;
217
- }
218
- };
219
- var ValidationError = class extends AxlError {
220
- lastOutput;
221
- reason;
222
- retries;
223
- constructor(lastOutput, reason, retries) {
224
- super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
225
- this.name = "ValidationError";
226
- this.lastOutput = lastOutput;
227
- this.reason = reason;
228
- this.retries = retries;
229
- }
230
- };
231
- var ToolDenied = class extends AxlError {
232
- toolName;
233
- agentName;
234
- constructor(toolName, agentName) {
235
- super(
236
- "TOOL_DENIED",
237
- `Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
238
- );
239
- this.name = "ToolDenied";
240
- this.toolName = toolName;
241
- this.agentName = agentName;
242
- }
243
- };
244
-
245
375
  // src/providers/errors.ts
246
376
  var ProviderError = class extends AxlError {
247
377
  /** Adapter/profile name, e.g. `'openai'`, `'anthropic'`, `'google'`. */
@@ -349,7 +479,7 @@ function sleep2(ms, signal) {
349
479
  );
350
480
  });
351
481
  }
352
- function isAbortError(err, signal) {
482
+ function isAbortError2(err, signal) {
353
483
  if (signal?.aborted) return true;
354
484
  return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
355
485
  }
@@ -368,7 +498,7 @@ async function fetchWithRetry(input, init, opts) {
368
498
  try {
369
499
  res = await fetch(input, init);
370
500
  } catch (err) {
371
- if (isAbortError(err, init?.signal ?? void 0)) throw err;
501
+ if (isAbortError2(err, init?.signal ?? void 0)) throw err;
372
502
  if (attempt >= maxRetries) {
373
503
  throw buildProviderError({
374
504
  provider,
@@ -2154,6 +2284,15 @@ function sanitizeSchemaForGemini(schema) {
2154
2284
  }
2155
2285
  return out;
2156
2286
  }
2287
+ function parseGeminiFunctionResponse(content) {
2288
+ let parsed;
2289
+ try {
2290
+ parsed = JSON.parse(content);
2291
+ } catch {
2292
+ return { result: content };
2293
+ }
2294
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
2295
+ }
2157
2296
  var GEMINI_PRICING = {
2158
2297
  "gemini-2.5-pro": [125e-8, 1e-5],
2159
2298
  "gemini-2.5-flash": [3e-7, 25e-7],
@@ -2489,15 +2628,9 @@ var GeminiProvider = class {
2489
2628
  }
2490
2629
  } else if (msg.role === "tool") {
2491
2630
  const functionName = toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
2492
- let responseData;
2493
- try {
2494
- responseData = JSON.parse(msg.content);
2495
- } catch {
2496
- responseData = { result: msg.content };
2497
- }
2498
2631
  const functionResponse = {
2499
2632
  name: functionName,
2500
- response: responseData
2633
+ response: parseGeminiFunctionResponse(msg.content)
2501
2634
  };
2502
2635
  if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
2503
2636
  functionResponse.id = msg.tool_call_id;
@@ -3047,7 +3180,7 @@ var defaultRegistry = new ProviderRegistry();
3047
3180
  // src/context.ts
3048
3181
  import { AsyncLocalStorage } from "async_hooks";
3049
3182
  import { randomUUID } from "crypto";
3050
- import { z, ZodError } from "zod";
3183
+ import { z, ZodError as ZodError2 } from "zod";
3051
3184
 
3052
3185
  // src/partial-json.ts
3053
3186
  var MAX_DEPTH = 256;
@@ -3666,9 +3799,9 @@ var AXL_EVENT_TYPES = [
3666
3799
  // Tool invocation lifecycle
3667
3800
  "tool_call_start",
3668
3801
  "tool_call_end",
3802
+ "tool_call_rejected",
3669
3803
  // Single-point tool events
3670
3804
  "tool_approval",
3671
- "tool_denied",
3672
3805
  // Delegation
3673
3806
  "delegate",
3674
3807
  // Handoff (spans two asks; not AskScoped).
@@ -3699,7 +3832,7 @@ var AXL_EVENT_TYPES = [
3699
3832
  "memory_remember",
3700
3833
  "memory_recall",
3701
3834
  "memory_forget",
3702
- // Durable execution checkpoints (`ctx.checkpoint`)
3835
+ // Scoped execution checkpoints (`ctx.checkpoint`)
3703
3836
  "checkpoint_save",
3704
3837
  "checkpoint_replay",
3705
3838
  // Human-in-the-loop (`ctx.awaitHuman`)
@@ -3714,6 +3847,12 @@ var AXL_EVENT_TYPES = [
3714
3847
  "done",
3715
3848
  "error"
3716
3849
  ];
3850
+ var AXL_EVENT_TYPES_V2 = AXL_EVENT_TYPES;
3851
+ var AXL_TOOL_LIFECYCLE_TYPES_V2 = [
3852
+ "tool_call_start",
3853
+ "tool_call_end",
3854
+ "tool_call_rejected"
3855
+ ];
3717
3856
 
3718
3857
  // src/event-stream.ts
3719
3858
  var STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
@@ -3729,8 +3868,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3729
3868
  "agent_call_end",
3730
3869
  "tool_call_start",
3731
3870
  "tool_call_end",
3871
+ "tool_call_rejected",
3732
3872
  "tool_approval",
3733
- "tool_denied",
3734
3873
  "handoff_start",
3735
3874
  "handoff_return",
3736
3875
  "delegate",
@@ -3744,18 +3883,6 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
3744
3883
  "await_human",
3745
3884
  "await_human_resolved"
3746
3885
  ]);
3747
- var EventStreamOverflowError = class extends Error {
3748
- maxQueued;
3749
- eventType;
3750
- constructor(maxQueued, eventType) {
3751
- super(
3752
- `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.`
3753
- );
3754
- this.name = "EventStreamOverflowError";
3755
- this.maxQueued = maxQueued;
3756
- this.eventType = eventType;
3757
- }
3758
- };
3759
3886
  var DEFAULT_MAX_QUEUED = 1e4;
3760
3887
  var DEFAULT_OVERFLOW = "drop-oldest-non-terminal";
3761
3888
  var AxlEventBus = class {
@@ -3766,6 +3893,7 @@ var AxlEventBus = class {
3766
3893
  maxQueued;
3767
3894
  onOverflow;
3768
3895
  overflowWarned = false;
3896
+ droppedEvents = 0;
3769
3897
  /** Callbacks fired exactly once when `_finish()` is called. Used by
3770
3898
  * curated views (e.g., `partialObjects`) that are listener-based and
3771
3899
  * need a termination signal independent of any synthetic `done` event. */
@@ -3811,6 +3939,10 @@ var AxlEventBus = class {
3811
3939
  * can be 4 KB+ each, hence the more aggressive cleanup here).
3812
3940
  */
3813
3941
  stringStreamByAsk = /* @__PURE__ */ new Map();
3942
+ /** Internal fan-out for `stringStream()` subscribers. Kept separate from
3943
+ * the public EventEmitter so strict curated-view overflow is a runtime
3944
+ * signal, not mistaken for (and swallowed as) a buggy user listener. */
3945
+ stringStreamSubscribers = /* @__PURE__ */ new Set();
3814
3946
  /**
3815
3947
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
3816
3948
  * is `await using`-disposed. Set via the second constructor argument.
@@ -3844,6 +3976,12 @@ var AxlEventBus = class {
3844
3976
  this.bus.on("error", () => {
3845
3977
  });
3846
3978
  }
3979
+ /** Machine-readable completeness across this bus's bounded iterable views.
3980
+ * EventEmitter listener delivery is independent and is not described by
3981
+ * this status. */
3982
+ get observationStatus() {
3983
+ return this.droppedEvents === 0 ? { complete: true } : { complete: false, reason: "queue_overflow", droppedEvents: this.droppedEvents };
3984
+ }
3847
3985
  unknownEventWarned = false;
3848
3986
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3849
3987
  on(event, handler) {
@@ -4156,7 +4294,7 @@ var AxlEventBus = class {
4156
4294
  }
4157
4295
  return {
4158
4296
  [Symbol.asyncIterator]() {
4159
- const pending = [];
4297
+ const pending = /* @__PURE__ */ new Map();
4160
4298
  let resolveWaiter = null;
4161
4299
  let finished = self.finished;
4162
4300
  let unsubscribed = false;
@@ -4170,10 +4308,39 @@ var AxlEventBus = class {
4170
4308
  if (filterPath !== void 0 && path !== filterPath) return false;
4171
4309
  return true;
4172
4310
  };
4311
+ const pendingKey = (askId, path) => JSON.stringify([askId, path]);
4312
+ const enqueue = (event) => {
4313
+ const key = pendingKey(event.askId, event.path);
4314
+ const existing = pending.get(key);
4315
+ if (existing) {
4316
+ pending.set(key, {
4317
+ ...event,
4318
+ delta: existing.delta + event.delta
4319
+ });
4320
+ wakeWaiter();
4321
+ return;
4322
+ }
4323
+ if (pending.size >= self.maxQueued) {
4324
+ if (self.onOverflow === "throw") {
4325
+ self.droppedEvents++;
4326
+ throw new EventStreamOverflowError(self.maxQueued, "string_delta");
4327
+ }
4328
+ const oldest = pending.keys().next().value;
4329
+ if (oldest !== void 0) {
4330
+ pending.delete(oldest);
4331
+ self.droppedEvents++;
4332
+ }
4333
+ self.warnOverflow(
4334
+ "dropping oldest pending string field (stringStream subscriber is slower than producer)"
4335
+ );
4336
+ }
4337
+ pending.set(key, event);
4338
+ wakeWaiter();
4339
+ };
4173
4340
  for (const [askId, paths] of self.stringStreamByAsk) {
4174
4341
  for (const [path, acc] of paths) {
4175
4342
  if (!matchesFilter(askId, path)) continue;
4176
- pending.push({
4343
+ enqueue({
4177
4344
  askId,
4178
4345
  agent: acc.agent,
4179
4346
  path,
@@ -4183,51 +4350,37 @@ var AxlEventBus = class {
4183
4350
  });
4184
4351
  }
4185
4352
  }
4186
- const handler = (event) => {
4187
- if (event.type !== "string_delta") return;
4188
- const askId = event.askId ?? "";
4189
- if (!matchesFilter(askId, event.data.path)) return;
4190
- const paths = self.stringStreamByAsk.get(askId);
4191
- const acc = paths?.get(event.data.path);
4192
- const accumulated = acc?.text ?? event.data.delta;
4193
- pending.push({
4194
- askId,
4195
- agent: event.agent,
4196
- path: event.data.path,
4197
- delta: event.data.delta,
4198
- accumulated,
4199
- attempt: event.attempt
4200
- });
4201
- wakeWaiter();
4202
- };
4203
- self.bus.on("string_delta", handler);
4204
- const pipelineHandler = (event) => {
4205
- if (event.type !== "pipeline") return;
4206
- if (event.status !== "failed") return;
4207
- const askId = event.askId ?? "";
4208
- for (let i = pending.length - 1; i >= 0; i--) {
4209
- if (pending[i].askId === askId) pending.splice(i, 1);
4353
+ const subscriber = {
4354
+ push(event) {
4355
+ if (matchesFilter(event.askId, event.path)) enqueue(event);
4356
+ },
4357
+ reset(askId) {
4358
+ for (const [key, event] of pending) {
4359
+ if (event.askId === askId) pending.delete(key);
4360
+ }
4210
4361
  }
4211
4362
  };
4212
- self.bus.on("pipeline", pipelineHandler);
4363
+ self.stringStreamSubscribers.add(subscriber);
4213
4364
  const unsubscribe = () => {
4214
4365
  if (unsubscribed) return;
4215
4366
  unsubscribed = true;
4216
- self.bus.off("string_delta", handler);
4217
- self.bus.off("pipeline", pipelineHandler);
4367
+ self.stringStreamSubscribers.delete(subscriber);
4218
4368
  unsubscribeFinish();
4219
4369
  };
4220
4370
  const unsubscribeFinish = self._onFinish(() => {
4221
4371
  finished = true;
4222
- self.bus.off("string_delta", handler);
4223
- self.bus.off("pipeline", pipelineHandler);
4372
+ self.stringStreamSubscribers.delete(subscriber);
4224
4373
  wakeWaiter();
4225
4374
  });
4226
4375
  return {
4227
4376
  async next() {
4228
4377
  while (true) {
4229
- if (pending.length > 0) {
4230
- return { value: pending.shift(), done: false };
4378
+ if (pending.size > 0) {
4379
+ const first = pending.entries().next().value;
4380
+ if (first) {
4381
+ pending.delete(first[0]);
4382
+ return { value: first[1], done: false };
4383
+ }
4231
4384
  }
4232
4385
  if (finished) {
4233
4386
  unsubscribe();
@@ -4285,6 +4438,7 @@ var AxlEventBus = class {
4285
4438
  */
4286
4439
  _push(event) {
4287
4440
  if (this.finished) return;
4441
+ let overflowError;
4288
4442
  if (event.type === "partial_object" && event.data?.object !== void 0) {
4289
4443
  const askId = event.askId ?? "";
4290
4444
  this.latestPartialByAsk.set(askId, {
@@ -4306,10 +4460,30 @@ var AxlEventBus = class {
4306
4460
  text: (existing?.text ?? "") + event.data.delta,
4307
4461
  attempt: event.attempt
4308
4462
  });
4463
+ const accumulated = paths.get(event.data.path).text;
4464
+ for (const subscriber of this.stringStreamSubscribers) {
4465
+ try {
4466
+ subscriber.push({
4467
+ askId,
4468
+ agent: event.agent,
4469
+ path: event.data.path,
4470
+ delta: event.data.delta,
4471
+ accumulated,
4472
+ attempt: event.attempt
4473
+ });
4474
+ } catch (error) {
4475
+ if (isEventStreamOverflowError(error)) {
4476
+ overflowError ??= error;
4477
+ } else {
4478
+ throw error;
4479
+ }
4480
+ }
4481
+ }
4309
4482
  } else if (event.type === "pipeline" && event.status === "failed") {
4310
4483
  const askId = event.askId ?? "";
4311
4484
  this.latestPartialByAsk.delete(askId);
4312
4485
  this.stringStreamByAsk.delete(askId);
4486
+ for (const subscriber of this.stringStreamSubscribers) subscriber.reset(askId);
4313
4487
  } else if (event.type === "ask_end") {
4314
4488
  this.stringStreamByAsk.delete(event.askId ?? "");
4315
4489
  }
@@ -4324,39 +4498,57 @@ var AxlEventBus = class {
4324
4498
  const waiter = this.waiters.shift();
4325
4499
  if (waiter) {
4326
4500
  waiter({ value: event, done: false });
4501
+ if (overflowError) throw overflowError;
4327
4502
  return;
4328
4503
  }
4329
4504
  if (this.eventQueue.length >= this.maxQueued && !TERMINAL_TYPES.has(event.type)) {
4330
- this.handleOverflow(event);
4505
+ try {
4506
+ this.handleOverflow(event);
4507
+ } catch (error) {
4508
+ if (isEventStreamOverflowError(error)) {
4509
+ overflowError ??= error;
4510
+ } else {
4511
+ throw error;
4512
+ }
4513
+ }
4514
+ if (overflowError) throw overflowError;
4331
4515
  return;
4332
4516
  }
4333
4517
  this.eventQueue.push(event);
4518
+ if (overflowError) throw overflowError;
4334
4519
  }
4335
4520
  /** Apply the overflow policy when the queue is at `maxQueued` and a
4336
4521
  * non-terminal event arrives. Terminal events bypass this path entirely
4337
4522
  * (checked by the caller). */
4338
4523
  handleOverflow(event) {
4339
4524
  if (this.onOverflow === "throw") {
4525
+ this.droppedEvents++;
4340
4526
  throw new EventStreamOverflowError(this.maxQueued, event.type);
4341
4527
  }
4342
4528
  let droppedIdx = -1;
4343
4529
  for (let i = 0; i < this.eventQueue.length; i++) {
4344
- if (!TERMINAL_TYPES.has(this.eventQueue[i].type)) {
4530
+ const candidate = this.eventQueue[i];
4531
+ if (!TERMINAL_TYPES.has(candidate.type)) {
4345
4532
  droppedIdx = i;
4346
4533
  break;
4347
4534
  }
4348
4535
  }
4349
- if (!this.overflowWarned) {
4350
- this.overflowWarned = true;
4351
- console.warn(
4352
- `[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.`
4353
- );
4354
- }
4536
+ this.warnOverflow(
4537
+ droppedIdx >= 0 ? "dropping oldest non-terminal events (consumer is slower than producer)" : "queue contains only protected terminal events; preserving them past the cap"
4538
+ );
4355
4539
  if (droppedIdx >= 0) {
4356
4540
  this.eventQueue.splice(droppedIdx, 1);
4541
+ this.droppedEvents++;
4357
4542
  }
4358
4543
  this.eventQueue.push(event);
4359
4544
  }
4545
+ warnOverflow(detail) {
4546
+ if (this.overflowWarned) return;
4547
+ this.overflowWarned = true;
4548
+ console.warn(
4549
+ `[axl] AxlEventBus queue exceeded maxQueued=${this.maxQueued}; ${detail}. This warning fires once per bus instance.`
4550
+ );
4551
+ }
4360
4552
  /**
4361
4553
  * Mark the bus as finished. Wakes any pending iterators with `done: true`
4362
4554
  * and drops further `_push` calls. Idempotent.
@@ -4423,6 +4615,43 @@ var AxlEventBus = class {
4423
4615
  }
4424
4616
  };
4425
4617
 
4618
+ // src/event-schema.ts
4619
+ var UnsupportedEventSchemaVersionError = class extends Error {
4620
+ constructor(version) {
4621
+ super(`Unsupported Axl event schema version: ${String(version)}`);
4622
+ this.version = version;
4623
+ this.name = "UnsupportedEventSchemaVersionError";
4624
+ }
4625
+ };
4626
+ function getEventSchemaVersion(event) {
4627
+ const version = event.schemaVersion === void 0 ? 1 : event.schemaVersion;
4628
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4629
+ return version;
4630
+ }
4631
+ function getExecutionEventSchemaVersion(execution) {
4632
+ const version = execution.eventSchemaVersion === void 0 ? 1 : execution.eventSchemaVersion;
4633
+ if (version !== 1 && version !== 2) throw new UnsupportedEventSchemaVersionError(version);
4634
+ return version;
4635
+ }
4636
+ function normalizeStoredExecution(execution) {
4637
+ const version = getExecutionEventSchemaVersion(execution);
4638
+ if (!Array.isArray(execution.events)) {
4639
+ throw new TypeError(`Execution ${execution.executionId} has a non-array events field`);
4640
+ }
4641
+ for (const event of execution.events) {
4642
+ const eventVersion = getEventSchemaVersion(event);
4643
+ if (eventVersion !== version) {
4644
+ throw new Error(
4645
+ `Execution ${execution.executionId} declares event schema v${version} but contains a v${eventVersion} event`
4646
+ );
4647
+ }
4648
+ }
4649
+ if (version === 2) {
4650
+ return { ...execution, eventSchemaVersion: 2 };
4651
+ }
4652
+ return { ...execution, eventSchemaVersion: 1 };
4653
+ }
4654
+
4426
4655
  // src/redaction.ts
4427
4656
  var REDACTED = "[redacted]";
4428
4657
  function walkObjectOneLevel(obj, preserveKeys) {
@@ -4473,7 +4702,7 @@ function redactMemory(event) {
4473
4702
  };
4474
4703
  }
4475
4704
  var passthrough = (e) => e;
4476
- var REDACTION_RULES = {
4705
+ var LEGACY_REDACTION_RULES = {
4477
4706
  workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
4478
4707
  workflow_end: (e) => {
4479
4708
  const d = e.data;
@@ -4573,10 +4802,125 @@ var REDACTION_RULES = {
4573
4802
  done: (e) => ({ ...e, data: { result: REDACTED } }),
4574
4803
  error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
4575
4804
  };
4805
+ function redactLegacyEvent(event) {
4806
+ const rule = LEGACY_REDACTION_RULES[event.type];
4807
+ return rule(event);
4808
+ }
4809
+ function redactV2Outcome(outcome) {
4810
+ switch (outcome.status) {
4811
+ case "succeeded":
4812
+ return { ...outcome, result: REDACTED };
4813
+ case "denied":
4814
+ return outcome.reason === void 0 ? outcome : { ...outcome, reason: REDACTED };
4815
+ case "cancelled":
4816
+ return {
4817
+ ...outcome,
4818
+ cancellation: {
4819
+ ...outcome.cancellation,
4820
+ ..."result" in outcome.cancellation ? { result: REDACTED } : {},
4821
+ ...outcome.cancellation.reason !== void 0 ? { reason: REDACTED } : {}
4822
+ }
4823
+ };
4824
+ case "failed": {
4825
+ const failure = outcome.failure;
4826
+ return {
4827
+ ...outcome,
4828
+ failure: {
4829
+ ...failure,
4830
+ ..."result" in failure ? { result: REDACTED } : {},
4831
+ error: {
4832
+ ...failure.error,
4833
+ message: REDACTED,
4834
+ ...failure.error.cause !== void 0 ? { cause: REDACTED } : {}
4835
+ }
4836
+ }
4837
+ };
4838
+ }
4839
+ }
4840
+ }
4841
+ function redactV2Rejection(data) {
4842
+ switch (data.reason) {
4843
+ case "unavailable":
4844
+ return data;
4845
+ case "invalid_json":
4846
+ return { ...data, message: REDACTED };
4847
+ case "invalid_arguments":
4848
+ return {
4849
+ ...data,
4850
+ args: REDACTED,
4851
+ issues: data.issues.map((issue) => ({
4852
+ ...issue,
4853
+ ...issue.message !== void 0 ? { message: REDACTED } : {}
4854
+ }))
4855
+ };
4856
+ }
4857
+ }
4858
+ var V2_TOOL_REDACTION_RULES = {
4859
+ tool_call_start: (event) => ({
4860
+ ...event,
4861
+ data: { ...event.data, args: REDACTED }
4862
+ }),
4863
+ tool_call_end: (event) => ({
4864
+ ...event,
4865
+ data: {
4866
+ ...event.data,
4867
+ args: REDACTED,
4868
+ outcome: redactV2Outcome(event.data.outcome)
4869
+ }
4870
+ }),
4871
+ tool_call_rejected: (event) => ({
4872
+ ...event,
4873
+ data: redactV2Rejection(event.data)
4874
+ })
4875
+ };
4876
+ function reuseLegacyRule(type) {
4877
+ return LEGACY_REDACTION_RULES[type];
4878
+ }
4879
+ var COMMON_V2_REDACTION_RULES = {
4880
+ workflow_start: reuseLegacyRule("workflow_start"),
4881
+ workflow_end: reuseLegacyRule("workflow_end"),
4882
+ ask_start: reuseLegacyRule("ask_start"),
4883
+ ask_end: reuseLegacyRule("ask_end"),
4884
+ agent_call_start: reuseLegacyRule("agent_call_start"),
4885
+ agent_call_end: reuseLegacyRule("agent_call_end"),
4886
+ token: reuseLegacyRule("token"),
4887
+ tool_approval: reuseLegacyRule("tool_approval"),
4888
+ delegate: reuseLegacyRule("delegate"),
4889
+ handoff_start: reuseLegacyRule("handoff_start"),
4890
+ handoff_return: reuseLegacyRule("handoff_return"),
4891
+ pipeline: reuseLegacyRule("pipeline"),
4892
+ partial_object: reuseLegacyRule("partial_object"),
4893
+ string_delta: reuseLegacyRule("string_delta"),
4894
+ verify: reuseLegacyRule("verify"),
4895
+ schema_diagnostic: reuseLegacyRule("schema_diagnostic"),
4896
+ log: reuseLegacyRule("log"),
4897
+ memory_remember: reuseLegacyRule("memory_remember"),
4898
+ memory_recall: reuseLegacyRule("memory_recall"),
4899
+ memory_forget: reuseLegacyRule("memory_forget"),
4900
+ checkpoint_save: reuseLegacyRule("checkpoint_save"),
4901
+ checkpoint_replay: reuseLegacyRule("checkpoint_replay"),
4902
+ await_human: reuseLegacyRule("await_human"),
4903
+ await_human_resolved: reuseLegacyRule("await_human_resolved"),
4904
+ guardrail: reuseLegacyRule("guardrail"),
4905
+ schema_check: reuseLegacyRule("schema_check"),
4906
+ validate: reuseLegacyRule("validate"),
4907
+ done: reuseLegacyRule("done"),
4908
+ error: reuseLegacyRule("error")
4909
+ };
4910
+ var REDACTION_RULES = {
4911
+ ...COMMON_V2_REDACTION_RULES,
4912
+ ...V2_TOOL_REDACTION_RULES
4913
+ };
4576
4914
  function redactEvent(event) {
4577
4915
  const rule = REDACTION_RULES[event.type];
4578
4916
  return rule(event);
4579
4917
  }
4918
+ function redactHistoricalEvent(event) {
4919
+ if (getEventSchemaVersion(event) === 1) {
4920
+ return redactLegacyEvent(event);
4921
+ }
4922
+ return redactEvent(event);
4923
+ }
4580
4924
 
4581
4925
  // src/schema-diagnostics.ts
4582
4926
  var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
@@ -4631,104 +4975,765 @@ function detectDroppedRefinements(schema) {
4631
4975
  if ("right" in def) walk(def.right, path);
4632
4976
  if (typeof def.getter === "function") {
4633
4977
  try {
4634
- walk(def.getter(), path);
4978
+ walk(def.getter(), path);
4979
+ } catch {
4980
+ }
4981
+ }
4982
+ };
4983
+ walk(schema, "");
4984
+ const result = { count, paths: [...paths] };
4985
+ refinementCache.set(schema, result);
4986
+ return result;
4987
+ }
4988
+ var DOCS_URL = "docs/observability.md#schema-diagnostics";
4989
+ var MAX_WARNED_KEYS = 2048;
4990
+ var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
4991
+ function warnSchemaDiagnosticOnce(key, message, silent) {
4992
+ if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
4993
+ if (warnedDiagnosticKeys.has(key)) return;
4994
+ if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
4995
+ warnedDiagnosticKeys.add(key);
4996
+ console.warn(`[axl] ${message} See ${DOCS_URL}.`);
4997
+ }
4998
+
4999
+ // src/config.ts
5000
+ function defineConfig(config) {
5001
+ return config;
5002
+ }
5003
+ function parseDuration(duration) {
5004
+ const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/);
5005
+ if (!match) throw new Error(`Invalid duration format: "${duration}"`);
5006
+ const value = parseFloat(match[1]);
5007
+ const unit = match[2];
5008
+ switch (unit) {
5009
+ case "ms":
5010
+ return value;
5011
+ case "s":
5012
+ return value * 1e3;
5013
+ case "m":
5014
+ return value * 6e4;
5015
+ case "h":
5016
+ return value * 36e5;
5017
+ default:
5018
+ throw new Error(`Unknown duration unit: "${unit}"`);
5019
+ }
5020
+ }
5021
+ function parseCost(cost) {
5022
+ const match = cost.match(/^\$?([\d.]+)$/);
5023
+ if (!match) throw new Error(`Invalid cost format: "${cost}"`);
5024
+ return parseFloat(match[1]);
5025
+ }
5026
+ function resolveConfig(config) {
5027
+ const resolved = { ...config };
5028
+ if (process.env.AXL_DEFAULT_PROVIDER) {
5029
+ resolved.defaultProvider = process.env.AXL_DEFAULT_PROVIDER;
5030
+ }
5031
+ if (process.env.AXL_STATE_STORE) {
5032
+ const envStore = process.env.AXL_STATE_STORE;
5033
+ if (envStore === "memory" || envStore === "sqlite") {
5034
+ resolved.state = {
5035
+ ...resolved.state,
5036
+ store: envStore
5037
+ };
5038
+ }
5039
+ }
5040
+ if (process.env.AXL_TRACE_ENABLED !== void 0) {
5041
+ resolved.trace = { ...resolved.trace, enabled: process.env.AXL_TRACE_ENABLED === "true" };
5042
+ }
5043
+ if (process.env.AXL_TRACE_LEVEL) {
5044
+ resolved.trace = {
5045
+ ...resolved.trace,
5046
+ level: process.env.AXL_TRACE_LEVEL
5047
+ };
5048
+ }
5049
+ if (process.env.OPENAI_API_KEY) {
5050
+ if (!resolved.providers) resolved.providers = {};
5051
+ resolved.providers.openai = {
5052
+ ...resolved.providers.openai ?? {},
5053
+ apiKey: process.env.OPENAI_API_KEY
5054
+ };
5055
+ }
5056
+ if (process.env.ANTHROPIC_API_KEY) {
5057
+ if (!resolved.providers) resolved.providers = {};
5058
+ resolved.providers.anthropic = {
5059
+ ...resolved.providers.anthropic ?? {},
5060
+ apiKey: process.env.ANTHROPIC_API_KEY
5061
+ };
5062
+ }
5063
+ const googleKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
5064
+ if (googleKey) {
5065
+ if (!resolved.providers) resolved.providers = {};
5066
+ resolved.providers.google = {
5067
+ ...resolved.providers.google ?? {},
5068
+ apiKey: googleKey
5069
+ };
5070
+ }
5071
+ return resolved;
5072
+ }
5073
+
5074
+ // src/tool-model-output.ts
5075
+ import { types as nodeTypes } from "util";
5076
+ function invalid(path, reason) {
5077
+ return new Error(`Invalid tool model output at ${path}: ${reason}`);
5078
+ }
5079
+ function propertyPath(parent, key) {
5080
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return `${parent}.${key}`;
5081
+ let escaped = "";
5082
+ for (const character of key) {
5083
+ const code = character.charCodeAt(0);
5084
+ if (character === "\\" || character === '"') escaped += `\\${character}`;
5085
+ else if (code <= 31 || code === 8232 || code === 8233) {
5086
+ escaped += `\\u${code.toString(16).padStart(4, "0")}`;
5087
+ } else escaped += character;
5088
+ }
5089
+ return `${parent}["${escaped}"]`;
5090
+ }
5091
+ function normalize(value, path, stack) {
5092
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
5093
+ if (typeof value === "number") {
5094
+ if (!Number.isFinite(value)) throw invalid(path, "number must be finite");
5095
+ return value;
5096
+ }
5097
+ if (value === void 0) throw invalid(path, "undefined is only allowed on object properties");
5098
+ if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
5099
+ throw invalid(path, `unsupported ${typeof value}`);
5100
+ }
5101
+ if (typeof value !== "object") throw invalid(path, `unsupported ${typeof value}`);
5102
+ let prototype;
5103
+ let descriptors;
5104
+ try {
5105
+ prototype = Object.getPrototypeOf(value);
5106
+ descriptors = Object.getOwnPropertyDescriptors(value);
5107
+ Object.setPrototypeOf(descriptors, null);
5108
+ } catch (cause) {
5109
+ throw invalid(
5110
+ path,
5111
+ `object inspection failed (${cause instanceof Error ? cause.name : "error"})`
5112
+ );
5113
+ }
5114
+ if (stack.has(value)) throw invalid(path, "cyclic reference");
5115
+ for (const key of Reflect.ownKeys(descriptors)) {
5116
+ const descriptor = descriptors[key];
5117
+ if (descriptor && !("value" in descriptor)) {
5118
+ throw invalid(
5119
+ typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
5120
+ "accessor properties are not supported"
5121
+ );
5122
+ }
5123
+ if (typeof key === "symbol") {
5124
+ throw invalid(`${path}[symbol]`, "symbol keys are not supported");
5125
+ }
5126
+ }
5127
+ if (typeof descriptors.toJSON?.value === "function") {
5128
+ throw invalid(propertyPath(path, "toJSON"), "custom toJSON is not supported");
5129
+ }
5130
+ const array = Array.isArray(value);
5131
+ if (array && prototype !== Array.prototype && prototype !== null) {
5132
+ throw invalid(path, "array subclasses are not supported");
5133
+ }
5134
+ const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
5135
+ if (typeof inheritedToJSON?.value === "function") {
5136
+ throw invalid(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
5137
+ }
5138
+ const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
5139
+ if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
5140
+ throw invalid(path, "thenable output is not supported");
5141
+ }
5142
+ stack.add(value);
5143
+ try {
5144
+ if (array) {
5145
+ const normalized2 = [];
5146
+ const length = descriptors.length?.value;
5147
+ if (!Number.isSafeInteger(length) || length < 0) {
5148
+ throw invalid(propertyPath(path, "length"), "array length is invalid");
5149
+ }
5150
+ for (let index = 0; index < length; index++) {
5151
+ const descriptor = descriptors[String(index)];
5152
+ if (!descriptor || !("value" in descriptor)) {
5153
+ throw invalid(`${path}[${index}]`, "arrays must be dense data properties");
5154
+ }
5155
+ if (descriptor.value === void 0) {
5156
+ throw invalid(`${path}[${index}]`, "array values cannot be undefined");
5157
+ }
5158
+ normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
5159
+ }
5160
+ for (const key of Object.keys(descriptors)) {
5161
+ const descriptor = descriptors[key];
5162
+ if (!descriptor?.enumerable) continue;
5163
+ const index = Number(key);
5164
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
5165
+ throw invalid(propertyPath(path, key), "non-index array properties are not supported");
5166
+ }
5167
+ }
5168
+ Object.setPrototypeOf(normalized2, null);
5169
+ return normalized2;
5170
+ }
5171
+ if (prototype !== Object.prototype && prototype !== null) {
5172
+ throw invalid(path, "only plain objects and null-prototype records are supported");
5173
+ }
5174
+ const normalized = /* @__PURE__ */ Object.create(null);
5175
+ for (const key of Object.keys(descriptors)) {
5176
+ const descriptor = descriptors[key];
5177
+ if (!descriptor?.enumerable) continue;
5178
+ if (!("value" in descriptor)) {
5179
+ throw invalid(propertyPath(path, key), "accessor properties are not supported");
5180
+ }
5181
+ if (descriptor.value === void 0) continue;
5182
+ normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
5183
+ }
5184
+ return normalized;
5185
+ } finally {
5186
+ stack.delete(value);
5187
+ }
5188
+ }
5189
+ function serializeToolModelOutput(toolName, output) {
5190
+ try {
5191
+ if (nodeTypes.isPromise(output)) {
5192
+ void Promise.prototype.then.call(output, void 0, () => void 0);
5193
+ }
5194
+ if (typeof output === "string") return output;
5195
+ return JSON.stringify(normalize(output, "$", /* @__PURE__ */ new WeakSet()));
5196
+ } catch (cause) {
5197
+ throw new ToolModelOutputError(toolName, cause);
5198
+ }
5199
+ }
5200
+
5201
+ // src/tool-invocation.ts
5202
+ var INVALID_JSON_MODEL_MESSAGE = "Error: Invalid JSON in tool arguments. Please provide valid JSON.";
5203
+ var INVALID_ARGUMENTS_MODEL_MESSAGE = "Error: Tool arguments are invalid. Correct the arguments and try again.";
5204
+ var SENSITIVE_SUCCESS_MESSAGE = "[REDACTED - sensitive tool output]";
5205
+ var SENSITIVE_FAILURE_MESSAGE = "[REDACTED - sensitive tool failure]";
5206
+ var MAX_MCP_MODEL_ERROR_LENGTH = 2e3;
5207
+ function cloneToolArguments(value) {
5208
+ try {
5209
+ return structuredClone(value);
5210
+ } catch {
5211
+ throw new Error("Prepared tool arguments must be structured-cloneable");
5212
+ }
5213
+ }
5214
+ function parseToolInvocation(options) {
5215
+ const { toolCall, configuredTool, override, mcpCall, mcpTraceName, availableTools } = options;
5216
+ const requestedTool = toolCall.function.name;
5217
+ if (!override && !configuredTool && !mcpCall) {
5218
+ return {
5219
+ kind: "rejected",
5220
+ requestedTool,
5221
+ toolName: requestedTool,
5222
+ callId: toolCall.id,
5223
+ data: { reason: "unavailable", requestedTool, availableTools },
5224
+ modelMessage: `Tool "${requestedTool}" is not available. Available tools: ${availableTools.length > 0 ? availableTools.join(", ") : "none"}`
5225
+ };
5226
+ }
5227
+ let parsedArguments;
5228
+ try {
5229
+ parsedArguments = JSON.parse(toolCall.function.arguments);
5230
+ } catch {
5231
+ return {
5232
+ kind: "rejected",
5233
+ requestedTool,
5234
+ toolName: mcpTraceName ?? requestedTool,
5235
+ callId: toolCall.id,
5236
+ data: {
5237
+ reason: "invalid_json",
5238
+ requestedTool,
5239
+ message: "Tool arguments were not valid JSON."
5240
+ },
5241
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5242
+ };
5243
+ }
5244
+ if (typeof parsedArguments !== "object" || parsedArguments === null || Array.isArray(parsedArguments)) {
5245
+ return {
5246
+ kind: "rejected",
5247
+ requestedTool,
5248
+ toolName: mcpTraceName ?? requestedTool,
5249
+ callId: toolCall.id,
5250
+ data: {
5251
+ reason: "invalid_json",
5252
+ requestedTool,
5253
+ message: "Tool arguments must decode to a JSON object."
5254
+ },
5255
+ modelMessage: INVALID_JSON_MODEL_MESSAGE
5256
+ };
5257
+ }
5258
+ const source = override ? { kind: "override", execute: override } : configuredTool ? { kind: "local", tool: configuredTool } : { kind: "mcp", call: mcpCall };
5259
+ let preparedArguments = parsedArguments;
5260
+ if (source.kind === "local") {
5261
+ try {
5262
+ preparedArguments = prepareToolInput(source.tool, parsedArguments);
5263
+ } catch (error) {
5264
+ return {
5265
+ kind: "rejected",
5266
+ requestedTool,
5267
+ toolName: requestedTool,
5268
+ callId: toolCall.id,
5269
+ data: {
5270
+ reason: "invalid_arguments",
5271
+ requestedTool,
5272
+ args: parsedArguments,
5273
+ issues: toolArgumentIssues(error)
5274
+ },
5275
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5276
+ };
5277
+ }
5278
+ }
5279
+ try {
5280
+ preparedArguments = cloneToolArguments(preparedArguments);
5281
+ } catch (error) {
5282
+ return {
5283
+ kind: "rejected",
5284
+ requestedTool,
5285
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5286
+ callId: toolCall.id,
5287
+ data: {
5288
+ reason: "invalid_arguments",
5289
+ requestedTool,
5290
+ args: parsedArguments,
5291
+ issues: toolArgumentIssues(error)
5292
+ },
5293
+ modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
5294
+ };
5295
+ }
5296
+ return {
5297
+ requestedTool,
5298
+ toolName: source.kind === "mcp" ? mcpTraceName ?? requestedTool : requestedTool,
5299
+ callId: toolCall.id,
5300
+ args: preparedArguments,
5301
+ configuredTool,
5302
+ source
5303
+ };
5304
+ }
5305
+ function isAbortError3(error) {
5306
+ try {
5307
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
5308
+ } catch {
5309
+ return false;
5310
+ }
5311
+ }
5312
+ function normalizeAbortError(reason) {
5313
+ if (isAbortError3(reason)) return reason;
5314
+ const error = new Error("The operation was aborted");
5315
+ error.name = "AbortError";
5316
+ if (reason !== void 0) {
5317
+ Object.defineProperty(error, "cause", { value: reason, enumerable: false });
5318
+ }
5319
+ return error;
5320
+ }
5321
+ function cancellationError(signal, error) {
5322
+ if (signal?.aborted) return normalizeAbortError(signal.reason);
5323
+ return isAbortError3(error) ? normalizeAbortError(error) : void 0;
5324
+ }
5325
+ function eventError(error) {
5326
+ let isError = false;
5327
+ try {
5328
+ isError = error instanceof Error;
5329
+ } catch {
5330
+ }
5331
+ if (isError) {
5332
+ const read = (key) => {
5333
+ try {
5334
+ return Reflect.get(error, key);
4635
5335
  } catch {
5336
+ return void 0;
4636
5337
  }
4637
- }
5338
+ };
5339
+ const name = read("name");
5340
+ const message = read("message");
5341
+ const code = read("code");
5342
+ const cause = read("cause");
5343
+ return {
5344
+ name: typeof name === "string" ? name : "Error",
5345
+ message: typeof message === "string" ? message : "Unknown error",
5346
+ ...typeof code === "string" ? { code } : {},
5347
+ ...cause !== void 0 ? { cause } : {}
5348
+ };
5349
+ }
5350
+ try {
5351
+ return { name: "Error", message: String(error) };
5352
+ } catch {
5353
+ return { name: "Error", message: "Unknown thrown value" };
5354
+ }
5355
+ }
5356
+ function isToolFailure(error) {
5357
+ try {
5358
+ return error instanceof ToolFailure;
5359
+ } catch {
5360
+ return false;
5361
+ }
5362
+ }
5363
+ function cancellation(phase, error, result) {
5364
+ let reason;
5365
+ try {
5366
+ reason = error instanceof Error && typeof error.message === "string" ? error.message : void 0;
5367
+ } catch {
5368
+ }
5369
+ const cancellation2 = phase === "after_handler" || phase === "after_hook" || phase === "projection" || phase === "serialization" ? { phase, result, ...reason ? { reason } : {} } : { phase, ...reason ? { reason } : {} };
5370
+ return {
5371
+ kind: "cancelled",
5372
+ cancellation: cancellation2,
5373
+ error: normalizeAbortError(error)
4638
5374
  };
4639
- walk(schema, "");
4640
- const result = { count, paths: [...paths] };
4641
- refinementCache.set(schema, result);
4642
- return result;
4643
5375
  }
4644
- var DOCS_URL = "docs/observability.md#schema-diagnostics";
4645
- var MAX_WARNED_KEYS = 2048;
4646
- var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
4647
- function warnSchemaDiagnosticOnce(key, message, silent) {
4648
- if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
4649
- if (warnedDiagnosticKeys.has(key)) return;
4650
- if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
4651
- warnedDiagnosticKeys.add(key);
4652
- console.warn(`[axl] ${message} See ${DOCS_URL}.`);
5376
+ function failed(phase, error, options = {}) {
5377
+ let modelMessage;
5378
+ if (isToolFailure(error)) {
5379
+ try {
5380
+ modelMessage = typeof error.modelMessage === "string" ? error.modelMessage : void 0;
5381
+ } catch {
5382
+ }
5383
+ }
5384
+ const toolFailure = modelMessage !== void 0;
5385
+ const common = {
5386
+ phase,
5387
+ kind: toolFailure ? "tool_failure" : "unexpected",
5388
+ disposition: toolFailure ? "continue" : "abort",
5389
+ error: eventError(error)
5390
+ };
5391
+ const failure = phase === "handler" ? { ...common, attempts: options.attempts ?? 1 } : phase === "after_hook" ? { ...common, result: options.result } : common;
5392
+ return {
5393
+ kind: "failed",
5394
+ failure,
5395
+ error,
5396
+ ...modelMessage !== void 0 ? { modelMessage } : {}
5397
+ };
4653
5398
  }
4654
-
4655
- // src/config.ts
4656
- function defineConfig(config) {
4657
- return config;
5399
+ function mcpContent(result) {
5400
+ return result.content.map((content) => content.type === "text" ? content.text : `[${content.type}]`).join("\n");
4658
5401
  }
4659
- function parseDuration(duration) {
4660
- const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)$/);
4661
- if (!match) throw new Error(`Invalid duration format: "${duration}"`);
4662
- const value = parseFloat(match[1]);
4663
- const unit = match[2];
4664
- switch (unit) {
4665
- case "ms":
4666
- return value;
4667
- case "s":
4668
- return value * 1e3;
4669
- case "m":
4670
- return value * 6e4;
4671
- case "h":
4672
- return value * 36e5;
4673
- default:
4674
- throw new Error(`Unknown duration unit: "${unit}"`);
5402
+ function safeMcpModelError(content) {
5403
+ let normalized = "";
5404
+ for (const character of content) {
5405
+ const code = character.charCodeAt(0);
5406
+ normalized += code <= 31 && code !== 9 && code !== 10 && code !== 13 || code === 127 ? " " : character;
4675
5407
  }
5408
+ return `MCP tool error: ${normalized.slice(0, MAX_MCP_MODEL_ERROR_LENGTH)}`;
4676
5409
  }
4677
- function parseCost(cost) {
4678
- const match = cost.match(/^\$?([\d.]+)$/);
4679
- if (!match) throw new Error(`Invalid cost format: "${cost}"`);
4680
- return parseFloat(match[1]);
5410
+ async function executeAcceptedTool(options) {
5411
+ const { invocation, context, signal, requestApproval, createChildContext } = options;
5412
+ let effectiveArgs = cloneToolArguments(invocation.args);
5413
+ if (invocation.source.kind === "local" && invocation.source.tool.requireApproval) {
5414
+ try {
5415
+ signal?.throwIfAborted();
5416
+ const approval = await requestApproval();
5417
+ signal?.throwIfAborted();
5418
+ if (!approval.approved) return { kind: "denied", reason: approval.reason };
5419
+ } catch (error) {
5420
+ rethrowEventStreamOverflow(error);
5421
+ const abort = cancellationError(signal, error);
5422
+ if (abort !== void 0) return cancellation("approval", abort);
5423
+ return {
5424
+ kind: "failed",
5425
+ failure: {
5426
+ phase: "approval",
5427
+ kind: "infrastructure",
5428
+ disposition: "abort",
5429
+ error: eventError(error)
5430
+ },
5431
+ error
5432
+ };
5433
+ }
5434
+ }
5435
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.before) {
5436
+ try {
5437
+ signal?.throwIfAborted();
5438
+ effectiveArgs = await invocation.source.tool.hooks.before(effectiveArgs, context);
5439
+ signal?.throwIfAborted();
5440
+ } catch (error) {
5441
+ rethrowEventStreamOverflow(error);
5442
+ const abort = cancellationError(signal, error);
5443
+ if (abort !== void 0) return cancellation("before_hook", abort);
5444
+ return failed("before_hook", error);
5445
+ }
5446
+ }
5447
+ let result;
5448
+ let attempts = 1;
5449
+ try {
5450
+ signal?.throwIfAborted();
5451
+ switch (invocation.source.kind) {
5452
+ case "override": {
5453
+ const executeOverride = invocation.source.execute;
5454
+ result = await executeOverride(effectiveArgs);
5455
+ break;
5456
+ }
5457
+ case "local":
5458
+ result = await executePreparedTool(
5459
+ invocation.source.tool,
5460
+ effectiveArgs,
5461
+ createChildContext(),
5462
+ {
5463
+ signal,
5464
+ onAttempt: (attempt) => attempts = attempt,
5465
+ checkAfterHandlerAbort: false
5466
+ }
5467
+ );
5468
+ break;
5469
+ case "mcp": {
5470
+ const mcpResult = await invocation.source.call(effectiveArgs);
5471
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, mcpResult);
5472
+ if (mcpResult.isError) {
5473
+ const content = mcpContent(mcpResult);
5474
+ const error = new Error(content || "MCP tool returned an error");
5475
+ return {
5476
+ kind: "failed",
5477
+ failure: {
5478
+ phase: "handler",
5479
+ kind: "mcp_error",
5480
+ disposition: "continue",
5481
+ error: { name: "McpToolError", message: error.message, code: "MCP_TOOL_ERROR" },
5482
+ attempts: 1
5483
+ },
5484
+ error,
5485
+ modelMessage: safeMcpModelError(content)
5486
+ };
5487
+ }
5488
+ result = mcpResult;
5489
+ break;
5490
+ }
5491
+ }
5492
+ } catch (error) {
5493
+ rethrowEventStreamOverflow(error);
5494
+ const abort = cancellationError(signal, error);
5495
+ if (abort !== void 0) return cancellation("handler", abort);
5496
+ return failed("handler", error, { attempts });
5497
+ }
5498
+ if (signal?.aborted) return cancellation("after_handler", signal.reason, result);
5499
+ if (invocation.source.kind === "local" && invocation.source.tool.hooks?.after) {
5500
+ const rawResult = result;
5501
+ try {
5502
+ result = await invocation.source.tool.hooks.after(result, context);
5503
+ signal?.throwIfAborted();
5504
+ } catch (error) {
5505
+ rethrowEventStreamOverflow(error);
5506
+ const abort = cancellationError(signal, error);
5507
+ if (abort !== void 0) return cancellation("after_hook", abort, rawResult);
5508
+ return failed("after_hook", error, { result: rawResult });
5509
+ }
5510
+ }
5511
+ return { kind: "succeeded", result };
4681
5512
  }
4682
- function resolveConfig(config) {
4683
- const resolved = { ...config };
4684
- if (process.env.AXL_DEFAULT_PROVIDER) {
4685
- resolved.defaultProvider = process.env.AXL_DEFAULT_PROVIDER;
5513
+ function prepareToolMessage(invocation, outcome) {
5514
+ const sensitive = invocation.configuredTool?.sensitive === true;
5515
+ if (outcome.kind === "denied") {
5516
+ return {
5517
+ ok: true,
5518
+ content: JSON.stringify({ error: "Tool request was denied by human approval." })
5519
+ };
4686
5520
  }
4687
- if (process.env.AXL_STATE_STORE) {
4688
- const envStore = process.env.AXL_STATE_STORE;
4689
- if (envStore === "memory" || envStore === "sqlite") {
4690
- resolved.state = {
4691
- ...resolved.state,
4692
- store: envStore
5521
+ if (outcome.kind === "failed") {
5522
+ if (outcome.failure.disposition !== "continue" || outcome.modelMessage === void 0) {
5523
+ throw new Error("Aborting tool failures cannot produce provider messages");
5524
+ }
5525
+ return { ok: true, content: sensitive ? SENSITIVE_FAILURE_MESSAGE : outcome.modelMessage };
5526
+ }
5527
+ if (outcome.kind !== "succeeded") {
5528
+ throw new Error("Cancelled tool calls cannot produce provider messages");
5529
+ }
5530
+ if (sensitive) return { ok: true, content: SENSITIVE_SUCCESS_MESSAGE };
5531
+ if (invocation.source.kind === "mcp") {
5532
+ try {
5533
+ return { ok: true, content: mcpContent(outcome.result) };
5534
+ } catch (error) {
5535
+ rethrowEventStreamOverflow(error);
5536
+ return { ok: false, phase: "serialization", error };
5537
+ }
5538
+ }
5539
+ if (invocation.configuredTool?.toModelOutput) {
5540
+ try {
5541
+ const project = invocation.configuredTool.toModelOutput;
5542
+ return {
5543
+ ok: true,
5544
+ content: serializeToolModelOutput(
5545
+ invocation.requestedTool,
5546
+ project(outcome.result)
5547
+ )
5548
+ };
5549
+ } catch (error) {
5550
+ rethrowEventStreamOverflow(error);
5551
+ if (error instanceof ToolModelOutputError && isEventStreamOverflowError(error.cause)) {
5552
+ throw error.cause;
5553
+ }
5554
+ return {
5555
+ ok: false,
5556
+ phase: "projection",
5557
+ error: error instanceof ToolModelOutputError && error.toolName === invocation.requestedTool ? error : new ToolModelOutputError(invocation.requestedTool, error)
4693
5558
  };
4694
5559
  }
4695
5560
  }
4696
- if (process.env.AXL_TRACE_ENABLED !== void 0) {
4697
- resolved.trace = { ...resolved.trace, enabled: process.env.AXL_TRACE_ENABLED === "true" };
5561
+ try {
5562
+ const content = JSON.stringify(outcome.result);
5563
+ if (content === void 0) throw new Error("Tool result is not JSON serializable");
5564
+ return { ok: true, content };
5565
+ } catch (error) {
5566
+ rethrowEventStreamOverflow(error);
5567
+ return { ok: false, phase: "serialization", error };
4698
5568
  }
4699
- if (process.env.AXL_TRACE_LEVEL) {
4700
- resolved.trace = {
4701
- ...resolved.trace,
4702
- level: process.env.AXL_TRACE_LEVEL
5569
+ }
5570
+ async function settleAcceptedTool(options) {
5571
+ const internal = await executeAcceptedTool(options);
5572
+ if (internal.kind === "cancelled") {
5573
+ return {
5574
+ outcome: { status: "cancelled", cancellation: internal.cancellation },
5575
+ abortError: internal.error
4703
5576
  };
4704
5577
  }
4705
- if (process.env.OPENAI_API_KEY) {
4706
- if (!resolved.providers) resolved.providers = {};
4707
- resolved.providers.openai = {
4708
- ...resolved.providers.openai ?? {},
4709
- apiKey: process.env.OPENAI_API_KEY
5578
+ if (internal.kind === "failed" && internal.failure.disposition === "abort") {
5579
+ return {
5580
+ outcome: { status: "failed", failure: internal.failure },
5581
+ abortError: internal.error
4710
5582
  };
4711
5583
  }
4712
- if (process.env.ANTHROPIC_API_KEY) {
4713
- if (!resolved.providers) resolved.providers = {};
4714
- resolved.providers.anthropic = {
4715
- ...resolved.providers.anthropic ?? {},
4716
- apiKey: process.env.ANTHROPIC_API_KEY
5584
+ const result = internal.kind === "succeeded" ? internal.result : void 0;
5585
+ const outputPhase = internal.kind === "succeeded" && !options.invocation.configuredTool?.sensitive && options.invocation.source.kind !== "mcp" && options.invocation.configuredTool?.toModelOutput ? "projection" : "serialization";
5586
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5587
+ return {
5588
+ outcome: {
5589
+ status: "cancelled",
5590
+ cancellation: { phase: outputPhase, result }
5591
+ },
5592
+ abortError: normalizeAbortError(options.signal.reason)
4717
5593
  };
4718
5594
  }
4719
- const googleKey = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
4720
- if (googleKey) {
4721
- if (!resolved.providers) resolved.providers = {};
4722
- resolved.providers.google = {
4723
- ...resolved.providers.google ?? {},
4724
- apiKey: googleKey
5595
+ const prepared = prepareToolMessage(options.invocation, internal);
5596
+ if (internal.kind === "succeeded" && options.signal?.aborted) {
5597
+ return {
5598
+ outcome: {
5599
+ status: "cancelled",
5600
+ cancellation: { phase: outputPhase, result }
5601
+ },
5602
+ abortError: normalizeAbortError(options.signal.reason)
4725
5603
  };
4726
5604
  }
4727
- return resolved;
5605
+ if (!prepared.ok) {
5606
+ const outputAbort = cancellationError(options.signal, prepared.error) ?? (prepared.error instanceof ToolModelOutputError ? cancellationError(options.signal, prepared.error.cause) : void 0);
5607
+ if (outputAbort !== void 0) {
5608
+ return {
5609
+ outcome: {
5610
+ status: "cancelled",
5611
+ cancellation: { phase: prepared.phase, result }
5612
+ },
5613
+ abortError: outputAbort
5614
+ };
5615
+ }
5616
+ const failure = {
5617
+ phase: prepared.phase,
5618
+ kind: "output",
5619
+ disposition: "abort",
5620
+ error: eventError(prepared.error),
5621
+ result
5622
+ };
5623
+ return {
5624
+ outcome: { status: "failed", failure },
5625
+ abortError: prepared.error
5626
+ };
5627
+ }
5628
+ if (internal.kind === "succeeded") {
5629
+ return {
5630
+ outcome: { status: "succeeded", result: internal.result },
5631
+ providerContent: prepared.content
5632
+ };
5633
+ }
5634
+ if (internal.kind === "denied") {
5635
+ return {
5636
+ outcome: { status: "denied", ...internal.reason ? { reason: internal.reason } : {} },
5637
+ providerContent: prepared.content
5638
+ };
5639
+ }
5640
+ return {
5641
+ outcome: { status: "failed", failure: internal.failure },
5642
+ providerContent: prepared.content
5643
+ };
5644
+ }
5645
+ function recordToolSpanOutcome(span, outcome) {
5646
+ span.setAttribute("axl.tool.outcome", outcome.status);
5647
+ span.setAttribute("axl.tool.success", outcome.status === "succeeded");
5648
+ switch (outcome.status) {
5649
+ case "succeeded":
5650
+ span.setStatus("ok");
5651
+ return;
5652
+ case "denied":
5653
+ span.setStatus("unset");
5654
+ return;
5655
+ case "failed":
5656
+ span.setAttribute("axl.tool.phase", outcome.failure.phase);
5657
+ span.setStatus("error");
5658
+ return;
5659
+ case "cancelled":
5660
+ span.setAttribute("axl.tool.phase", outcome.cancellation.phase);
5661
+ span.setStatus("error");
5662
+ }
4728
5663
  }
4729
5664
 
4730
5665
  // src/context.ts
4731
5666
  var signalStorage = new AsyncLocalStorage();
5667
+ var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
5668
+ function resolveBranchDrainTimeoutMs(value) {
5669
+ const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
5670
+ if (typeof resolved !== "number" || Number.isNaN(resolved) || !Number.isFinite(resolved) || resolved < 0) {
5671
+ throw new AxlError(
5672
+ "INVALID_CONFIG",
5673
+ `branchDrainTimeoutMs must be a non-negative finite number (got ${String(resolved)})`
5674
+ );
5675
+ }
5676
+ return resolved;
5677
+ }
5678
+ function parseHumanDecision(value) {
5679
+ const invalid2 = () => {
5680
+ throw new AxlError(
5681
+ "INVALID_HUMAN_DECISION",
5682
+ "Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
5683
+ );
5684
+ };
5685
+ if (typeof value !== "object" || value === null || Array.isArray(value)) invalid2();
5686
+ let prototype;
5687
+ try {
5688
+ prototype = Object.getPrototypeOf(value);
5689
+ } catch {
5690
+ return invalid2();
5691
+ }
5692
+ if (prototype !== Object.prototype && prototype !== null) invalid2();
5693
+ let ownKeys;
5694
+ try {
5695
+ ownKeys = Reflect.ownKeys(value);
5696
+ } catch {
5697
+ return invalid2();
5698
+ }
5699
+ if (ownKeys.some(
5700
+ (key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
5701
+ )) {
5702
+ invalid2();
5703
+ }
5704
+ const readDataProperty = (key) => {
5705
+ let descriptor;
5706
+ try {
5707
+ descriptor = Object.getOwnPropertyDescriptor(value, key);
5708
+ } catch {
5709
+ return invalid2();
5710
+ }
5711
+ if (!descriptor) {
5712
+ if (ownKeys.includes(key)) invalid2();
5713
+ return { present: false, value: void 0 };
5714
+ }
5715
+ if (!ownKeys.includes(key)) return invalid2();
5716
+ if (!("value" in descriptor)) return invalid2();
5717
+ return { present: true, value: descriptor.value };
5718
+ };
5719
+ const approved = readDataProperty("approved");
5720
+ const data = readDataProperty("data");
5721
+ const reason = readDataProperty("reason");
5722
+ if (approved.present && approved.value === true && !reason.present) {
5723
+ if (data.value === void 0 || typeof data.value === "string") {
5724
+ return { approved: true, ...data.value === void 0 ? {} : { data: data.value } };
5725
+ }
5726
+ }
5727
+ if (approved.present && approved.value === false && !data.present) {
5728
+ if (reason.value === void 0 || typeof reason.value === "string") {
5729
+ return {
5730
+ approved: false,
5731
+ ...reason.value === void 0 ? {} : { reason: reason.value }
5732
+ };
5733
+ }
5734
+ }
5735
+ return invalid2();
5736
+ }
4732
5737
  var askStorage = new AsyncLocalStorage();
4733
5738
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
4734
5739
  function zodToJsonSchema(schema) {
@@ -4862,9 +5867,8 @@ var WorkflowContext = class _WorkflowContext {
4862
5867
  providerRegistry;
4863
5868
  sessionHistory;
4864
5869
  onTrace;
4865
- onToken;
4866
- onToolCall;
4867
5870
  pendingDecisions;
5871
+ awaitHumanClaims;
4868
5872
  budgetContext;
4869
5873
  stateStore;
4870
5874
  /** Root step counter for this execution. Inherited by every ctx.ask()
@@ -4885,27 +5889,27 @@ var WorkflowContext = class _WorkflowContext {
4885
5889
  * the caller-supplied key.
4886
5890
  */
4887
5891
  autoCheckpointCounters;
4888
- /** Idempotency guards for `workflow_start` / `workflow_end` emission.
5892
+ branchDrainState;
5893
+ /** Root-shared idempotency guards for workflow lifecycle emission.
4889
5894
  * Both `runtime.execute()` and `runtime.stream()` have paths where a
4890
5895
  * post-emit side-effect (checkpoint deletion, state-store persistence)
4891
5896
  * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
4892
5897
  * fired — the outer catch would then fire `_emitWorkflowEnd({status:
4893
5898
  * 'failed'})` for a second time. These flags make both emitters
4894
5899
  * single-fire so consumers never see paired-then-conflicting terminal
4895
- * events for one execution. */
4896
- _workflowStartEmitted = false;
4897
- _workflowEndEmitted = false;
5900
+ * events for one execution. Sharing the state with child contexts also
5901
+ * suppresses late branch events after bounded finalization. */
5902
+ workflowLifecycleState;
4898
5903
  signal;
4899
5904
  summaryCache;
4900
5905
  workflowName;
4901
5906
  mcpManager;
4902
5907
  spanManager;
4903
5908
  memoryManager;
4904
- resumeMode;
4905
5909
  toolOverrides;
4906
5910
  awaitHumanHandler;
4907
- onAgentStart;
4908
5911
  onAgentCallComplete;
5912
+ _onDecisionCleanupFailed;
4909
5913
  /** Shared mutable slot for the lazy `AxlEventBus`. The slot is shared
4910
5914
  * by reference across parent/child contexts so a late allocation
4911
5915
  * (consumer subscribes after a child context exists) is visible
@@ -4914,6 +5918,7 @@ var WorkflowContext = class _WorkflowContext {
4914
5918
  * contexts that never observe). */
4915
5919
  _busRef;
4916
5920
  _eventStreamOptions;
5921
+ _forceStreaming;
4917
5922
  /** Removes the constructor-registered `'abort'` listener from
4918
5923
  * `this.signal` so a long-lived signal (e.g., a request-scoped or
4919
5924
  * process-wide signal reused across many `runtime.createContext()` /
@@ -4933,8 +5938,7 @@ var WorkflowContext = class _WorkflowContext {
4933
5938
  * *between* asks. `ctx.events.partialObjects` is the coalescing view
4934
5939
  * designed for streaming-structured-output UIs.
4935
5940
  * - **Ad-hoc contexts** from `runtime.createContext()`: the same
4936
- * iterable replaces the legacy `onToken` / `onToolCall` /
4937
- * `onAgentStart` callbacks.
5941
+ * iterable provides typed per-context observation.
4938
5942
  * - **Cross-execution**: prefer `runtime.on('trace', …)` instead — that
4939
5943
  * fans out across all executions, while `ctx.events` is per-context.
4940
5944
  *
@@ -4949,9 +5953,9 @@ var WorkflowContext = class _WorkflowContext {
4949
5953
  *
4950
5954
  * **Subscribe before the first `ctx.ask()`.** The streaming code
4951
5955
  * path inside `ctx.ask()` activates only when an observer is
4952
- * present at the time the ask starts (`_streamingEnabled` — either
4953
- * the legacy `onToken` callback is set or `ctx.events` has been
4954
- * allocated). If you allocate `ctx.events` AFTER a `ctx.ask()` has
5956
+ * present at the time the ask starts (`_streamingEnabled` — an explicit
5957
+ * runtime streaming mode or allocated `ctx.events`). If you allocate
5958
+ * `ctx.events` AFTER a `ctx.ask()` has
4955
5959
  * begun, that in-flight ask will not stream `token` /
4956
5960
  * `partial_object` events at all (the agent loop went through
4957
5961
  * `provider.chat` instead of `provider.stream`). Subsequent asks
@@ -4990,18 +5994,16 @@ var WorkflowContext = class _WorkflowContext {
4990
5994
  this._busRef.current?._finish();
4991
5995
  this.abortListenerCleanup?.();
4992
5996
  }
4993
- /** True if any observer wants per-token streaming for asks started
4994
- * from this context: either the legacy `onToken` callback is set
4995
- * (e.g., `runtime.stream()` plumbs a sentinel) OR `ctx.events` has
4996
- * been allocated (a workflow-handler consumer subscribed before
4997
- * the ask). `ctx.ask` reads this to decide whether to enter the
5997
+ /** True when asks should use provider streaming: `runtime.stream()` set an
5998
+ * explicit internal mode or `ctx.events` has been allocated. `ctx.ask`
5999
+ * reads this to decide whether to enter the
4998
6000
  * streaming code path; the gate is re-checked per ask, so a
4999
6001
  * consumer subscribing AFTER the first ask started still gets
5000
6002
  * streaming on the next one. The check `_busRef.current !== undefined`
5001
6003
  * is a one-way flag — `_finish` does not unset the reference, so
5002
6004
  * once an observer was present, every subsequent ask streams. */
5003
6005
  get _streamingEnabled() {
5004
- return this.onToken !== void 0 || this._busRef.current !== void 0;
6006
+ return this._forceStreaming || this._busRef.current !== void 0;
5005
6007
  }
5006
6008
  constructor(init) {
5007
6009
  this.input = init.input;
@@ -5011,9 +6013,8 @@ var WorkflowContext = class _WorkflowContext {
5011
6013
  this.providerRegistry = init.providerRegistry;
5012
6014
  this.sessionHistory = init.sessionHistory ?? [];
5013
6015
  this.onTrace = init.onTrace;
5014
- this.onToken = init.onToken;
5015
- this.onToolCall = init.onToolCall;
5016
6016
  this.pendingDecisions = init.pendingDecisions;
6017
+ this.awaitHumanClaims = init.awaitHumanClaims ?? /* @__PURE__ */ new Map();
5017
6018
  this.budgetContext = init.budgetContext;
5018
6019
  this.stateStore = init.stateStore;
5019
6020
  this.signal = init.signal;
@@ -5021,13 +6022,13 @@ var WorkflowContext = class _WorkflowContext {
5021
6022
  this.mcpManager = init.mcpManager;
5022
6023
  this.spanManager = init.spanManager;
5023
6024
  this.memoryManager = init.memoryManager;
5024
- this.resumeMode = init.resumeMode ?? false;
5025
6025
  this.toolOverrides = init.toolOverrides;
5026
6026
  this.awaitHumanHandler = init.awaitHumanHandler;
5027
- this.onAgentStart = init.onAgentStart;
5028
6027
  this.onAgentCallComplete = init.onAgentCallComplete;
6028
+ this._onDecisionCleanupFailed = init._onDecisionCleanupFailed;
5029
6029
  this._busRef = init._busRef ?? { current: void 0 };
5030
6030
  this._eventStreamOptions = init.eventStreamOptions;
6031
+ this._forceStreaming = init._forceStreaming ?? false;
5031
6032
  if (init._busRef === void 0 && this.signal && !this.signal.aborted) {
5032
6033
  const signal = this.signal;
5033
6034
  const onAbort = () => {
@@ -5043,6 +6044,15 @@ var WorkflowContext = class _WorkflowContext {
5043
6044
  byAgent: /* @__PURE__ */ new Map(),
5044
6045
  root: 0
5045
6046
  };
6047
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
6048
+ this.branchDrainState = init.branchDrainState ?? {
6049
+ pending: /* @__PURE__ */ new Set(),
6050
+ timeoutMs: branchDrainTimeoutMs
6051
+ };
6052
+ this.workflowLifecycleState = init.workflowLifecycleState ?? {
6053
+ startEmitted: false,
6054
+ endEmitted: false
6055
+ };
5046
6056
  if (init.metadata?.summaryCache) {
5047
6057
  this.summaryCache = init.metadata.summaryCache;
5048
6058
  }
@@ -5051,16 +6061,9 @@ var WorkflowContext = class _WorkflowContext {
5051
6061
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
5052
6062
  * Shares: budget tracking, abort signals, trace emission, provider registry,
5053
6063
  * state store, span manager, memory manager, MCP manager, config,
5054
- * awaitHuman handler, pending decisions, tool overrides, AND the
5055
- * streaming callbacks (onToken / onAgentStart / onToolCall).
6064
+ * awaitHuman handler, pending decisions, tool overrides, and event bus.
5056
6065
  * Isolates: session history.
5057
6066
  *
5058
- * Streaming callbacks now propagate into nested asks because every callback
5059
- * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
5060
- * consumers that want root-only behavior can filter on `meta.depth === 0`
5061
- * instead of relying on the runtime to drop nested events. This is the
5062
- * "nested ask visibility" fix from spec §3.2.
5063
- *
5064
6067
  * The shared step counter lives in `askStorage` (ALS), so there is no
5065
6068
  * per-context counter to pass through anymore. Nested events are
5066
6069
  * correlated to their parent via `parentAskId` (set on every nested ask
@@ -5081,12 +6084,11 @@ var WorkflowContext = class _WorkflowContext {
5081
6084
  spanManager: this.spanManager,
5082
6085
  memoryManager: this.memoryManager,
5083
6086
  onTrace: this.onTrace,
5084
- onToken: this.onToken,
5085
- onToolCall: this.onToolCall,
5086
- onAgentStart: this.onAgentStart,
5087
6087
  onAgentCallComplete: this.onAgentCallComplete,
6088
+ _onDecisionCleanupFailed: this._onDecisionCleanupFailed,
5088
6089
  awaitHumanHandler: this.awaitHumanHandler,
5089
6090
  pendingDecisions: this.pendingDecisions,
6091
+ awaitHumanClaims: this.awaitHumanClaims,
5090
6092
  toolOverrides: this.toolOverrides,
5091
6093
  signal: this.signal,
5092
6094
  workflowName: this.workflowName,
@@ -5094,12 +6096,15 @@ var WorkflowContext = class _WorkflowContext {
5094
6096
  // emitted from `ctx.ask` / spawn / race / parallel / map in this
5095
6097
  // child context don't collide with the parent's in the state store.
5096
6098
  autoCheckpointCounters: this.autoCheckpointCounters,
6099
+ branchDrainState: this.branchDrainState,
6100
+ workflowLifecycleState: this.workflowLifecycleState,
5097
6101
  // Share the parent's bus slot (mutable ref). Late allocation in
5098
6102
  // either parent or child propagates because both read the same
5099
6103
  // slot. Nested-ask events (agent-as-tool pattern) bubble up to
5100
6104
  // the parent's iterator with `parentAskId`/`depth` correlation
5101
6105
  // intact.
5102
6106
  _busRef: this._busRef,
6107
+ _forceStreaming: this._forceStreaming,
5103
6108
  eventStreamOptions: this._eventStreamOptions
5104
6109
  // Isolated: sessionHistory (empty)
5105
6110
  });
@@ -5112,28 +6117,194 @@ var WorkflowContext = class _WorkflowContext {
5112
6117
  get currentSignal() {
5113
6118
  return signalStorage.getStore() ?? this.signal;
5114
6119
  }
5115
- /**
5116
- * Build a `CallbackMeta` for the current ask frame. Used at every
5117
- * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
5118
- * route or filter by ask correlation (`meta.depth === 0` for root
5119
- * chat UIs, etc.).
5120
- *
5121
- * If the call is somehow outside an ask frame (e.g., a programmatic
5122
- * `tool.run()` test harness), falls back to a synthetic meta keyed off
5123
- * `executionId` so the type contract holds — consumers see `depth: 0`
5124
- * and can ignore those events.
5125
- */
5126
- currentCallbackMeta(agentName) {
5127
- const frame = askStorage.getStore();
5128
- if (frame) {
5129
- return {
5130
- askId: frame.askId,
5131
- ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
5132
- depth: frame.depth,
5133
- agent: agentName
6120
+ _registerBranchDrain(promise) {
6121
+ const state = this.branchDrainState;
6122
+ state.pending.add(promise);
6123
+ void promise.then(
6124
+ () => state.pending.delete(promise),
6125
+ (error) => {
6126
+ state.pending.delete(promise);
6127
+ if (!state.overflow && isEventStreamOverflowError(error)) {
6128
+ state.overflow = error;
6129
+ }
6130
+ }
6131
+ );
6132
+ }
6133
+ /** Wait for race/quorum losers (including nested branch work) to settle.
6134
+ * Primitive calls still resolve as soon as their winner/quorum is known;
6135
+ * this is only the runtime's terminal/persistence barrier. */
6136
+ async _drainBranchWork() {
6137
+ const state = this.branchDrainState;
6138
+ state.deadline ??= Date.now() + state.timeoutMs;
6139
+ while (state.pending.size > 0) {
6140
+ const overflow2 = state.overflow;
6141
+ state.overflow = void 0;
6142
+ if (overflow2) throw overflow2;
6143
+ const remaining = state.deadline - Date.now();
6144
+ if (remaining <= 0) {
6145
+ return {
6146
+ complete: false,
6147
+ reason: "branch_drain_timeout",
6148
+ pendingContinuations: state.pending.size,
6149
+ timeoutMs: state.timeoutMs
6150
+ };
6151
+ }
6152
+ let timer;
6153
+ const settled = await Promise.race([
6154
+ Promise.allSettled([...state.pending]).then(() => true),
6155
+ new Promise((resolve2) => {
6156
+ timer = setTimeout(() => resolve2(false), remaining);
6157
+ })
6158
+ ]);
6159
+ if (timer) clearTimeout(timer);
6160
+ if (!settled) {
6161
+ return {
6162
+ complete: false,
6163
+ reason: "branch_drain_timeout",
6164
+ pendingContinuations: state.pending.size,
6165
+ timeoutMs: state.timeoutMs
6166
+ };
6167
+ }
6168
+ }
6169
+ const overflow = state.overflow;
6170
+ state.overflow = void 0;
6171
+ if (overflow) throw overflow;
6172
+ state.deadline = void 0;
6173
+ return { complete: true };
6174
+ }
6175
+ /** Resolve, execute, and observe one non-handoff tool request. Rejections
6176
+ * never start. Every accepted request emits one terminal event before a
6177
+ * continuation message or abort escapes this coordinator. */
6178
+ async observeToolInvocation(agent2, toolCall, currentMessages, availableTools) {
6179
+ const signal = this.currentSignal;
6180
+ signal?.throwIfAborted();
6181
+ const requestedTool = toolCall.function.name;
6182
+ const configuredTool = agent2._config.tools?.find(
6183
+ (candidate) => candidate.name === requestedTool
6184
+ );
6185
+ const override = this.toolOverrides?.get(requestedTool);
6186
+ const mcpManager = this.mcpManager;
6187
+ const mcpMatch = !configuredTool ? mcpManager?.getToolsForAgent(agent2._config.mcp, agent2._config.mcpTools).find(({ tool: tool2 }) => tool2.name === requestedTool) : void 0;
6188
+ const parsed = parseToolInvocation({
6189
+ toolCall,
6190
+ configuredTool,
6191
+ override,
6192
+ ...mcpMatch && mcpManager ? {
6193
+ mcpCall: (args) => mcpManager.callTool(requestedTool, args, mcpMatch.server),
6194
+ mcpTraceName: `${mcpMatch.server}:${requestedTool}`
6195
+ } : {},
6196
+ availableTools
6197
+ });
6198
+ signal?.throwIfAborted();
6199
+ if ("kind" in parsed) {
6200
+ this.emitEvent({
6201
+ type: "tool_call_rejected",
6202
+ agent: agent2._name,
6203
+ tool: parsed.toolName,
6204
+ callId: parsed.callId,
6205
+ data: parsed.data
6206
+ });
6207
+ currentMessages.push({
6208
+ role: "tool",
6209
+ content: parsed.modelMessage,
6210
+ tool_call_id: parsed.callId
6211
+ });
6212
+ return;
6213
+ }
6214
+ const startArgs = cloneToolArguments(parsed.args);
6215
+ this.emitEvent({
6216
+ type: "tool_call_start",
6217
+ agent: agent2._name,
6218
+ tool: parsed.toolName,
6219
+ callId: parsed.callId,
6220
+ data: {
6221
+ args: startArgs,
6222
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {}
6223
+ }
6224
+ });
6225
+ const startedAt = Date.now();
6226
+ const requestApproval = async () => {
6227
+ const approve = async () => {
6228
+ const approvalArgs = cloneToolArguments(parsed.args);
6229
+ const decision = await this.awaitHuman({
6230
+ channel: "tool_approval",
6231
+ prompt: `Tool "${parsed.toolName}" wants to execute with args: ${JSON.stringify(parsed.args)}`,
6232
+ metadata: { toolName: parsed.toolName, args: approvalArgs, agent: agent2._name }
6233
+ });
6234
+ return decision.approved ? { approved: true } : { approved: false, reason: decision.reason ?? "Denied by human" };
5134
6235
  };
6236
+ const approval = this.spanManager ? await this.spanManager.withSpanAsync(
6237
+ "axl.tool.approval",
6238
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6239
+ async (span) => {
6240
+ const value = await approve();
6241
+ span.setAttribute("axl.tool.approval.approved", value.approved);
6242
+ return value;
6243
+ }
6244
+ ) : await approve();
6245
+ this.emitEvent({
6246
+ type: "tool_approval",
6247
+ agent: agent2._name,
6248
+ tool: parsed.toolName,
6249
+ callId: parsed.callId,
6250
+ data: {
6251
+ approved: approval.approved,
6252
+ args: cloneToolArguments(parsed.args),
6253
+ ...approval.reason ? { reason: approval.reason } : {}
6254
+ }
6255
+ });
6256
+ return approval;
6257
+ };
6258
+ let terminalEmitted = false;
6259
+ const executeAndObserve = async (span) => {
6260
+ const settlement2 = await settleAcceptedTool({
6261
+ invocation: parsed,
6262
+ context: this,
6263
+ signal,
6264
+ requestApproval,
6265
+ createChildContext: () => this.createChildContext()
6266
+ });
6267
+ const duration = Date.now() - startedAt;
6268
+ span?.setAttribute("axl.tool.duration", duration);
6269
+ if (span) recordToolSpanOutcome(span, settlement2.outcome);
6270
+ if (terminalEmitted) {
6271
+ throw new Error(`Tool invocation ${parsed.callId} emitted more than one terminal event`);
6272
+ }
6273
+ terminalEmitted = true;
6274
+ try {
6275
+ this.emitEvent({
6276
+ type: "tool_call_end",
6277
+ agent: agent2._name,
6278
+ tool: parsed.toolName,
6279
+ callId: parsed.callId,
6280
+ duration,
6281
+ data: {
6282
+ args: cloneToolArguments(parsed.args),
6283
+ ...parsed.requestedTool !== parsed.toolName ? { requestedTool: parsed.requestedTool } : {},
6284
+ outcome: settlement2.outcome
6285
+ }
6286
+ });
6287
+ } catch (error) {
6288
+ if (error instanceof Error && settlement2.abortError !== void 0) {
6289
+ throw preserveErrorCause(error, settlement2.abortError);
6290
+ }
6291
+ throw error;
6292
+ }
6293
+ return settlement2;
6294
+ };
6295
+ const settlement = this.spanManager ? await this.spanManager.withSpanAsync(
6296
+ "axl.tool.call",
6297
+ { "axl.tool.name": parsed.toolName, "axl.agent.name": agent2._name },
6298
+ executeAndObserve
6299
+ ) : await executeAndObserve();
6300
+ if (settlement.providerContent !== void 0) {
6301
+ currentMessages.push({
6302
+ role: "tool",
6303
+ content: settlement.providerContent,
6304
+ tool_call_id: parsed.callId
6305
+ });
5135
6306
  }
5136
- return { askId: this.executionId, depth: 0, agent: agentName };
6307
+ if (settlement.abortError !== void 0) throw settlement.abortError;
5137
6308
  }
5138
6309
  // ── ctx.ask() ─────────────────────────────────────────────────────────
5139
6310
  async ask(agent2, prompt, options) {
@@ -5162,18 +6333,21 @@ var WorkflowContext = class _WorkflowContext {
5162
6333
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
5163
6334
  const usageCapture = {};
5164
6335
  const doCall = async () => {
5165
- const result = await this.executeAgentCall(
6336
+ const result2 = await this.executeAgentCall(
5166
6337
  agent2,
5167
6338
  prompt,
5168
6339
  options,
5169
6340
  void 0,
5170
6341
  usageCapture
5171
6342
  );
5172
- return result;
6343
+ return result2;
5173
6344
  };
5174
6345
  let outcome;
6346
+ let askFailure;
6347
+ let askFailed = false;
6348
+ let result;
5175
6349
  try {
5176
- const result = this.spanManager ? await this.spanManager.withSpanAsync(
6350
+ result = this.spanManager ? await this.spanManager.withSpanAsync(
5177
6351
  "axl.agent.ask",
5178
6352
  {
5179
6353
  "axl.agent.name": agent2._name,
@@ -5232,36 +6406,34 @@ var WorkflowContext = class _WorkflowContext {
5232
6406
  );
5233
6407
  }
5234
6408
  }
5235
- return result;
5236
6409
  } catch (err) {
6410
+ askFailed = true;
6411
+ askFailure = err;
5237
6412
  outcome = {
5238
6413
  ok: false,
5239
6414
  error: err instanceof Error ? err.message : String(err)
5240
6415
  };
5241
- throw err;
5242
- } finally {
5243
- try {
5244
- this.emitEvent({
5245
- type: "ask_end",
5246
- outcome: outcome ?? {
5247
- ok: false,
5248
- error: "ask_end emitted without outcome \u2014 internal bug"
5249
- },
5250
- cost: frame.askCost.value,
5251
- ...frame.askUnpriced ? { unpriced: true } : {},
5252
- duration: Date.now() - askStart
5253
- });
5254
- } catch (emitErr) {
5255
- if (emitErr instanceof EventStreamOverflowError && outcome && !outcome.ok) {
5256
- console.error(
5257
- "[axl] ask_end emit overflowed during error path; preserving original error:",
5258
- emitErr.message
5259
- );
5260
- } else {
5261
- throw emitErr;
5262
- }
5263
- }
5264
6416
  }
6417
+ let terminalFailure;
6418
+ let terminalFailed = false;
6419
+ try {
6420
+ this.emitEvent({
6421
+ type: "ask_end",
6422
+ outcome: outcome ?? {
6423
+ ok: false,
6424
+ error: "ask_end emitted without outcome \u2014 internal bug"
6425
+ },
6426
+ cost: frame.askCost.value,
6427
+ ...frame.askUnpriced ? { unpriced: true } : {},
6428
+ duration: Date.now() - askStart
6429
+ });
6430
+ } catch (error) {
6431
+ terminalFailed = true;
6432
+ terminalFailure = isEventStreamOverflowError(error) ? preserveErrorCause(error, askFailure) : error;
6433
+ }
6434
+ if (terminalFailed) throw terminalFailure;
6435
+ if (askFailed) throw askFailure;
6436
+ return result;
5265
6437
  });
5266
6438
  },
5267
6439
  { agent: agentName }
@@ -5423,6 +6595,7 @@ ${appendedSchemaText}`;
5423
6595
  let lastStartAttempt = 1;
5424
6596
  let lastStartMaxAttempts = 1;
5425
6597
  while (turns < maxTurns) {
6598
+ this.currentSignal?.throwIfAborted();
5426
6599
  if (Date.now() - startTime > timeoutMs) {
5427
6600
  throw new TimeoutError("ctx.ask()", timeoutMs);
5428
6601
  }
@@ -5482,7 +6655,6 @@ ${appendedSchemaText}`;
5482
6655
  }
5483
6656
  } : { type: "json_object" };
5484
6657
  }
5485
- const callbackMeta = this.currentCallbackMeta(agent2._name);
5486
6658
  const callParams = {
5487
6659
  ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
5488
6660
  maxTokens: chatOptions.maxTokens,
@@ -5521,9 +6693,9 @@ ${appendedSchemaText}`;
5521
6693
  ...messagesSnapshot ? { messages: messagesSnapshot } : {}
5522
6694
  }
5523
6695
  });
5524
- this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
5525
6696
  let response;
5526
6697
  try {
6698
+ this.currentSignal?.throwIfAborted();
5527
6699
  if (this._streamingEnabled) {
5528
6700
  let content2 = "";
5529
6701
  const toolCalls = [];
@@ -5546,7 +6718,6 @@ ${appendedSchemaText}`;
5546
6718
  if (chunk.type === "text_delta") {
5547
6719
  content2 += chunk.content;
5548
6720
  this.emitEvent({ type: "token", data: chunk.content });
5549
- this.onToken?.(chunk.content, callbackMeta);
5550
6721
  if (walker) {
5551
6722
  walker.processChunk(chunk.content);
5552
6723
  if (walker.consumeBoundary()) {
@@ -5656,6 +6827,7 @@ ${appendedSchemaText}`;
5656
6827
  ...retryReason ? { retryReason } : {}
5657
6828
  }
5658
6829
  });
6830
+ this.currentSignal?.throwIfAborted();
5659
6831
  if (response.tool_calls && response.tool_calls.length > 0) {
5660
6832
  currentMessages.push({
5661
6833
  role: "assistant",
@@ -5825,232 +6997,12 @@ ${appendedSchemaText}`;
5825
6997
  return await handoffFn();
5826
6998
  }
5827
6999
  }
5828
- const toolOverride = this.toolOverrides?.get(toolName);
5829
- if (toolOverride) {
5830
- let toolArgs2;
5831
- try {
5832
- toolArgs2 = JSON.parse(toolCall.function.arguments);
5833
- } catch {
5834
- currentMessages.push({
5835
- role: "tool",
5836
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
5837
- tool_call_id: toolCall.id
5838
- });
5839
- continue;
5840
- }
5841
- this.emitEvent({
5842
- type: "tool_call_start",
5843
- tool: toolName,
5844
- callId: toolCall.id,
5845
- data: { args: toolArgs2 }
5846
- });
5847
- this.onToolCall?.(
5848
- { name: toolName, args: toolArgs2, callId: toolCall.id },
5849
- callbackMeta
5850
- );
5851
- const toolStart2 = Date.now();
5852
- const executeOverride = async () => {
5853
- let toolResult3;
5854
- try {
5855
- toolResult3 = await toolOverride(toolArgs2);
5856
- } catch (err) {
5857
- toolResult3 = { error: err instanceof Error ? err.message : String(err) };
5858
- }
5859
- return toolResult3;
5860
- };
5861
- const toolResult2 = this.spanManager ? await this.spanManager.withSpanAsync(
5862
- "axl.tool.call",
5863
- {
5864
- "axl.tool.name": toolName,
5865
- "axl.agent.name": agent2._name
5866
- },
5867
- async (span) => {
5868
- const r = await executeOverride();
5869
- span.setAttribute("axl.tool.duration", Date.now() - toolStart2);
5870
- const isError = r && typeof r === "object" && "error" in r;
5871
- span.setAttribute("axl.tool.success", !isError);
5872
- if (isError)
5873
- span.setStatus("error", r.error);
5874
- return r;
5875
- }
5876
- ) : await executeOverride();
5877
- const resultContent2 = JSON.stringify(toolResult2);
5878
- this.emitEvent({
5879
- type: "tool_call_end",
5880
- agent: agent2._name,
5881
- tool: toolName,
5882
- duration: Date.now() - toolStart2,
5883
- data: { args: toolArgs2, result: toolResult2, callId: toolCall.id }
5884
- });
5885
- currentMessages.push({
5886
- role: "tool",
5887
- content: resultContent2,
5888
- tool_call_id: toolCall.id
5889
- });
5890
- continue;
5891
- }
5892
- const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
5893
- const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
5894
- if (!tool2 && !isMcpTool) {
5895
- this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
5896
- currentMessages.push({
5897
- role: "tool",
5898
- content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
5899
- tool_call_id: toolCall.id
5900
- });
5901
- continue;
5902
- }
5903
- let toolArgs;
5904
- try {
5905
- toolArgs = JSON.parse(toolCall.function.arguments);
5906
- } catch {
5907
- currentMessages.push({
5908
- role: "tool",
5909
- content: `Error: Invalid JSON in tool arguments. Please provide valid JSON.`,
5910
- tool_call_id: toolCall.id
5911
- });
5912
- continue;
5913
- }
5914
- this.emitEvent({
5915
- type: "tool_call_start",
5916
- tool: toolName,
5917
- callId: toolCall.id,
5918
- data: { args: toolArgs }
5919
- });
5920
- this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
5921
- const toolStart = Date.now();
5922
- if (tool2 && tool2.requireApproval) {
5923
- const approvalFn = async () => {
5924
- const decision = await this.awaitHuman({
5925
- channel: "tool_approval",
5926
- prompt: `Tool "${toolName}" wants to execute with args: ${JSON.stringify(toolArgs)}`,
5927
- metadata: { toolName, args: toolArgs, agent: agent2._name }
5928
- });
5929
- if (!decision.approved) {
5930
- const reason = decision.reason ?? "Denied by human";
5931
- currentMessages.push({
5932
- role: "tool",
5933
- content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
5934
- tool_call_id: toolCall.id
5935
- });
5936
- return { approved: false, reason };
5937
- }
5938
- return { approved: true };
5939
- };
5940
- let approvalOutcome;
5941
- if (this.spanManager) {
5942
- approvalOutcome = await this.spanManager.withSpanAsync(
5943
- "axl.tool.approval",
5944
- {
5945
- "axl.tool.name": toolName,
5946
- "axl.agent.name": agent2._name
5947
- },
5948
- async (span) => {
5949
- const result = await approvalFn();
5950
- span.setAttribute("axl.tool.approval.approved", result.approved);
5951
- return result;
5952
- }
5953
- );
5954
- } else {
5955
- approvalOutcome = await approvalFn();
5956
- }
5957
- this.emitEvent({
5958
- type: "tool_approval",
5959
- agent: agent2._name,
5960
- tool: toolName,
5961
- data: {
5962
- approved: approvalOutcome.approved,
5963
- args: toolArgs,
5964
- ...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
5965
- }
5966
- });
5967
- if (!approvalOutcome.approved) continue;
5968
- }
5969
- if (tool2 && tool2.hooks?.before) {
5970
- try {
5971
- toolArgs = await tool2.hooks.before(toolArgs, this);
5972
- } catch (err) {
5973
- const errorMsg = err instanceof Error ? err.message : String(err);
5974
- currentMessages.push({
5975
- role: "tool",
5976
- content: JSON.stringify({ error: `Before hook error: ${errorMsg}` }),
5977
- tool_call_id: toolCall.id
5978
- });
5979
- continue;
5980
- }
5981
- }
5982
- const executeTool = async () => {
5983
- let toolResult2;
5984
- let resultContent2;
5985
- if (isMcpTool && this.mcpManager) {
5986
- try {
5987
- const mcpResult = await this.mcpManager.callTool(toolName, toolArgs);
5988
- toolResult2 = mcpResult;
5989
- resultContent2 = mcpResult.content.map(
5990
- (c) => c.type === "text" ? c.text : `[${c.type}]`
5991
- ).join("\n");
5992
- if (mcpResult.isError) {
5993
- resultContent2 = `Error: ${resultContent2}`;
5994
- }
5995
- } catch (err) {
5996
- toolResult2 = { error: err instanceof Error ? err.message : String(err) };
5997
- resultContent2 = JSON.stringify(toolResult2);
5998
- }
5999
- } else if (tool2) {
6000
- const childCtx = this.createChildContext();
6001
- try {
6002
- toolResult2 = await tool2._execute(toolArgs, childCtx);
6003
- } catch (err) {
6004
- toolResult2 = { error: err instanceof Error ? err.message : String(err) };
6005
- }
6006
- if (tool2.hooks?.after && !(toolResult2 && typeof toolResult2 === "object" && "error" in toolResult2)) {
6007
- try {
6008
- toolResult2 = await tool2.hooks.after(toolResult2, this);
6009
- } catch (err) {
6010
- toolResult2 = {
6011
- error: `After hook error: ${err instanceof Error ? err.message : String(err)}`
6012
- };
6013
- }
6014
- }
6015
- resultContent2 = tool2.sensitive ? "[REDACTED - sensitive tool output]" : JSON.stringify(toolResult2);
6016
- } else {
6017
- toolResult2 = void 0;
6018
- resultContent2 = "Tool execution error";
6019
- }
6020
- return { toolResult: toolResult2, resultContent: resultContent2 };
6021
- };
6022
- const traceName = isMcpTool && this.mcpManager ? this.mcpManager.getQualifiedName(toolName) ?? toolName : toolName;
6023
- const { toolResult, resultContent } = this.spanManager ? await this.spanManager.withSpanAsync(
6024
- "axl.tool.call",
6025
- {
6026
- "axl.tool.name": traceName,
6027
- "axl.agent.name": agent2._name
6028
- },
6029
- async (span) => {
6030
- const r = await executeTool();
6031
- span.setAttribute("axl.tool.duration", Date.now() - toolStart);
6032
- const isError = r.toolResult && typeof r.toolResult === "object" && "error" in r.toolResult;
6033
- span.setAttribute("axl.tool.success", !isError);
6034
- if (isError)
6035
- span.setStatus(
6036
- "error",
6037
- r.toolResult.error
6038
- );
6039
- return r;
6040
- }
6041
- ) : await executeTool();
6042
- this.emitEvent({
6043
- type: "tool_call_end",
6044
- agent: agent2._name,
6045
- tool: traceName,
6046
- duration: Date.now() - toolStart,
6047
- data: { args: toolArgs, result: toolResult, callId: toolCall.id }
6048
- });
6049
- currentMessages.push({
6050
- role: "tool",
6051
- content: resultContent,
6052
- tool_call_id: toolCall.id
6053
- });
7000
+ await this.observeToolInvocation(
7001
+ agent2,
7002
+ toolCall,
7003
+ currentMessages,
7004
+ toolDefs.map((tool2) => tool2.function.name)
7005
+ );
6054
7006
  }
6055
7007
  continue;
6056
7008
  }
@@ -6170,7 +7122,7 @@ ${appendedSchemaText}`;
6170
7122
  pendingRetryReason = "schema";
6171
7123
  continue;
6172
7124
  }
6173
- const zodErr = schemaErr instanceof ZodError ? schemaErr : new ZodError([
7125
+ const zodErr = schemaErr instanceof ZodError2 ? schemaErr : new ZodError2([
6174
7126
  {
6175
7127
  code: "custom",
6176
7128
  path: [],
@@ -6187,6 +7139,7 @@ ${appendedSchemaText}`;
6187
7139
  metadata: this.metadata
6188
7140
  });
6189
7141
  } catch (err) {
7142
+ rethrowEventStreamOverflow(err);
6190
7143
  const reason = err instanceof Error ? err.message : String(err);
6191
7144
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
6192
7145
  }
@@ -6523,12 +7476,12 @@ ${summaryResponse.content}`
6523
7476
  * Execute a function with checkpoint-replay semantics.
6524
7477
  *
6525
7478
  * On first execution, runs `fn()`, saves the result under `name`, and
6526
- * returns it. On replay (resume after restart), returns the saved result
6527
- * without re-executing. This prevents duplicate side effects (double API
6528
- * calls, double refunds, etc.).
7479
+ * returns it. A later evaluation in the same explicit execution scope
7480
+ * returns the saved result without re-executing. Axl does not restart or
7481
+ * resume workflow continuations across processes.
6529
7482
  *
6530
7483
  * `name` must be a stable, caller-supplied identifier — the same call
6531
- * site MUST pass the same name across runs of the same execution for
7484
+ * site MUST pass the same name across evaluations of the same execution for
6532
7485
  * replay to work. Names are scoped to a single execution and must be
6533
7486
  * unique within it; reusing a name (e.g. inside a loop) gives
6534
7487
  * last-write-wins behavior. For loops, compose names from the iterator:
@@ -6651,6 +7604,7 @@ ${summaryResponse.content}`
6651
7604
  for (let i = 0; i < n; i++) {
6652
7605
  const index = i;
6653
7606
  const p = signalStorage.run(composedSignal, () => fn(index));
7607
+ this._registerBranchDrain(p);
6654
7608
  p.then((value) => {
6655
7609
  if (settled) return;
6656
7610
  results[index] = { ok: true, value };
@@ -6665,6 +7619,14 @@ ${summaryResponse.content}`
6665
7619
  reject(new QuorumNotMet(quorum, successCount, results));
6666
7620
  }
6667
7621
  }).catch((err) => {
7622
+ if (isEventStreamOverflowError(err)) {
7623
+ if (!settled) {
7624
+ settled = true;
7625
+ controller.abort();
7626
+ reject(err);
7627
+ }
7628
+ return;
7629
+ }
6668
7630
  if (settled) return;
6669
7631
  const isAbort = err instanceof DOMException && err.name === "AbortError";
6670
7632
  if (isAbort) {
@@ -6687,14 +7649,16 @@ ${summaryResponse.content}`
6687
7649
  }
6688
7650
  const parentSignal = this.currentSignal;
6689
7651
  const promises = Array.from({ length: n }, (_, i) => {
6690
- const run = () => fn(i).then((value) => ({ ok: true, value })).catch(
6691
- (err) => ({
7652
+ const run = () => fn(i).then((value) => ({ ok: true, value })).catch((err) => {
7653
+ rethrowEventStreamOverflow(err);
7654
+ return {
6692
7655
  ok: false,
6693
7656
  error: err instanceof Error ? err.message : String(err)
6694
- })
6695
- );
7657
+ };
7658
+ });
6696
7659
  return parentSignal ? signalStorage.run(parentSignal, run) : run();
6697
7660
  });
7661
+ for (const promise of promises) this._registerBranchDrain(promise);
6698
7662
  return Promise.all(promises);
6699
7663
  }
6700
7664
  // ── ctx.vote() ────────────────────────────────────────────────────────
@@ -6832,6 +7796,7 @@ ${summaryResponse.content}`
6832
7796
  try {
6833
7797
  validateResult = await options.validate(parsed, { metadata: this.metadata });
6834
7798
  } catch (err) {
7799
+ rethrowEventStreamOverflow(err);
6835
7800
  const reason = err instanceof Error ? err.message : String(err);
6836
7801
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
6837
7802
  }
@@ -6849,6 +7814,7 @@ ${summaryResponse.content}`
6849
7814
  emitVerifyOutcome(true, attempt + 1);
6850
7815
  return parsed;
6851
7816
  } catch (err) {
7817
+ rethrowEventStreamOverflow(err);
6852
7818
  if (err instanceof ValidationError) {
6853
7819
  lastRetry = {
6854
7820
  error: err.reason,
@@ -6871,12 +7837,12 @@ ${summaryResponse.content}`
6871
7837
  }
6872
7838
  continue;
6873
7839
  }
6874
- const errorMsg = err instanceof ZodError ? err.message : err instanceof Error ? err.message : String(err);
7840
+ const errorMsg = err instanceof ZodError2 ? err.message : err instanceof Error ? err.message : String(err);
6875
7841
  lastRetry = { error: errorMsg, output: rawOutput };
6876
7842
  if (attempt === maxRetries) {
6877
7843
  emitVerifyOutcome(false, attempt + 1, errorMsg);
6878
7844
  if (options?.fallback !== void 0) return options.fallback;
6879
- const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: errorMsg }]);
7845
+ const zodErr = err instanceof ZodError2 ? err : new ZodError2([{ code: "custom", path: [], message: errorMsg }]);
6880
7846
  throw new VerifyError(rawOutput, zodErr, maxRetries);
6881
7847
  }
6882
7848
  }
@@ -6885,7 +7851,7 @@ ${summaryResponse.content}`
6885
7851
  if (options?.fallback !== void 0) return options.fallback;
6886
7852
  throw new VerifyError(
6887
7853
  lastRetry?.output,
6888
- new ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
7854
+ new ZodError2([{ code: "custom", path: [], message: "Verify failed" }]),
6889
7855
  maxRetries
6890
7856
  );
6891
7857
  }
@@ -6915,6 +7881,7 @@ ${summaryResponse.content}`
6915
7881
  const unpriced = this.budgetContext.unpriced;
6916
7882
  return { value, budgetExceeded: exceeded, totalCost, unpriced };
6917
7883
  } catch (err) {
7884
+ rethrowEventStreamOverflow(err);
6918
7885
  if (this.budgetContext.exceeded) {
6919
7886
  return {
6920
7887
  value: null,
@@ -7015,7 +7982,8 @@ ${summaryResponse.content}`
7015
7982
  let remaining = fns.length;
7016
7983
  for (const fn of fns) {
7017
7984
  const p = signalStorage.run(composedSignal, fn);
7018
- p.then(async (value) => {
7985
+ this._registerBranchDrain(p);
7986
+ const continuation = p.then(async (value) => {
7019
7987
  if (settled) return;
7020
7988
  if (schema) {
7021
7989
  const parsed = schema.safeParse(value);
@@ -7045,6 +8013,7 @@ ${summaryResponse.content}`
7045
8013
  return;
7046
8014
  }
7047
8015
  } catch (err) {
8016
+ rethrowEventStreamOverflow(err);
7048
8017
  remaining--;
7049
8018
  lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
7050
8019
  if (remaining === 0 && !settled) {
@@ -7064,6 +8033,14 @@ ${summaryResponse.content}`
7064
8033
  controller.abort();
7065
8034
  resolve2(value);
7066
8035
  }).catch((err) => {
8036
+ if (isEventStreamOverflowError(err)) {
8037
+ if (!settled) {
8038
+ settled = true;
8039
+ controller.abort();
8040
+ reject(err);
8041
+ }
8042
+ throw err;
8043
+ }
7067
8044
  if (settled) return;
7068
8045
  if (err instanceof DOMException && err.name === "AbortError") {
7069
8046
  remaining--;
@@ -7080,6 +8057,7 @@ ${summaryResponse.content}`
7080
8057
  reject(lastError);
7081
8058
  }
7082
8059
  });
8060
+ this._registerBranchDrain(continuation);
7083
8061
  }
7084
8062
  });
7085
8063
  }
@@ -7118,9 +8096,19 @@ ${summaryResponse.content}`
7118
8096
  const idx = nextIndex++;
7119
8097
  try {
7120
8098
  const value = mapSignal ? await signalStorage.run(mapSignal, () => fn(items[idx], idx)) : await fn(items[idx], idx);
8099
+ if (settled) return;
7121
8100
  results[idx] = { ok: true, value };
7122
8101
  successCount++;
7123
8102
  } catch (err) {
8103
+ if (isEventStreamOverflowError(err)) {
8104
+ settled = true;
8105
+ controller?.abort();
8106
+ reject(err);
8107
+ throw err;
8108
+ }
8109
+ if (settled) {
8110
+ return;
8111
+ }
7124
8112
  if (err instanceof DOMException && err.name === "AbortError" && controller?.signal.aborted) {
7125
8113
  completedCount++;
7126
8114
  return;
@@ -7146,7 +8134,9 @@ ${summaryResponse.content}`
7146
8134
  };
7147
8135
  const workers = Math.min(concurrency, items.length);
7148
8136
  for (let i = 0; i < workers; i++) {
7149
- runNext().catch((err) => {
8137
+ const worker = runNext();
8138
+ this._registerBranchDrain(worker);
8139
+ worker.catch((err) => {
7150
8140
  if (!settled) reject(err);
7151
8141
  });
7152
8142
  }
@@ -7172,12 +8162,44 @@ ${summaryResponse.content}`
7172
8162
  return this._awaitHumanImpl(options);
7173
8163
  }
7174
8164
  async _awaitHumanImpl(options) {
8165
+ const branchSignal = this.currentSignal;
8166
+ const makeAbortError = (reason) => {
8167
+ if (typeof DOMException !== "undefined") {
8168
+ return new DOMException(
8169
+ typeof reason === "string" ? reason : "awaitHuman aborted",
8170
+ "AbortError"
8171
+ );
8172
+ }
8173
+ const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
8174
+ err.name = "AbortError";
8175
+ return err;
8176
+ };
8177
+ const awaitWithAbort = async (promise, signal2 = branchSignal) => {
8178
+ if (!signal2) return promise;
8179
+ if (signal2.aborted) throw makeAbortError(signal2.reason);
8180
+ let onAbort;
8181
+ try {
8182
+ return await Promise.race([
8183
+ promise,
8184
+ new Promise((_, reject) => {
8185
+ onAbort = () => reject(makeAbortError(signal2.reason));
8186
+ signal2.addEventListener("abort", onAbort, { once: true });
8187
+ })
8188
+ ]);
8189
+ } finally {
8190
+ if (onAbort) signal2.removeEventListener("abort", onAbort);
8191
+ }
8192
+ };
7175
8193
  if (this.awaitHumanHandler) {
8194
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
7176
8195
  this.emitEvent({
7177
8196
  type: "await_human",
7178
8197
  data: { channel: options.channel, prompt: options.prompt }
7179
8198
  });
7180
- const decision2 = await this.awaitHumanHandler(options);
8199
+ if (branchSignal?.aborted) throw makeAbortError(branchSignal.reason);
8200
+ const decision2 = parseHumanDecision(
8201
+ await awaitWithAbort(Promise.resolve(this.awaitHumanHandler(options)))
8202
+ );
7181
8203
  this.emitEvent({
7182
8204
  type: "await_human_resolved",
7183
8205
  data: { channel: options.channel, decision: decision2 }
@@ -7189,68 +8211,126 @@ ${summaryResponse.content}`
7189
8211
  "Tool requires approval but no approval handler is configured. Provide awaitHumanHandler to createContext() or use runtime.execute() with workflow infrastructure."
7190
8212
  );
7191
8213
  }
7192
- if (this.stateStore) {
7193
- await this.stateStore.savePendingDecision(this.executionId, {
7194
- executionId: this.executionId,
7195
- channel: options.channel,
7196
- prompt: options.prompt,
7197
- metadata: options.metadata,
7198
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7199
- });
7200
- await this.stateStore.saveExecutionState(this.executionId, {
7201
- workflow: this.workflowName ?? "unknown",
7202
- input: this.input,
7203
- step: this.stepRefRoot.value,
7204
- status: "waiting",
7205
- metadata: {
7206
- ...this.metadata,
7207
- awaitHumanChannel: options.channel,
7208
- awaitHumanPrompt: options.prompt
7209
- }
7210
- });
7211
- }
7212
- this.emitEvent({
7213
- type: "await_human",
7214
- data: { channel: options.channel, prompt: options.prompt }
8214
+ const existingClaim = this.awaitHumanClaims.get(this.executionId);
8215
+ if (existingClaim) {
8216
+ const error = new AxlError(
8217
+ "CONCURRENT_HUMAN_DECISION_UNSUPPORTED",
8218
+ `Execution "${this.executionId}" already has an active human decision. Concurrent awaitHuman calls require request-scoped decision identifiers.`
8219
+ );
8220
+ existingClaim.abort(error);
8221
+ throw error;
8222
+ }
8223
+ const claimController = new AbortController();
8224
+ this.awaitHumanClaims.set(this.executionId, claimController);
8225
+ const signal = branchSignal ? AbortSignal.any([branchSignal, claimController.signal]) : claimController.signal;
8226
+ let saveAttempted = false;
8227
+ let markReady;
8228
+ const ready = new Promise((resolve2) => {
8229
+ markReady = resolve2;
7215
8230
  });
7216
- const makeAbortError = (reason) => {
7217
- if (typeof DOMException !== "undefined") {
7218
- return new DOMException(
7219
- typeof reason === "string" ? reason : "awaitHuman aborted",
7220
- "AbortError"
7221
- );
7222
- }
7223
- const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
7224
- err.name = "AbortError";
7225
- return err;
8231
+ let markCleanupFinished;
8232
+ const cleanupFinished = new Promise((resolve2) => {
8233
+ markCleanupFinished = resolve2;
8234
+ });
8235
+ let resolveDecision;
8236
+ let rejectDecision;
8237
+ const decisionPromise = new Promise((resolve2, reject) => {
8238
+ resolveDecision = resolve2;
8239
+ rejectDecision = reject;
8240
+ });
8241
+ void decisionPromise.catch(() => {
8242
+ });
8243
+ const registeredResolver = {
8244
+ resolve: resolveDecision,
8245
+ ready,
8246
+ state: "preparing",
8247
+ cancelled: false,
8248
+ cleanupFinished
7226
8249
  };
7227
- const decision = await new Promise((resolve2, reject) => {
7228
- if (this.signal?.aborted) {
7229
- reject(makeAbortError(this.signal.reason));
7230
- return;
8250
+ this.pendingDecisions.set(this.executionId, registeredResolver);
8251
+ const abortListener = () => {
8252
+ registeredResolver.cancelled = true;
8253
+ markReady();
8254
+ rejectDecision(makeAbortError(signal.reason));
8255
+ };
8256
+ signal.addEventListener("abort", abortListener, { once: true });
8257
+ let decision;
8258
+ try {
8259
+ if (signal.aborted) throw makeAbortError(signal.reason);
8260
+ if (this.stateStore) {
8261
+ saveAttempted = true;
8262
+ await this.stateStore.savePendingDecision(this.executionId, {
8263
+ executionId: this.executionId,
8264
+ channel: options.channel,
8265
+ prompt: options.prompt,
8266
+ metadata: options.metadata,
8267
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8268
+ });
7231
8269
  }
7232
- this.pendingDecisions.set(this.executionId, resolve2);
7233
- const onAbort = () => {
7234
- const current = this.pendingDecisions.get(this.executionId);
7235
- if (current === resolve2) {
7236
- this.pendingDecisions.delete(this.executionId);
8270
+ if (signal.aborted) throw makeAbortError(signal.reason);
8271
+ this.emitEvent({
8272
+ type: "await_human",
8273
+ data: { channel: options.channel, prompt: options.prompt }
8274
+ });
8275
+ registeredResolver.state = "pending";
8276
+ markReady();
8277
+ decision = await decisionPromise;
8278
+ } catch (error) {
8279
+ if (registeredResolver.state === "preparing") {
8280
+ registeredResolver.state = "failed";
8281
+ markReady();
8282
+ }
8283
+ let needsCompensation = saveAttempted;
8284
+ const inFlightResolution = registeredResolver.resolution;
8285
+ if (registeredResolver.state === "resolving" && inFlightResolution) {
8286
+ needsCompensation = !await inFlightResolution;
8287
+ } else if (registeredResolver.state === "resolved") {
8288
+ needsCompensation = false;
8289
+ }
8290
+ if (this.stateStore && needsCompensation) {
8291
+ try {
8292
+ await this.stateStore.resolveDecision(this.executionId, {
8293
+ approved: false,
8294
+ reason: "Execution aborted while awaiting approval"
8295
+ });
8296
+ } catch (cleanupError) {
8297
+ if (error instanceof Error && Object.isExtensible(error)) {
8298
+ Object.defineProperty(error, "cleanupError", {
8299
+ configurable: true,
8300
+ enumerable: false,
8301
+ value: cleanupError
8302
+ });
8303
+ }
8304
+ try {
8305
+ this._onDecisionCleanupFailed?.({
8306
+ executionId: this.executionId,
8307
+ ...this.workflowName ? { workflow: this.workflowName } : {},
8308
+ operation: "resolveDecision_compensation",
8309
+ error: cleanupError
8310
+ });
8311
+ } catch (observerError) {
8312
+ console.error(
8313
+ "[axl] decision_cleanup_failed listener threw; workflow outcome unchanged:",
8314
+ observerError instanceof Error ? observerError.message : String(observerError)
8315
+ );
8316
+ }
7237
8317
  }
7238
- reject(makeAbortError(this.signal?.reason));
7239
- };
7240
- this.signal?.addEventListener("abort", onAbort, { once: true });
7241
- });
8318
+ }
8319
+ throw error;
8320
+ } finally {
8321
+ signal.removeEventListener("abort", abortListener);
8322
+ if (this.pendingDecisions.get(this.executionId) === registeredResolver) {
8323
+ this.pendingDecisions.delete(this.executionId);
8324
+ }
8325
+ if (this.awaitHumanClaims.get(this.executionId) === claimController) {
8326
+ this.awaitHumanClaims.delete(this.executionId);
8327
+ }
8328
+ markCleanupFinished();
8329
+ }
7242
8330
  this.emitEvent({
7243
8331
  type: "await_human_resolved",
7244
8332
  data: { channel: options.channel, decision }
7245
8333
  });
7246
- if (this.stateStore) {
7247
- await this.stateStore.saveExecutionState(this.executionId, {
7248
- workflow: this.workflowName ?? "unknown",
7249
- input: this.input,
7250
- step: this.stepRefRoot.value,
7251
- status: "running"
7252
- });
7253
- }
7254
8334
  return decision;
7255
8335
  }
7256
8336
  /**
@@ -7302,8 +8382,8 @@ ${summaryResponse.content}`
7302
8382
  // Internal: the runtime is the only caller, user workflows never call these.
7303
8383
  /** @internal — idempotent; no-ops on second+ call within one ctx. */
7304
8384
  _emitWorkflowStart(input) {
7305
- if (this._workflowStartEmitted) return;
7306
- this._workflowStartEmitted = true;
8385
+ if (this.workflowLifecycleState.startEmitted) return;
8386
+ this.workflowLifecycleState.startEmitted = true;
7307
8387
  this.emitEvent({
7308
8388
  type: "workflow_start",
7309
8389
  workflow: this.workflowName,
@@ -7319,8 +8399,8 @@ ${summaryResponse.content}`
7319
8399
  * status. First-wins semantics: the completed event stands, the
7320
8400
  * inner side-effect failure propagates as a normal thrown error. */
7321
8401
  _emitWorkflowEnd(info) {
7322
- if (this._workflowEndEmitted) return;
7323
- this._workflowEndEmitted = true;
8402
+ if (this.workflowLifecycleState.endEmitted) return;
8403
+ this.workflowLifecycleState.endEmitted = true;
7324
8404
  this.emitEvent({
7325
8405
  type: "workflow_end",
7326
8406
  workflow: this.workflowName,
@@ -7330,7 +8410,8 @@ ${summaryResponse.content}`
7330
8410
  duration: info.duration,
7331
8411
  ...info.result !== void 0 ? { result: info.result } : {},
7332
8412
  ...info.error !== void 0 ? { error: info.error } : {},
7333
- ...info.aborted ? { aborted: true } : {}
8413
+ ...info.aborted ? { aborted: true } : {},
8414
+ ...info.observation ? { observation: info.observation } : {}
7334
8415
  }
7335
8416
  });
7336
8417
  }
@@ -7619,6 +8700,7 @@ ${summaryResponse.content}`
7619
8700
  * consumer sites via the `type` discriminator.
7620
8701
  */
7621
8702
  emitEvent(partial) {
8703
+ if (this.workflowLifecycleState.endEmitted && partial.type !== "workflow_end") return;
7622
8704
  const data = partial.data;
7623
8705
  const frame = askStorage.getStore();
7624
8706
  const step = (frame?.stepRef ?? this.stepRefRoot).value++;
@@ -7644,6 +8726,7 @@ ${summaryResponse.content}`
7644
8726
  }
7645
8727
  }
7646
8728
  const event = {
8729
+ schemaVersion: 2,
7647
8730
  executionId: this.executionId,
7648
8731
  step,
7649
8732
  timestamp: Date.now(),
@@ -7664,13 +8747,13 @@ ${summaryResponse.content}`
7664
8747
  ...partial,
7665
8748
  data
7666
8749
  };
7667
- const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
8750
+ const finalEvent = this.config.trace?.redact ? redactHistoricalEvent(event) : event;
7668
8751
  let overflowErr;
7669
8752
  if (this.onTrace) {
7670
8753
  try {
7671
8754
  this.onTrace(finalEvent);
7672
8755
  } catch (err) {
7673
- if (err instanceof EventStreamOverflowError) {
8756
+ if (isEventStreamOverflowError(err)) {
7674
8757
  overflowErr = err;
7675
8758
  } else {
7676
8759
  console.error(
@@ -7685,7 +8768,7 @@ ${summaryResponse.content}`
7685
8768
  try {
7686
8769
  bus._push(finalEvent);
7687
8770
  } catch (err) {
7688
- if (err instanceof EventStreamOverflowError) {
8771
+ if (isEventStreamOverflowError(err)) {
7689
8772
  if (!overflowErr) {
7690
8773
  overflowErr = err;
7691
8774
  } else {
@@ -7872,26 +8955,31 @@ var MemoryStore = class {
7872
8955
  }
7873
8956
  // ── Execution History ──────────────────────────────────────────────
7874
8957
  async saveExecution(execution) {
7875
- this.executionHistory.set(execution.executionId, structuredClone(execution));
8958
+ const normalized = normalizeStoredExecution({
8959
+ ...execution,
8960
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
8961
+ });
8962
+ this.executionHistory.set(execution.executionId, structuredClone(normalized));
7876
8963
  }
7877
8964
  async getExecution(executionId) {
7878
8965
  const exec = this.executionHistory.get(executionId);
7879
- return exec ? structuredClone(exec) : null;
8966
+ return exec ? normalizeStoredExecution(structuredClone(exec)) : null;
7880
8967
  }
7881
8968
  async listExecutions(limit) {
7882
8969
  const sorted = [...this.executionHistory.values()].sort((a, b) => b.startedAt - a.startedAt);
7883
8970
  const result = limit ? sorted.slice(0, limit) : sorted;
7884
- return result.map((e) => structuredClone(e));
8971
+ return result.map((e) => normalizeStoredExecution(structuredClone(e)));
7885
8972
  }
7886
8973
  async deleteExecution(executionId) {
7887
- const removed = this.executionHistory.delete(executionId);
7888
- this.checkpoints.delete(executionId);
7889
- this.executionStates.delete(executionId);
7890
- this.streamingEvents.delete(executionId);
7891
- if (this.decisions.delete(executionId)) {
8974
+ const removedHistory = this.executionHistory.delete(executionId);
8975
+ const removedCheckpoints = this.checkpoints.delete(executionId);
8976
+ const removedState = this.executionStates.delete(executionId);
8977
+ const removedStreaming = this.streamingEvents.delete(executionId);
8978
+ const removedDecision = this.decisions.delete(executionId);
8979
+ if (removedDecision) {
7892
8980
  this.persistAwaitHumanState();
7893
8981
  }
7894
- return removed;
8982
+ return removedHistory || removedCheckpoints || removedState || removedStreaming || removedDecision;
7895
8983
  }
7896
8984
  // ── Eval History ──────────────────────────────────────────────────
7897
8985
  async saveEvalResult(entry) {
@@ -7997,16 +9085,23 @@ function rowToExecutionInfo(row) {
7997
9085
  startedAt: row.started_at,
7998
9086
  completedAt: row.completed_at ?? void 0,
7999
9087
  duration: row.duration,
8000
- error: row.error ?? void 0,
8001
- events: safeJsonParse(row.events) ?? []
9088
+ events: safeJsonParse(row.events) ?? [],
9089
+ ...row.event_schema_version != null ? { eventSchemaVersion: row.event_schema_version } : {}
8002
9090
  };
9091
+ if (row.error != null) info.error = row.error;
8003
9092
  if (row.metadata != null) {
8004
9093
  const parsed = safeJsonParse(row.metadata);
8005
9094
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8006
9095
  info.metadata = parsed;
8007
9096
  }
8008
9097
  }
8009
- return info;
9098
+ if (row.observation != null) {
9099
+ const parsed = safeJsonParse(row.observation);
9100
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9101
+ info.observation = parsed;
9102
+ }
9103
+ }
9104
+ return normalizeStoredExecution(info);
8010
9105
  }
8011
9106
  var SQLiteStore = class {
8012
9107
  db;
@@ -8046,6 +9141,10 @@ var SQLiteStore = class {
8046
9141
  * column (TEXT, nullable, JSON-serialized) so caller-supplied
8047
9142
  * `ExecuteOptions.metadata` round-trips through history. Existing
8048
9143
  * rows get `NULL`, deserialized as `metadata: undefined`.
9144
+ * - v4 (event schema): add `execution_history.event_schema_version`.
9145
+ * Existing rows remain `NULL`, the documented v1 sentinel.
9146
+ * - v5 (observation completeness): add `execution_history.observation`.
9147
+ * Existing rows remain `NULL`, meaning they predate the contract.
8049
9148
  *
8050
9149
  * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
8051
9150
  * IF NOT EXISTS` runs against the post-migration column name. Fresh
@@ -8063,7 +9162,7 @@ var SQLiteStore = class {
8063
9162
  * transactional re-read costs nothing.
8064
9163
  */
8065
9164
  migrate() {
8066
- const TARGET_VERSION = 3;
9165
+ const TARGET_VERSION = 5;
8067
9166
  const current = this.db.pragma("user_version", { simple: true });
8068
9167
  if (current >= TARGET_VERSION) return;
8069
9168
  this.db.exec("BEGIN IMMEDIATE");
@@ -8091,6 +9190,18 @@ var SQLiteStore = class {
8091
9190
  this.db.exec("ALTER TABLE execution_history ADD COLUMN metadata TEXT");
8092
9191
  }
8093
9192
  }
9193
+ if (committed < 4) {
9194
+ const cols = this.db.pragma("table_info(execution_history)");
9195
+ if (cols.length > 0 && !cols.some((c) => c.name === "event_schema_version")) {
9196
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN event_schema_version INTEGER");
9197
+ }
9198
+ }
9199
+ if (committed < 5) {
9200
+ const cols = this.db.pragma("table_info(execution_history)");
9201
+ if (cols.length > 0 && !cols.some((c) => c.name === "observation")) {
9202
+ this.db.exec("ALTER TABLE execution_history ADD COLUMN observation TEXT");
9203
+ }
9204
+ }
8094
9205
  this.db.pragma(`user_version = ${TARGET_VERSION}`);
8095
9206
  this.db.exec("COMMIT");
8096
9207
  } catch (err) {
@@ -8154,7 +9265,9 @@ var SQLiteStore = class {
8154
9265
  duration INTEGER NOT NULL DEFAULT 0,
8155
9266
  error TEXT,
8156
9267
  events TEXT NOT NULL,
8157
- metadata TEXT
9268
+ metadata TEXT,
9269
+ event_schema_version INTEGER,
9270
+ observation TEXT
8158
9271
  );
8159
9272
 
8160
9273
  CREATE TABLE IF NOT EXISTS eval_history (
@@ -8239,7 +9352,7 @@ var SQLiteStore = class {
8239
9352
  const stmt = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?");
8240
9353
  stmt.run(executionId);
8241
9354
  }
8242
- // ── Execution State (for awaitHuman suspend/resume) ────────────────────
9355
+ // ── Legacy application-managed execution state ────────────────────────
8243
9356
  async saveExecutionState(executionId, state) {
8244
9357
  const stmt = this.db.prepare(
8245
9358
  "INSERT OR REPLACE INTO execution_state (execution_id, workflow, input, step, status, metadata) VALUES (?, ?, ?, ?, ?, ?)"
@@ -8274,19 +9387,25 @@ var SQLiteStore = class {
8274
9387
  }
8275
9388
  // ── Execution History ────────────────────────────────────────────────────
8276
9389
  async saveExecution(execution) {
9390
+ const normalized = normalizeStoredExecution({
9391
+ ...execution,
9392
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
9393
+ });
8277
9394
  this.db.prepare(
8278
- "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
9395
+ "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events, metadata, event_schema_version, observation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8279
9396
  ).run(
8280
- execution.executionId,
8281
- execution.workflow,
8282
- execution.status,
8283
- execution.totalCost,
8284
- execution.startedAt,
8285
- execution.completedAt ?? null,
8286
- execution.duration,
8287
- execution.error ?? null,
8288
- JSON.stringify(execution.events),
8289
- execution.metadata !== void 0 ? JSON.stringify(execution.metadata) : null
9397
+ normalized.executionId,
9398
+ normalized.workflow,
9399
+ normalized.status,
9400
+ normalized.totalCost,
9401
+ normalized.startedAt,
9402
+ normalized.completedAt ?? null,
9403
+ normalized.duration,
9404
+ normalized.error ?? null,
9405
+ JSON.stringify(normalized.events),
9406
+ normalized.metadata !== void 0 ? JSON.stringify(normalized.metadata) : null,
9407
+ normalized.eventSchemaVersion,
9408
+ normalized.observation !== void 0 ? JSON.stringify(normalized.observation) : null
8290
9409
  );
8291
9410
  }
8292
9411
  async getExecution(executionId) {
@@ -8300,11 +9419,11 @@ var SQLiteStore = class {
8300
9419
  }
8301
9420
  async deleteExecution(executionId) {
8302
9421
  const tx = this.db.transaction(() => {
8303
- const result = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
8304
- this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
8305
- this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
8306
- this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
8307
- return result.changes > 0;
9422
+ const historyResult = this.db.prepare("DELETE FROM execution_history WHERE execution_id = ?").run(executionId);
9423
+ const checkpointsResult = this.db.prepare("DELETE FROM checkpoints WHERE execution_id = ?").run(executionId);
9424
+ const stateResult = this.db.prepare("DELETE FROM execution_state WHERE execution_id = ?").run(executionId);
9425
+ const decisionsResult = this.db.prepare("DELETE FROM decisions WHERE execution_id = ?").run(executionId);
9426
+ return historyResult.changes + checkpointsResult.changes + stateResult.changes + decisionsResult.changes > 0;
8308
9427
  });
8309
9428
  return tx();
8310
9429
  }
@@ -8630,6 +9749,11 @@ var AxlStream = class extends Readable {
8630
9749
  get lifecycle() {
8631
9750
  return this.events.lifecycle;
8632
9751
  }
9752
+ /** Completeness of this stream's iterator queue. Check after consumption
9753
+ * before treating an unmatched lifecycle event as a producer defect. */
9754
+ get observationStatus() {
9755
+ return this.events.observationStatus;
9756
+ }
8633
9757
  /** Coalescing iterator over `partial_object` events — yields the latest
8634
9758
  * object payload per `askId`. Designed for streaming-structured-output
8635
9759
  * UIs: when the producer outpaces the consumer, intermediate snapshots
@@ -8728,6 +9852,7 @@ var AxlStream = class extends Readable {
8728
9852
  if (this.events.isFinished) return;
8729
9853
  this.result = result;
8730
9854
  const doneEvent = {
9855
+ schemaVersion: 2,
8731
9856
  type: "done",
8732
9857
  executionId,
8733
9858
  step: Number.MAX_SAFE_INTEGER,
@@ -8746,6 +9871,7 @@ var AxlStream = class extends Readable {
8746
9871
  _error(error, executionId) {
8747
9872
  if (this.events.isFinished) return;
8748
9873
  const errorEvent = {
9874
+ schemaVersion: 2,
8749
9875
  type: "error",
8750
9876
  executionId,
8751
9877
  step: Number.MAX_SAFE_INTEGER,
@@ -9324,7 +10450,7 @@ async function createSpanManager(config) {
9324
10450
  if (!config?.enabled) {
9325
10451
  return new NoopSpanManager();
9326
10452
  }
9327
- const { OTelSpanManager } = await import("./span-manager-3IKXXUTZ.js");
10453
+ const { OTelSpanManager } = await import("./span-manager-74ZCGELO.js");
9328
10454
  return OTelSpanManager.create(config);
9329
10455
  }
9330
10456
 
@@ -9346,11 +10472,7 @@ var STREAMING_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
9346
10472
  "partial_object",
9347
10473
  "string_delta"
9348
10474
  ]);
9349
- var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set([
9350
- "sessionId",
9351
- "sessionHistory",
9352
- "resumeMode"
9353
- ]);
10475
+ var INTERNAL_METADATA_KEYS = /* @__PURE__ */ new Set(["sessionId", "sessionHistory"]);
9354
10476
  function liftPersistedMetadata(metadata) {
9355
10477
  if (!metadata) return void 0;
9356
10478
  const filtered = {};
@@ -9468,7 +10590,7 @@ var StreamingFlusher = class {
9468
10590
  await Promise.allSettled(pendings);
9469
10591
  }
9470
10592
  };
9471
- function isAbortError2(err) {
10593
+ function isAbortError4(err) {
9472
10594
  return typeof err === "object" && err !== null && err.name === "AbortError";
9473
10595
  }
9474
10596
  function pushEventBounded(execInfo, event, cap) {
@@ -9480,7 +10602,18 @@ function pushEventBounded(execInfo, event, cap) {
9480
10602
  const last = execInfo.events[execInfo.events.length - 1];
9481
10603
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9482
10604
  if (!alreadyTruncated) {
10605
+ if (execInfo.observation?.complete !== false) {
10606
+ execInfo.observation = {
10607
+ complete: false,
10608
+ reason: "persistence_truncated",
10609
+ maxEvents: cap
10610
+ };
10611
+ }
10612
+ if (event.type === "workflow_end") {
10613
+ event.data.observation = execInfo.observation;
10614
+ }
9483
10615
  execInfo.events[execInfo.events.length - 1] = {
10616
+ schemaVersion: 2,
9484
10617
  type: "log",
9485
10618
  executionId: event.executionId,
9486
10619
  step: event.step,
@@ -9509,7 +10642,9 @@ function boundRecoveredEvents(events, cap) {
9509
10642
  const last = truncated[truncated.length - 1];
9510
10643
  const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
9511
10644
  if (!alreadyTruncated) {
10645
+ const version = getEventSchemaVersion(events[0] ?? {});
9512
10646
  truncated[truncated.length - 1] = {
10647
+ ...version === 2 ? { schemaVersion: 2 } : {},
9513
10648
  type: "log",
9514
10649
  executionId: events[0]?.executionId ?? "",
9515
10650
  step: last?.step ?? 0,
@@ -9523,6 +10658,16 @@ function boundRecoveredEvents(events, cap) {
9523
10658
  }
9524
10659
  return truncated;
9525
10660
  }
10661
+ var REMOVED_OBSERVATION_CALLBACKS = ["onToken", "onToolCall", "onAgentStart"];
10662
+ function assertNoRemovedObservationCallbacks(options) {
10663
+ if (options === null || typeof options !== "object") return;
10664
+ const removed = REMOVED_OBSERVATION_CALLBACKS.filter((key) => Object.hasOwn(options, key));
10665
+ if (removed.length === 0) return;
10666
+ throw new AxlError(
10667
+ "INVALID_CONFIG",
10668
+ `runtime.createContext() no longer accepts ${removed.join(", ")}. Observe ctx.events or use runtime.stream() instead. See docs/migration/stream-first-observation.md.`
10669
+ );
10670
+ }
9526
10671
  var costScopeStorage = new AsyncLocalStorage2();
9527
10672
  function forwardAbortSignal(external, internal) {
9528
10673
  if (!external) return () => {
@@ -9684,9 +10829,7 @@ var AxlRuntime = class extends EventEmitter2 {
9684
10829
  * → parse output → emit `workflow_end` → delete checkpoints → persist.
9685
10830
  *
9686
10831
  * Shared between `execute()` and `stream()`. Centralizes the
9687
- * "start iff end" pairing invariant so a future emit path
9688
- * (e.g., `resumeExecution`) can inherit correct behavior by routing
9689
- * through this helper. Also factors out the AbortError detection.
10832
+ * "start iff end" pairing invariant and AbortError detection.
9690
10833
  *
9691
10834
  * Side effects on `execInfo`: sets `status` / `completedAt` /
9692
10835
  * `duration` / `result` (or `error`) before emitting `workflow_end`.
@@ -9710,18 +10853,23 @@ var AxlRuntime = class extends EventEmitter2 {
9710
10853
  */
9711
10854
  async runWorkflowBody(args) {
9712
10855
  const { workflow: workflow2, ctx, execInfo, validated, span, cleanupAbortForwarder } = args;
10856
+ let observation = execInfo.observation ?? { complete: true };
9713
10857
  try {
9714
10858
  ctx._emitWorkflowStart(validated);
9715
10859
  const raw = await workflow2.handler(ctx);
10860
+ const drainedObservation = await ctx._drainBranchWork();
10861
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
9716
10862
  const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
9717
10863
  execInfo.status = "completed";
9718
10864
  execInfo.completedAt = Date.now();
9719
10865
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9720
10866
  execInfo.result = result;
10867
+ execInfo.observation = observation;
9721
10868
  ctx._emitWorkflowEnd({
9722
10869
  status: "completed",
9723
10870
  duration: execInfo.duration,
9724
- result
10871
+ result,
10872
+ observation
9725
10873
  });
9726
10874
  if (this.stateStore.deleteCheckpoints) {
9727
10875
  await this.stateStore.deleteCheckpoints(execInfo.executionId);
@@ -9731,19 +10879,28 @@ var AxlRuntime = class extends EventEmitter2 {
9731
10879
  this.persistExecution(execInfo);
9732
10880
  return result;
9733
10881
  } catch (err) {
9734
- const aborted = isAbortError2(err);
10882
+ let terminalError = err;
10883
+ try {
10884
+ const drainedObservation = await ctx._drainBranchWork();
10885
+ observation = drainedObservation.complete === false ? drainedObservation : execInfo.observation ?? drainedObservation;
10886
+ } catch (drainErr) {
10887
+ terminalError = drainErr instanceof Error ? preserveErrorCause(drainErr, terminalError) : drainErr;
10888
+ }
10889
+ const aborted = isAbortError4(terminalError);
9735
10890
  execInfo.status = "failed";
9736
10891
  execInfo.completedAt = Date.now();
9737
10892
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
9738
- execInfo.error = err instanceof Error ? err.message : String(err);
10893
+ execInfo.error = terminalError instanceof Error ? terminalError.message : String(terminalError);
10894
+ execInfo.observation = observation;
9739
10895
  ctx._emitWorkflowEnd({
9740
10896
  status: "failed",
9741
10897
  duration: execInfo.duration,
9742
10898
  error: execInfo.error,
9743
- ...aborted ? { aborted: true } : {}
10899
+ ...aborted ? { aborted: true } : {},
10900
+ observation
9744
10901
  });
9745
10902
  this.persistExecution(execInfo);
9746
- throw err;
10903
+ throw terminalError;
9747
10904
  } finally {
9748
10905
  this.abortControllers.delete(execInfo.executionId);
9749
10906
  cleanupAbortForwarder?.();
@@ -9931,13 +11088,13 @@ var AxlRuntime = class extends EventEmitter2 {
9931
11088
  return result;
9932
11089
  }
9933
11090
  /** Get all execution info (running + completed + historical). */
9934
- /** Normalize an `ExecutionInfo` loaded from a `StateStore` so the
9935
- * `events: AxlEvent[]` type contract holds for downstream consumers
11091
+ /** Normalize an execution loaded from a `StateStore` so the
11092
+ * historical event union holds for downstream consumers
9936
11093
  * (Studio aggregators, REST routes, redaction). Custom or legacy stores
9937
11094
  * may persist rows without `events` (or with a non-array value) — without
9938
11095
  * this guard, a single malformed row crashes every iterator over
9939
- * `exec.events` and takes down dependent features. Mutates the row
9940
- * in place; logs at most one warning per `executionId`. */
11096
+ * `exec.events` and takes down dependent features. Returns a normalized
11097
+ * copy; logs at most one warning per `executionId`. */
9941
11098
  normalizeStoredExecution(exec) {
9942
11099
  if (!Array.isArray(exec.events)) {
9943
11100
  if (!this.warnedMalformedExecutions.has(exec.executionId)) {
@@ -9946,9 +11103,9 @@ var AxlRuntime = class extends EventEmitter2 {
9946
11103
  `[axl] StateStore returned execution ${exec.executionId} with non-array events (got ${exec.events === null ? "null" : typeof exec.events}); coercing to []`
9947
11104
  );
9948
11105
  }
9949
- exec.events = [];
11106
+ return normalizeStoredExecution({ ...exec, events: [] });
9950
11107
  }
9951
- return exec;
11108
+ return normalizeStoredExecution(exec);
9952
11109
  }
9953
11110
  async getExecutions() {
9954
11111
  if (!this.historicalExecutionsLoadPromise && this.stateStore.listExecutions) {
@@ -9958,8 +11115,9 @@ var AxlRuntime = class extends EventEmitter2 {
9958
11115
  this.historicalExecutions.set(exec.executionId, this.normalizeStoredExecution(exec));
9959
11116
  }
9960
11117
  }
9961
- }).catch(() => {
11118
+ }).catch((error) => {
9962
11119
  this.historicalExecutionsLoadPromise = null;
11120
+ throw error;
9963
11121
  });
9964
11122
  }
9965
11123
  if (this.historicalExecutionsLoadPromise) {
@@ -9977,6 +11135,7 @@ var AxlRuntime = class extends EventEmitter2 {
9977
11135
  * and automatically emits trace events and tracks cost.
9978
11136
  */
9979
11137
  createContext(options) {
11138
+ assertNoRemovedObservationCallbacks(options);
9980
11139
  const executionId = randomUUID3();
9981
11140
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
9982
11141
  this.registerWithCostScope(executionId);
@@ -9992,10 +11151,8 @@ var AxlRuntime = class extends EventEmitter2 {
9992
11151
  memoryManager: this.memoryManager,
9993
11152
  sessionHistory: options?.sessionHistory,
9994
11153
  signal: options?.signal,
9995
- onToken: options?.onToken,
9996
- onToolCall: options?.onToolCall,
9997
- onAgentStart: options?.onAgentStart,
9998
11154
  awaitHumanHandler: options?.awaitHumanHandler,
11155
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
9999
11156
  eventStreamOptions: options?.events,
10000
11157
  onTrace: (event) => {
10001
11158
  this.emit("trace", event);
@@ -10029,6 +11186,7 @@ var AxlRuntime = class extends EventEmitter2 {
10029
11186
  );
10030
11187
  }
10031
11188
  const validated = workflow2.inputSchema.parse(input);
11189
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10032
11190
  const executionId = randomUUID3();
10033
11191
  const controller = new AbortController();
10034
11192
  this.abortControllers.set(executionId, controller);
@@ -10041,11 +11199,13 @@ var AxlRuntime = class extends EventEmitter2 {
10041
11199
  executionId,
10042
11200
  workflow: name,
10043
11201
  status: "running",
11202
+ eventSchemaVersion: 2,
10044
11203
  events: [],
10045
11204
  totalCost: 0,
10046
11205
  unpriced: false,
10047
11206
  startedAt: Date.now(),
10048
11207
  duration: 0,
11208
+ observation: { complete: true },
10049
11209
  metadata: liftPersistedMetadata(options?.metadata)
10050
11210
  };
10051
11211
  this.executions.set(executionId, execInfo);
@@ -10059,6 +11219,8 @@ var AxlRuntime = class extends EventEmitter2 {
10059
11219
  sessionHistory,
10060
11220
  signal: controller.signal,
10061
11221
  eventStreamOptions: options?.events,
11222
+ branchDrainTimeoutMs,
11223
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
10062
11224
  onTrace: (event) => {
10063
11225
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10064
11226
  execInfo.totalCost += eventCostContribution(event);
@@ -10103,7 +11265,6 @@ var AxlRuntime = class extends EventEmitter2 {
10103
11265
  workflowName: name,
10104
11266
  mcpManager: this.mcpManager,
10105
11267
  memoryManager: this.memoryManager,
10106
- resumeMode: !!options?.metadata?.resumeMode,
10107
11268
  spanManager: this.spanManager,
10108
11269
  budgetContext: {
10109
11270
  totalCost: 0,
@@ -10134,6 +11295,7 @@ var AxlRuntime = class extends EventEmitter2 {
10134
11295
  }
10135
11296
  /** Execute a workflow and return a stream. */
10136
11297
  stream(name, input, options) {
11298
+ const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
10137
11299
  const axlStream = new AxlStream(options?.events);
10138
11300
  const controller = new AbortController();
10139
11301
  axlStream.on("close", () => controller.abort());
@@ -10154,11 +11316,13 @@ var AxlRuntime = class extends EventEmitter2 {
10154
11316
  executionId,
10155
11317
  workflow: name,
10156
11318
  status: "running",
11319
+ eventSchemaVersion: 2,
10157
11320
  events: [],
10158
11321
  totalCost: 0,
10159
11322
  unpriced: false,
10160
11323
  startedAt: Date.now(),
10161
11324
  duration: 0,
11325
+ observation: { complete: true },
10162
11326
  metadata: liftPersistedMetadata(options?.metadata)
10163
11327
  };
10164
11328
  this.executions.set(executionId, execInfo);
@@ -10171,12 +11335,11 @@ var AxlRuntime = class extends EventEmitter2 {
10171
11335
  sessionHistory,
10172
11336
  signal: controller.signal,
10173
11337
  eventStreamOptions: options?.events,
10174
- // Sentinel `onToken` so the streaming code path in WorkflowContext
10175
- // is entered (it's gated on `this.onToken` being defined). The
10176
- // actual `token` AxlEvents are emitted via `emitEvent` and reach
10177
- // the wire through `onTrace` — this callback is just the gate.
10178
- onToken: () => {
10179
- },
11338
+ branchDrainTimeoutMs,
11339
+ _onDecisionCleanupFailed: (event) => this.emit("decision_cleanup_failed", event),
11340
+ // Transport mode is explicit internal state. It is inherited by child
11341
+ // contexts and does not allocate an unconsumed ctx.events queue.
11342
+ _forceStreaming: true,
10180
11343
  onTrace: (event) => {
10181
11344
  pushEventBounded(execInfo, event, this.maxEventsPerExecution);
10182
11345
  execInfo.totalCost += eventCostContribution(event);
@@ -10222,7 +11385,6 @@ var AxlRuntime = class extends EventEmitter2 {
10222
11385
  workflowName: name,
10223
11386
  mcpManager: this.mcpManager,
10224
11387
  memoryManager: this.memoryManager,
10225
- resumeMode: !!options?.metadata?.resumeMode,
10226
11388
  spanManager: this.spanManager,
10227
11389
  budgetContext: {
10228
11390
  totalCost: 0,
@@ -10386,6 +11548,12 @@ var AxlRuntime = class extends EventEmitter2 {
10386
11548
  await this.stateStore.finalizeStreamingEvents?.(id);
10387
11549
  continue;
10388
11550
  }
11551
+ const eventSchemaVersion = getEventSchemaVersion(events[0]);
11552
+ for (const event of events) {
11553
+ if (getEventSchemaVersion(event) !== eventSchemaVersion) {
11554
+ throw new Error(`Streaming buffer ${id} contains mixed event schema versions`);
11555
+ }
11556
+ }
10389
11557
  const boundedEvents = boundRecoveredEvents(events, this.maxEventsPerExecution);
10390
11558
  const workflowStartEvent = boundedEvents.find((e) => e.type === "workflow_start");
10391
11559
  const lastEvent = boundedEvents[boundedEvents.length - 1];
@@ -10394,7 +11562,7 @@ var AxlRuntime = class extends EventEmitter2 {
10394
11562
  const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
10395
11563
  const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
10396
11564
  const unpriced = boundedEvents.some(isUnpricedLeaf);
10397
- const synthesized = {
11565
+ const executionBase = {
10398
11566
  executionId: id,
10399
11567
  workflow: workflowName,
10400
11568
  status: "failed",
@@ -10406,6 +11574,16 @@ var AxlRuntime = class extends EventEmitter2 {
10406
11574
  duration: completedAt - startedAt,
10407
11575
  error: "process terminated (recovered from streaming buffer)"
10408
11576
  };
11577
+ const synthesized = eventSchemaVersion === 2 ? {
11578
+ ...executionBase,
11579
+ eventSchemaVersion: 2,
11580
+ events: boundedEvents,
11581
+ observation: { complete: false, reason: "process_interrupted" }
11582
+ } : {
11583
+ ...executionBase,
11584
+ eventSchemaVersion: 1,
11585
+ events: boundedEvents
11586
+ };
10409
11587
  if (this.stateStore.saveExecution) {
10410
11588
  try {
10411
11589
  await this.stateStore.saveExecution(synthesized);
@@ -10486,7 +11664,8 @@ var AxlRuntime = class extends EventEmitter2 {
10486
11664
  */
10487
11665
  async deleteExecution(id) {
10488
11666
  const wasActive = this.executions.has(id);
10489
- const hadPendingDecision = this.pendingDecisionResolvers.has(id);
11667
+ const pendingDecisionResolver = this.pendingDecisionResolvers.get(id);
11668
+ const hadPendingDecision = pendingDecisionResolver !== void 0;
10490
11669
  if (wasActive) {
10491
11670
  this.pendingDeletedExecutions.add(id);
10492
11671
  this.streamableExecutionIds.delete(id);
@@ -10495,8 +11674,12 @@ var AxlRuntime = class extends EventEmitter2 {
10495
11674
  controller.abort();
10496
11675
  }
10497
11676
  }
10498
- if (hadPendingDecision) {
10499
- this.pendingDecisionResolvers.delete(id);
11677
+ if (pendingDecisionResolver) pendingDecisionResolver.cancelled = true;
11678
+ if (pendingDecisionResolver) {
11679
+ await pendingDecisionResolver.cleanupFinished;
11680
+ if (this.pendingDecisionResolvers.get(id) === pendingDecisionResolver) {
11681
+ this.pendingDecisionResolvers.delete(id);
11682
+ }
10500
11683
  }
10501
11684
  const existing = await this.getExecution(id);
10502
11685
  const workflow2 = existing?.workflow;
@@ -10574,60 +11757,68 @@ var AxlRuntime = class extends EventEmitter2 {
10574
11757
  }
10575
11758
  /** Resolve a pending human decision. */
10576
11759
  async resolveDecision(executionId, decision) {
10577
- const resolver = this.pendingDecisionResolvers.get(executionId);
11760
+ decision = parseHumanDecision(decision);
11761
+ let resolver = this.pendingDecisionResolvers.get(executionId);
10578
11762
  if (resolver) {
10579
- resolver(decision);
10580
- this.pendingDecisionResolvers.delete(executionId);
11763
+ if (resolver.cancelled) {
11764
+ throw new AxlError(
11765
+ "PENDING_DECISION_NOT_FOUND",
11766
+ `The pending human decision for execution "${executionId}" is no longer active.`
11767
+ );
11768
+ }
11769
+ await resolver.ready;
11770
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11771
+ throw new AxlError(
11772
+ "PENDING_DECISION_NOT_FOUND",
11773
+ `The pending human decision for execution "${executionId}" is no longer active.`
11774
+ );
11775
+ }
10581
11776
  }
10582
- await this.stateStore.resolveDecision(executionId, decision);
10583
11777
  if (!resolver) {
10584
- const state = await this.stateStore.getExecutionState(executionId);
10585
- if (state && state.status === "waiting") {
10586
- await this.resumeExecution(executionId);
11778
+ const pending = await this.stateStore.getPendingDecisions();
11779
+ resolver = this.pendingDecisionResolvers.get(executionId);
11780
+ if (resolver) {
11781
+ if (resolver.cancelled) {
11782
+ throw new AxlError(
11783
+ "PENDING_DECISION_NOT_FOUND",
11784
+ `The pending human decision for execution "${executionId}" is no longer active.`
11785
+ );
11786
+ }
11787
+ await resolver.ready;
11788
+ if (resolver.cancelled || resolver.state !== "pending" || this.pendingDecisionResolvers.get(executionId) !== resolver) {
11789
+ throw new AxlError(
11790
+ "PENDING_DECISION_NOT_FOUND",
11791
+ `The pending human decision for execution "${executionId}" is no longer active.`
11792
+ );
11793
+ }
11794
+ } else if (!pending.some((request) => request.executionId === executionId)) {
11795
+ throw new AxlError(
11796
+ "PENDING_DECISION_NOT_FOUND",
11797
+ `No pending human decision exists for execution "${executionId}".`
11798
+ );
11799
+ } else {
11800
+ throw new AxlError(
11801
+ "CROSS_PROCESS_RESUME_UNSUPPORTED",
11802
+ `Execution "${executionId}" is not awaiting a decision in this runtime. Cross-process approval replay requires a durable decision/lease lineage and is not yet supported; any persisted pending request was preserved.`
11803
+ );
10587
11804
  }
10588
11805
  }
10589
- }
10590
- /**
10591
- * Resume a specific execution that was waiting for a human decision.
10592
- * Re-runs the workflow from scratch; the workflow should use checkpoint-replay
10593
- * to skip already-completed steps and pick up from the awaitHuman point.
10594
- */
10595
- async resumeExecution(executionId) {
10596
- const state = await this.stateStore.getExecutionState(executionId);
10597
- if (!state) {
10598
- throw new Error(`No execution state found for "${executionId}"`);
10599
- }
10600
- const workflow2 = this.workflows.get(state.workflow);
10601
- if (!workflow2) {
10602
- throw new Error(
10603
- `Workflow "${state.workflow}" not registered. Cannot resume execution "${executionId}".`
10604
- );
10605
- }
10606
- return this.execute(state.workflow, state.input, {
10607
- metadata: { ...state.metadata, resumedFrom: executionId, resumeMode: true }
11806
+ let finishResolution;
11807
+ const resolutionFinished = new Promise((resolve2) => {
11808
+ finishResolution = resolve2;
10608
11809
  });
10609
- }
10610
- /**
10611
- * Resume all pending executions that were waiting for human decisions.
10612
- * Call this on startup to resume workflows that were interrupted by a restart.
10613
- * Returns the execution IDs that were resumed.
10614
- */
10615
- async resumePending() {
10616
- const pendingIds = await this.stateStore.listPendingExecutions();
10617
- const resumed = [];
10618
- for (const executionId of pendingIds) {
10619
- try {
10620
- await this.resumeExecution(executionId);
10621
- resumed.push(executionId);
10622
- } catch (err) {
10623
- this.emit("error", {
10624
- type: "resume_failed",
10625
- executionId,
10626
- error: err instanceof Error ? err.message : String(err)
10627
- });
10628
- }
11810
+ resolver.state = "resolving";
11811
+ resolver.resolution = resolutionFinished;
11812
+ try {
11813
+ await this.stateStore.resolveDecision(executionId, decision);
11814
+ resolver.state = "resolved";
11815
+ finishResolution(true);
11816
+ resolver.resolve(decision);
11817
+ } catch (error) {
11818
+ resolver.state = "pending";
11819
+ finishResolution(false);
11820
+ throw error;
10629
11821
  }
10630
- return resumed;
10631
11822
  }
10632
11823
  /**
10633
11824
  * Summarize a list of chat messages into a concise summary string.
@@ -11529,28 +12720,32 @@ var RedisStore = class _RedisStore {
11529
12720
  async saveExecution(execution) {
11530
12721
  const ttl = this.ttlFor("executionHistory");
11531
12722
  const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
11532
- await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(execution), setOptions).zAdd(this.execHistoryZsetKey(), {
12723
+ const storedExecution = normalizeStoredExecution({
12724
+ ...execution,
12725
+ eventSchemaVersion: getExecutionEventSchemaVersion(execution)
12726
+ });
12727
+ await this.client.multi().sAdd(this.execHistorySetKey(), execution.executionId).set(this.execHistoryKey(execution.executionId), JSON.stringify(storedExecution), setOptions).zAdd(this.execHistoryZsetKey(), {
11533
12728
  score: execution.startedAt,
11534
12729
  value: execution.executionId
11535
12730
  }).exec();
11536
12731
  }
11537
12732
  async getExecution(executionId) {
11538
12733
  const raw = await this.client.get(this.execHistoryKey(executionId));
11539
- return raw ? JSON.parse(raw) : null;
12734
+ return raw ? normalizeStoredExecution(JSON.parse(raw)) : null;
11540
12735
  }
11541
12736
  async listExecutions(limit) {
11542
- return this.listHistoryByZset(
12737
+ const executions = await this.listHistoryByZset(
11543
12738
  this.execHistorySetKey(),
11544
12739
  this.execHistoryZsetKey(),
11545
12740
  (id) => this.execHistoryKey(id),
11546
12741
  (a, b) => b.startedAt - a.startedAt,
11547
12742
  limit
11548
12743
  );
12744
+ return executions.map(normalizeStoredExecution);
11549
12745
  }
11550
12746
  async deleteExecution(executionId) {
11551
- 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();
11552
- const deleted = typeof deletedCount === "number" ? deletedCount : 0;
11553
- return deleted > 0;
12747
+ const results = await this.client.multi().sRem(this.execHistorySetKey(), executionId).del(this.execHistoryKey(executionId)).zRem(this.execHistoryZsetKey(), executionId).del(this.checkpointKey(executionId)).del(this.executionStateKey(executionId)).sRem(this.pendingExecSetKey(), executionId).del(this.streamingEventsKey(executionId)).sRem(this.streamingIdsKey(), executionId).hDel(this.decisionsKey(), executionId).exec();
12748
+ return results.some((result) => typeof result === "number" && result > 0);
11554
12749
  }
11555
12750
  // ── Eval History ────────────────────────────────────────────────────
11556
12751
  async saveEvalResult(entry) {
@@ -11642,14 +12837,15 @@ var RedisStore = class _RedisStore {
11642
12837
  async getStreamingEvents(executionId) {
11643
12838
  const raws = await this.client.lRange(this.streamingEventsKey(executionId), 0, -1);
11644
12839
  if (raws.length === 0) return [];
11645
- const events = [];
11646
- for (const raw of raws) {
12840
+ return raws.map((raw, index) => {
11647
12841
  try {
11648
- events.push(JSON.parse(raw));
11649
- } catch {
12842
+ return JSON.parse(raw);
12843
+ } catch (error) {
12844
+ throw new Error(`Streaming buffer ${executionId} contains invalid JSON at index ${index}`, {
12845
+ cause: error
12846
+ });
11650
12847
  }
11651
- }
11652
- return events;
12848
+ });
11653
12849
  }
11654
12850
  /** Close the Redis connection. */
11655
12851
  async close() {
@@ -11860,6 +13056,8 @@ function cosineSimilarity2(a, b) {
11860
13056
  }
11861
13057
  export {
11862
13058
  AXL_EVENT_TYPES,
13059
+ AXL_EVENT_TYPES_V2,
13060
+ AXL_TOOL_LIFECYCLE_TYPES_V2,
11863
13061
  AZURE_PROFILE,
11864
13062
  AnthropicProvider,
11865
13063
  AxlError,
@@ -11907,7 +13105,9 @@ export {
11907
13105
  SqliteVectorStore,
11908
13106
  ThinkTagScanner,
11909
13107
  TimeoutError,
11910
- ToolDenied,
13108
+ ToolFailure,
13109
+ ToolModelOutputError,
13110
+ UnsupportedEventSchemaVersionError,
11911
13111
  VLLM_PROFILE,
11912
13112
  ValidationError,
11913
13113
  VerifyError,
@@ -11922,6 +13122,8 @@ export {
11922
13122
  extractJson,
11923
13123
  extractThinkTags,
11924
13124
  findConfig,
13125
+ getEventSchemaVersion,
13126
+ getExecutionEventSchemaVersion,
11925
13127
  hasPositiveTokens,
11926
13128
  importModule,
11927
13129
  isCostBearingLeaf,
@@ -11930,6 +13132,7 @@ export {
11930
13132
  isUnpricedLeaf,
11931
13133
  isUsableCost,
11932
13134
  needsTsxLoader,
13135
+ normalizeStoredExecution,
11933
13136
  openaiReasoningEmit,
11934
13137
  parsePartialJson,
11935
13138
  pickDefault,
@@ -11938,6 +13141,7 @@ export {
11938
13141
  reasoningEffortEmit,
11939
13142
  reasoningObjectEmit,
11940
13143
  redactEvent,
13144
+ redactHistoricalEvent,
11941
13145
  registerConditions,
11942
13146
  resolveApiKey,
11943
13147
  resolvePerModel,