@axlsdk/axl 0.15.0 → 0.16.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
@@ -1316,6 +1316,41 @@ ${jsonInstruction}` : jsonInstruction;
1316
1316
  };
1317
1317
 
1318
1318
  // src/providers/gemini.ts
1319
+ var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
1320
+ "additionalProperties",
1321
+ "$schema",
1322
+ "$ref",
1323
+ "$defs",
1324
+ "definitions",
1325
+ "not",
1326
+ "allOf",
1327
+ "patternProperties",
1328
+ "unevaluatedProperties",
1329
+ "unevaluatedItems"
1330
+ ]);
1331
+ function sanitizeSchemaForGemini(schema) {
1332
+ if (Array.isArray(schema)) {
1333
+ return schema.map((item) => sanitizeSchemaForGemini(item));
1334
+ }
1335
+ if (schema === null || typeof schema !== "object") {
1336
+ return schema;
1337
+ }
1338
+ const src = schema;
1339
+ const out = {};
1340
+ for (const [key, value] of Object.entries(src)) {
1341
+ if (GEMINI_DISALLOWED_SCHEMA_KEYS.has(key)) continue;
1342
+ if (key === "oneOf") {
1343
+ out.anyOf = sanitizeSchemaForGemini(value);
1344
+ continue;
1345
+ }
1346
+ if (key === "const") {
1347
+ if (!("enum" in src)) out.enum = [value];
1348
+ continue;
1349
+ }
1350
+ out[key] = sanitizeSchemaForGemini(value);
1351
+ }
1352
+ return out;
1353
+ }
1319
1354
  var GEMINI_PRICING = {
1320
1355
  "gemini-2.5-pro": [125e-8, 1e-5],
1321
1356
  "gemini-2.5-flash": [3e-7, 25e-7],
@@ -1486,7 +1521,9 @@ var GeminiProvider = class {
1486
1521
  if (options.responseFormat && options.responseFormat.type !== "text") {
1487
1522
  generationConfig.responseMimeType = "application/json";
1488
1523
  if (options.responseFormat.type === "json_schema" && options.responseFormat.json_schema?.schema) {
1489
- generationConfig.responseSchema = options.responseFormat.json_schema.schema;
1524
+ generationConfig.responseSchema = sanitizeSchemaForGemini(
1525
+ options.responseFormat.json_schema.schema
1526
+ );
1490
1527
  }
1491
1528
  }
1492
1529
  if (Object.keys(generationConfig).length > 0) {
@@ -1663,7 +1700,7 @@ var GeminiProvider = class {
1663
1700
  return {
1664
1701
  name: tool2.function.name,
1665
1702
  description: tool2.function.description,
1666
- parameters: tool2.function.parameters
1703
+ parameters: sanitizeSchemaForGemini(tool2.function.parameters)
1667
1704
  };
1668
1705
  }
1669
1706
  // ---------------------------------------------------------------------------
@@ -1922,6 +1959,7 @@ var defaultRegistry = new ProviderRegistry();
1922
1959
 
1923
1960
  // src/context.ts
1924
1961
  import { AsyncLocalStorage } from "async_hooks";
1962
+ import { randomUUID } from "crypto";
1925
1963
  import { z, ZodError } from "zod";
1926
1964
 
1927
1965
  // src/errors.ts
@@ -2041,6 +2079,432 @@ var ToolDenied = class extends AxlError {
2041
2079
  }
2042
2080
  };
2043
2081
 
2082
+ // src/partial-json.ts
2083
+ var MAX_DEPTH = 256;
2084
+ function parsePartialJson(input) {
2085
+ const parser = new PartialParser(input);
2086
+ parser.skipWhitespace();
2087
+ if (parser.eof()) return void 0;
2088
+ const result = parser.parseValue();
2089
+ return result.value;
2090
+ }
2091
+ var PartialParser = class {
2092
+ constructor(src) {
2093
+ this.src = src;
2094
+ }
2095
+ pos = 0;
2096
+ depth = 0;
2097
+ eof() {
2098
+ return this.pos >= this.src.length;
2099
+ }
2100
+ peek() {
2101
+ return this.src[this.pos];
2102
+ }
2103
+ advance() {
2104
+ return this.src[this.pos++];
2105
+ }
2106
+ skipWhitespace() {
2107
+ while (!this.eof()) {
2108
+ const c = this.peek();
2109
+ if (c === " " || c === "\n" || c === " " || c === "\r") {
2110
+ this.pos++;
2111
+ } else {
2112
+ break;
2113
+ }
2114
+ }
2115
+ }
2116
+ parseValue() {
2117
+ this.skipWhitespace();
2118
+ if (this.eof()) return { value: void 0, complete: false };
2119
+ const c = this.peek();
2120
+ if (c === "{" || c === "[") {
2121
+ if (++this.depth > MAX_DEPTH) {
2122
+ throw new SyntaxError(
2123
+ `Maximum nesting depth exceeded (${MAX_DEPTH}) at position ${this.pos}`
2124
+ );
2125
+ }
2126
+ try {
2127
+ return c === "{" ? this.parseObject() : this.parseArray();
2128
+ } finally {
2129
+ this.depth--;
2130
+ }
2131
+ }
2132
+ if (c === '"') return this.parseString();
2133
+ if (c === "t" || c === "f") return this.parseBool();
2134
+ if (c === "n") return this.parseNull();
2135
+ if (c === "-" || c >= "0" && c <= "9") return this.parseNumber();
2136
+ throw new SyntaxError(`Unexpected character '${c}' at position ${this.pos}`);
2137
+ }
2138
+ parseObject() {
2139
+ this.advance();
2140
+ const obj = {};
2141
+ this.skipWhitespace();
2142
+ if (this.eof()) return { value: obj, complete: false };
2143
+ if (this.peek() === "}") {
2144
+ this.advance();
2145
+ return { value: obj, complete: true };
2146
+ }
2147
+ while (!this.eof()) {
2148
+ this.skipWhitespace();
2149
+ if (this.eof()) return { value: obj, complete: false };
2150
+ if (this.peek() === "}") {
2151
+ this.advance();
2152
+ return { value: obj, complete: true };
2153
+ }
2154
+ if (this.peek() !== '"') {
2155
+ throw new SyntaxError(`Expected '"' at position ${this.pos}, got '${this.peek()}'`);
2156
+ }
2157
+ const keyResult = this.parseString();
2158
+ if (!keyResult.complete) {
2159
+ return { value: obj, complete: false };
2160
+ }
2161
+ const key = keyResult.value;
2162
+ this.skipWhitespace();
2163
+ if (this.eof()) return { value: obj, complete: false };
2164
+ if (this.peek() !== ":") {
2165
+ return { value: obj, complete: false };
2166
+ }
2167
+ this.advance();
2168
+ this.skipWhitespace();
2169
+ if (this.eof()) return { value: obj, complete: false };
2170
+ const valueResult = this.parseValue();
2171
+ if (valueResult.value !== void 0 || valueResult.complete) {
2172
+ obj[key] = valueResult.value;
2173
+ }
2174
+ if (!valueResult.complete) {
2175
+ return { value: obj, complete: false };
2176
+ }
2177
+ this.skipWhitespace();
2178
+ if (this.eof()) return { value: obj, complete: false };
2179
+ const next = this.peek();
2180
+ if (next === ",") {
2181
+ this.advance();
2182
+ continue;
2183
+ }
2184
+ if (next === "}") {
2185
+ this.advance();
2186
+ return { value: obj, complete: true };
2187
+ }
2188
+ throw new SyntaxError(`Expected ',' or '}' at position ${this.pos}, got '${next}'`);
2189
+ }
2190
+ return { value: obj, complete: false };
2191
+ }
2192
+ parseArray() {
2193
+ this.advance();
2194
+ const arr = [];
2195
+ this.skipWhitespace();
2196
+ if (this.eof()) return { value: arr, complete: false };
2197
+ if (this.peek() === "]") {
2198
+ this.advance();
2199
+ return { value: arr, complete: true };
2200
+ }
2201
+ while (!this.eof()) {
2202
+ this.skipWhitespace();
2203
+ if (this.eof()) return { value: arr, complete: false };
2204
+ if (this.peek() === "]") {
2205
+ this.advance();
2206
+ return { value: arr, complete: true };
2207
+ }
2208
+ const elemResult = this.parseValue();
2209
+ if (elemResult.value !== void 0 || elemResult.complete) {
2210
+ arr.push(elemResult.value);
2211
+ }
2212
+ if (!elemResult.complete) {
2213
+ return { value: arr, complete: false };
2214
+ }
2215
+ this.skipWhitespace();
2216
+ if (this.eof()) return { value: arr, complete: false };
2217
+ const next = this.peek();
2218
+ if (next === ",") {
2219
+ this.advance();
2220
+ continue;
2221
+ }
2222
+ if (next === "]") {
2223
+ this.advance();
2224
+ return { value: arr, complete: true };
2225
+ }
2226
+ throw new SyntaxError(`Expected ',' or ']' at position ${this.pos}, got '${next}'`);
2227
+ }
2228
+ return { value: arr, complete: false };
2229
+ }
2230
+ parseString() {
2231
+ this.advance();
2232
+ let result = "";
2233
+ while (!this.eof()) {
2234
+ const c = this.advance();
2235
+ if (c === '"') return { value: result, complete: true };
2236
+ if (c === "\\") {
2237
+ if (this.eof()) {
2238
+ return { value: result, complete: false };
2239
+ }
2240
+ const esc = this.advance();
2241
+ switch (esc) {
2242
+ case '"':
2243
+ result += '"';
2244
+ break;
2245
+ case "\\":
2246
+ result += "\\";
2247
+ break;
2248
+ case "/":
2249
+ result += "/";
2250
+ break;
2251
+ case "b":
2252
+ result += "\b";
2253
+ break;
2254
+ case "f":
2255
+ result += "\f";
2256
+ break;
2257
+ case "n":
2258
+ result += "\n";
2259
+ break;
2260
+ case "r":
2261
+ result += "\r";
2262
+ break;
2263
+ case "t":
2264
+ result += " ";
2265
+ break;
2266
+ case "u": {
2267
+ if (this.pos + 4 > this.src.length) {
2268
+ return { value: result, complete: false };
2269
+ }
2270
+ const hex = this.src.slice(this.pos, this.pos + 4);
2271
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
2272
+ throw new SyntaxError(`Invalid \\u escape '${hex}' at position ${this.pos}`);
2273
+ }
2274
+ this.pos += 4;
2275
+ result += String.fromCharCode(parseInt(hex, 16));
2276
+ break;
2277
+ }
2278
+ default:
2279
+ throw new SyntaxError(`Invalid escape '\\${esc}' at position ${this.pos - 1}`);
2280
+ }
2281
+ } else {
2282
+ result += c;
2283
+ }
2284
+ }
2285
+ return { value: result, complete: false };
2286
+ }
2287
+ parseNumber() {
2288
+ const start = this.pos;
2289
+ if (this.peek() === "-") this.advance();
2290
+ while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
2291
+ if (!this.eof() && this.peek() === ".") {
2292
+ this.advance();
2293
+ while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
2294
+ }
2295
+ if (!this.eof() && (this.peek() === "e" || this.peek() === "E")) {
2296
+ this.advance();
2297
+ if (!this.eof() && (this.peek() === "+" || this.peek() === "-")) this.advance();
2298
+ while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
2299
+ }
2300
+ const text = this.src.slice(start, this.pos);
2301
+ if (text === "" || text === "-" || /\.$/.test(text) || /[eE][+-]?$/.test(text)) {
2302
+ return { value: void 0, complete: false };
2303
+ }
2304
+ const n = Number(text);
2305
+ if (Number.isNaN(n)) {
2306
+ return { value: void 0, complete: false };
2307
+ }
2308
+ return { value: n, complete: true };
2309
+ }
2310
+ parseBool() {
2311
+ const remaining = this.src.slice(this.pos);
2312
+ if (remaining.startsWith("true")) {
2313
+ this.pos += 4;
2314
+ return { value: true, complete: true };
2315
+ }
2316
+ if (remaining.startsWith("false")) {
2317
+ this.pos += 5;
2318
+ return { value: false, complete: true };
2319
+ }
2320
+ if ("true".startsWith(remaining) || "false".startsWith(remaining)) {
2321
+ this.pos = this.src.length;
2322
+ return { value: void 0, complete: false };
2323
+ }
2324
+ throw new SyntaxError(`Expected 'true' or 'false' at position ${this.pos}`);
2325
+ }
2326
+ parseNull() {
2327
+ const remaining = this.src.slice(this.pos);
2328
+ if (remaining.startsWith("null")) {
2329
+ this.pos += 4;
2330
+ return { value: null, complete: true };
2331
+ }
2332
+ if ("null".startsWith(remaining)) {
2333
+ this.pos = this.src.length;
2334
+ return { value: void 0, complete: false };
2335
+ }
2336
+ throw new SyntaxError(`Expected 'null' at position ${this.pos}`);
2337
+ }
2338
+ };
2339
+
2340
+ // src/event-utils.ts
2341
+ var COST_BEARING_LEAF_TYPES = [
2342
+ "agent_call_end",
2343
+ "tool_call_end",
2344
+ "memory_remember",
2345
+ "memory_recall"
2346
+ ];
2347
+ var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
2348
+ function eventCostContribution(event) {
2349
+ if (event.type === "ask_end") return 0;
2350
+ const c = event.cost;
2351
+ return typeof c === "number" && Number.isFinite(c) && c >= 0 ? c : 0;
2352
+ }
2353
+ function isCostBearingLeaf(event) {
2354
+ return COST_LEAF_SET.has(event.type);
2355
+ }
2356
+ function isRootLevel(event) {
2357
+ const d = event.depth;
2358
+ return (d ?? 0) === 0;
2359
+ }
2360
+
2361
+ // src/redaction.ts
2362
+ var REDACTED = "[redacted]";
2363
+ function walkObjectOneLevel(obj, preserveKeys) {
2364
+ const out = {};
2365
+ for (const [k, v] of Object.entries(obj)) {
2366
+ if (preserveKeys.has(k) || typeof v === "number" || typeof v === "boolean") {
2367
+ out[k] = v;
2368
+ } else if (typeof v === "string") {
2369
+ out[k] = REDACTED;
2370
+ } else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
2371
+ const inner = v;
2372
+ const innerOut = {};
2373
+ for (const [ik, iv] of Object.entries(inner)) {
2374
+ if (typeof iv === "number" || typeof iv === "boolean") {
2375
+ innerOut[ik] = iv;
2376
+ } else {
2377
+ innerOut[ik] = REDACTED;
2378
+ }
2379
+ }
2380
+ out[k] = innerOut;
2381
+ } else {
2382
+ out[k] = REDACTED;
2383
+ }
2384
+ }
2385
+ return out;
2386
+ }
2387
+ var LOG_PRESERVE_KEYS = /* @__PURE__ */ new Set(["event"]);
2388
+ var MEMORY_PRESERVE_KEYS = /* @__PURE__ */ new Set(["scope"]);
2389
+ function redactGate(event) {
2390
+ const d = event.data;
2391
+ if (!d) return event;
2392
+ if (d.reason === void 0 && d.feedbackMessage === void 0) return event;
2393
+ return {
2394
+ ...event,
2395
+ data: {
2396
+ ...d,
2397
+ ...d.reason !== void 0 ? { reason: REDACTED } : {},
2398
+ ...d.feedbackMessage !== void 0 ? { feedbackMessage: REDACTED } : {}
2399
+ }
2400
+ };
2401
+ }
2402
+ function redactMemory(event) {
2403
+ const d = event.data;
2404
+ if (!d || typeof d !== "object") return event;
2405
+ return {
2406
+ ...event,
2407
+ data: walkObjectOneLevel(d, MEMORY_PRESERVE_KEYS)
2408
+ };
2409
+ }
2410
+ var passthrough = (e) => e;
2411
+ var REDACTION_RULES = {
2412
+ workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
2413
+ workflow_end: (e) => {
2414
+ const d = e.data;
2415
+ if (d.result === void 0 && d.error === void 0) return e;
2416
+ return {
2417
+ ...e,
2418
+ data: {
2419
+ ...d,
2420
+ ...d.result !== void 0 ? { result: REDACTED } : {},
2421
+ ...d.error !== void 0 ? { error: REDACTED } : {}
2422
+ }
2423
+ };
2424
+ },
2425
+ ask_start: (e) => ({ ...e, prompt: REDACTED }),
2426
+ ask_end: (e) => ({
2427
+ ...e,
2428
+ outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
2429
+ }),
2430
+ agent_call_start: (e) => {
2431
+ const d = e.data;
2432
+ const out = { ...d, prompt: REDACTED };
2433
+ if (d.system !== void 0) out.system = REDACTED;
2434
+ if (Array.isArray(d.messages)) {
2435
+ out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
2436
+ }
2437
+ return { ...e, data: out };
2438
+ },
2439
+ agent_call_end: (e) => {
2440
+ const d = e.data;
2441
+ const out = { ...d, response: REDACTED };
2442
+ if (d.thinking !== void 0) out.thinking = REDACTED;
2443
+ if (d.error !== void 0) out.error = REDACTED;
2444
+ return { ...e, data: out };
2445
+ },
2446
+ token: (e) => ({ ...e, data: REDACTED }),
2447
+ tool_call_start: (e) => ({ ...e, data: { ...e.data, args: REDACTED } }),
2448
+ tool_call_end: (e) => ({ ...e, data: { ...e.data, args: REDACTED, result: REDACTED } }),
2449
+ tool_approval: (e) => ({
2450
+ ...e,
2451
+ data: {
2452
+ ...e.data,
2453
+ args: REDACTED,
2454
+ ...e.data.reason !== void 0 ? { reason: REDACTED } : {}
2455
+ }
2456
+ }),
2457
+ tool_denied: (e) => {
2458
+ if (!e.data) return e;
2459
+ const d = e.data;
2460
+ if (d.args === void 0 && d.reason === void 0) return e;
2461
+ return {
2462
+ ...e,
2463
+ data: {
2464
+ ...d,
2465
+ ...d.args !== void 0 ? { args: REDACTED } : {},
2466
+ ...d.reason !== void 0 ? { reason: REDACTED } : {}
2467
+ }
2468
+ };
2469
+ },
2470
+ delegate: passthrough,
2471
+ handoff_start: (e) => e.data.message !== void 0 ? { ...e, data: { ...e.data, message: REDACTED } } : e,
2472
+ handoff_return: passthrough,
2473
+ pipeline: (e) => e.status === "failed" ? { ...e, reason: REDACTED } : e,
2474
+ partial_object: (e) => ({ ...e, data: { ...e.data, object: REDACTED } }),
2475
+ verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
2476
+ log: (e) => {
2477
+ if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
2478
+ return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
2479
+ },
2480
+ memory_remember: redactMemory,
2481
+ memory_recall: redactMemory,
2482
+ memory_forget: redactMemory,
2483
+ checkpoint_save: passthrough,
2484
+ checkpoint_replay: passthrough,
2485
+ await_human: (e) => e.data.prompt !== void 0 ? { ...e, data: { ...e.data, prompt: REDACTED } } : e,
2486
+ await_human_resolved: (e) => {
2487
+ const dec = e.data.decision;
2488
+ if (!dec) return e;
2489
+ const hasData = "data" in dec && dec.data !== void 0;
2490
+ const hasReason = "reason" in dec && dec.reason !== void 0;
2491
+ if (!hasData && !hasReason) return e;
2492
+ const scrubbed = { ...dec };
2493
+ if (hasData) scrubbed.data = REDACTED;
2494
+ if (hasReason) scrubbed.reason = REDACTED;
2495
+ return { ...e, data: { ...e.data, decision: scrubbed } };
2496
+ },
2497
+ guardrail: redactGate,
2498
+ schema_check: redactGate,
2499
+ validate: redactGate,
2500
+ done: (e) => ({ ...e, data: { result: REDACTED } }),
2501
+ error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
2502
+ };
2503
+ function redactEvent(event) {
2504
+ const rule = REDACTION_RULES[event.type];
2505
+ return rule(event);
2506
+ }
2507
+
2044
2508
  // src/config.ts
2045
2509
  function defineConfig(config) {
2046
2510
  return config;
@@ -2118,6 +2582,7 @@ function resolveConfig(config) {
2118
2582
 
2119
2583
  // src/context.ts
2120
2584
  var signalStorage = new AsyncLocalStorage();
2585
+ var askStorage = new AsyncLocalStorage();
2121
2586
  function zodToJsonSchema(schema) {
2122
2587
  const result = z.toJSONSchema(schema, { unrepresentable: "any" });
2123
2588
  delete result.$schema;
@@ -2219,8 +2684,34 @@ var WorkflowContext = class _WorkflowContext {
2219
2684
  pendingDecisions;
2220
2685
  budgetContext;
2221
2686
  stateStore;
2222
- stepCounter = 0;
2223
- checkpointCounter = 0;
2687
+ /** Root step counter for this execution. Inherited by every ctx.ask()
2688
+ * frame so all events from this WorkflowContext (root + nested) share
2689
+ * a single monotonic counter, even when concurrent branch primitives
2690
+ * fire asks before any single parent ask exists. Per-instance so
2691
+ * separate executions don't cross-talk. */
2692
+ stepRefRoot = { value: 0 };
2693
+ /**
2694
+ * Auto-checkpoint counters for internal `_checkpoint` callers. Held by
2695
+ * reference so child contexts created via `createChildContext` share
2696
+ * the same number space — otherwise a tool handler's nested `ctx.ask()`
2697
+ * (which auto-checkpoints) would collide with the parent's
2698
+ * auto-checkpoints in the state store. See `WorkflowContextInit
2699
+ * .autoCheckpointCounters` for the per-agent vs root split.
2700
+ * User-facing `ctx.checkpoint(name, fn)` requires an explicit name and
2701
+ * does NOT touch these counters — it goes straight to the store under
2702
+ * the caller-supplied key.
2703
+ */
2704
+ autoCheckpointCounters;
2705
+ /** Idempotency guards for `workflow_start` / `workflow_end` emission.
2706
+ * Both `runtime.execute()` and `runtime.stream()` have paths where a
2707
+ * post-emit side-effect (checkpoint deletion, state-store persistence)
2708
+ * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
2709
+ * fired — the outer catch would then fire `_emitWorkflowEnd({status:
2710
+ * 'failed'})` for a second time. These flags make both emitters
2711
+ * single-fire so consumers never see paired-then-conflicting terminal
2712
+ * events for one execution. */
2713
+ _workflowStartEmitted = false;
2714
+ _workflowEndEmitted = false;
2224
2715
  signal;
2225
2716
  summaryCache;
2226
2717
  workflowName;
@@ -2232,7 +2723,6 @@ var WorkflowContext = class _WorkflowContext {
2232
2723
  awaitHumanHandler;
2233
2724
  onAgentStart;
2234
2725
  onAgentCallComplete;
2235
- parentToolCallId;
2236
2726
  constructor(init) {
2237
2727
  this.input = init.input;
2238
2728
  this.executionId = init.executionId;
@@ -2256,7 +2746,10 @@ var WorkflowContext = class _WorkflowContext {
2256
2746
  this.awaitHumanHandler = init.awaitHumanHandler;
2257
2747
  this.onAgentStart = init.onAgentStart;
2258
2748
  this.onAgentCallComplete = init.onAgentCallComplete;
2259
- this.parentToolCallId = init.parentToolCallId;
2749
+ this.autoCheckpointCounters = init.autoCheckpointCounters ?? {
2750
+ byAgent: /* @__PURE__ */ new Map(),
2751
+ root: 0
2752
+ };
2260
2753
  if (init.metadata?.summaryCache) {
2261
2754
  this.summaryCache = init.metadata.summaryCache;
2262
2755
  }
@@ -2265,15 +2758,23 @@ var WorkflowContext = class _WorkflowContext {
2265
2758
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
2266
2759
  * Shares: budget tracking, abort signals, trace emission, provider registry,
2267
2760
  * state store, span manager, memory manager, MCP manager, config,
2268
- * awaitHuman handler, pending decisions, tool overrides.
2269
- * Isolates: session history, step counter, streaming callbacks (onToken, onAgentStart, onToolCall).
2761
+ * awaitHuman handler, pending decisions, tool overrides, AND the
2762
+ * streaming callbacks (onToken / onAgentStart / onToolCall).
2763
+ * Isolates: session history.
2764
+ *
2765
+ * Streaming callbacks now propagate into nested asks because every callback
2766
+ * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
2767
+ * consumers that want root-only behavior can filter on `meta.depth === 0`
2768
+ * instead of relying on the runtime to drop nested events. This is the
2769
+ * "nested ask visibility" fix from spec §3.2.
2270
2770
  *
2271
- * @param parentToolCallId - The `callId` of the outer `tool_call` that
2272
- * spawned this child. When set, every trace event emitted by the child
2273
- * will carry this id as `parentToolCallId`, so consumers can join nested
2274
- * agent calls back to the outer tool call that invoked them.
2771
+ * The shared step counter lives in `askStorage` (ALS), so there is no
2772
+ * per-context counter to pass through anymore. Nested events are
2773
+ * correlated to their parent via `parentAskId` (set on every nested ask
2774
+ * by ALS frame allocation) — the deprecated `parentToolCallId` field
2775
+ * was removed in 0.16.0.
2275
2776
  */
2276
- createChildContext(parentToolCallId) {
2777
+ createChildContext() {
2277
2778
  return new _WorkflowContext({
2278
2779
  input: this.input,
2279
2780
  executionId: this.executionId,
@@ -2287,18 +2788,20 @@ var WorkflowContext = class _WorkflowContext {
2287
2788
  spanManager: this.spanManager,
2288
2789
  memoryManager: this.memoryManager,
2289
2790
  onTrace: this.onTrace,
2791
+ onToken: this.onToken,
2792
+ onToolCall: this.onToolCall,
2793
+ onAgentStart: this.onAgentStart,
2290
2794
  onAgentCallComplete: this.onAgentCallComplete,
2291
2795
  awaitHumanHandler: this.awaitHumanHandler,
2292
2796
  pendingDecisions: this.pendingDecisions,
2293
2797
  toolOverrides: this.toolOverrides,
2294
2798
  signal: this.signal,
2295
2799
  workflowName: this.workflowName,
2296
- // Join key for nested event correlation. Inherit the parent's
2297
- // `parentToolCallId` when this child is itself nested inside another
2298
- // child — so grand-children still point to the outermost tool call.
2299
- parentToolCallId: parentToolCallId ?? this.parentToolCallId
2300
- // Isolated: sessionHistory (empty), stepCounter (0),
2301
- // onToken (null), onAgentStart (null), onToolCall (null)
2800
+ // Share the parent's auto-checkpoint counters so internal checkpoints
2801
+ // emitted from `ctx.ask` / spawn / race / parallel / map in this
2802
+ // child context don't collide with the parent's in the state store.
2803
+ autoCheckpointCounters: this.autoCheckpointCounters
2804
+ // Isolated: sessionHistory (empty)
2302
2805
  });
2303
2806
  }
2304
2807
  /**
@@ -2309,72 +2812,150 @@ var WorkflowContext = class _WorkflowContext {
2309
2812
  get currentSignal() {
2310
2813
  return signalStorage.getStore() ?? this.signal;
2311
2814
  }
2815
+ /**
2816
+ * Build a `CallbackMeta` for the current ask frame. Used at every
2817
+ * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
2818
+ * route or filter by ask correlation (`meta.depth === 0` for root
2819
+ * chat UIs, etc.).
2820
+ *
2821
+ * If the call is somehow outside an ask frame (e.g., a programmatic
2822
+ * `tool.run()` test harness), falls back to a synthetic meta keyed off
2823
+ * `executionId` so the type contract holds — consumers see `depth: 0`
2824
+ * and can ignore those events.
2825
+ */
2826
+ currentCallbackMeta(agentName) {
2827
+ const frame = askStorage.getStore();
2828
+ if (frame) {
2829
+ return {
2830
+ askId: frame.askId,
2831
+ ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
2832
+ depth: frame.depth,
2833
+ agent: agentName
2834
+ };
2835
+ }
2836
+ return { askId: this.executionId, depth: 0, agent: agentName };
2837
+ }
2312
2838
  // ── ctx.ask() ─────────────────────────────────────────────────────────
2313
2839
  async ask(agent2, prompt, options) {
2314
- return this._checkpoint(async () => {
2315
- const costBefore = this.budgetContext?.totalCost ?? 0;
2316
- const startTime = Date.now();
2317
- const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
2318
- const usageCapture = {};
2319
- const doCall = async () => {
2320
- const result2 = await this.executeAgentCall(
2321
- agent2,
2322
- prompt,
2323
- options,
2324
- void 0,
2325
- usageCapture
2326
- );
2327
- return result2;
2328
- };
2329
- const result = this.spanManager ? await this.spanManager.withSpanAsync(
2330
- "axl.agent.ask",
2331
- {
2332
- "axl.agent.name": agent2._name,
2333
- "axl.agent.model": agent2.resolveModel(resolveCtx)
2334
- },
2335
- async (span) => {
2336
- const r = await doCall();
2337
- const costAfter2 = this.budgetContext?.totalCost ?? 0;
2338
- span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
2339
- span.setAttribute("axl.agent.duration", Date.now() - startTime);
2340
- if (usageCapture.value) {
2341
- span.setAttribute("axl.agent.prompt_tokens", usageCapture.value.prompt_tokens);
2342
- span.setAttribute(
2343
- "axl.agent.completion_tokens",
2344
- usageCapture.value.completion_tokens
2840
+ const agentName = agent2._name;
2841
+ return this._checkpoint(
2842
+ this._autoCheckpointName("ask", agentName),
2843
+ async () => {
2844
+ const parentFrame = askStorage.getStore();
2845
+ const askId = randomUUID();
2846
+ const depth = (parentFrame?.depth ?? -1) + 1;
2847
+ const stepRef = parentFrame?.stepRef ?? this.stepRefRoot;
2848
+ const frame = {
2849
+ askId,
2850
+ parentAskId: parentFrame?.askId,
2851
+ depth,
2852
+ agent: agent2._name,
2853
+ stepRef,
2854
+ askCost: { value: 0 }
2855
+ };
2856
+ return askStorage.run(frame, async () => {
2857
+ const askStart = Date.now();
2858
+ this.emitEvent({ type: "ask_start", prompt });
2859
+ const costBefore = this.budgetContext?.totalCost ?? 0;
2860
+ const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
2861
+ const usageCapture = {};
2862
+ const doCall = async () => {
2863
+ const result = await this.executeAgentCall(
2864
+ agent2,
2865
+ prompt,
2866
+ options,
2867
+ void 0,
2868
+ usageCapture
2345
2869
  );
2346
- if (usageCapture.value.cached_tokens)
2347
- span.setAttribute("axl.agent.cached_tokens", usageCapture.value.cached_tokens);
2870
+ return result;
2871
+ };
2872
+ let outcome;
2873
+ try {
2874
+ const result = this.spanManager ? await this.spanManager.withSpanAsync(
2875
+ "axl.agent.ask",
2876
+ {
2877
+ "axl.agent.name": agent2._name,
2878
+ "axl.agent.model": agent2.resolveModel(resolveCtx)
2879
+ },
2880
+ async (span) => {
2881
+ const r = await doCall();
2882
+ const costAfter2 = this.budgetContext?.totalCost ?? 0;
2883
+ span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
2884
+ span.setAttribute("axl.agent.duration", Date.now() - askStart);
2885
+ if (usageCapture.value) {
2886
+ span.setAttribute(
2887
+ "axl.agent.prompt_tokens",
2888
+ usageCapture.value.prompt_tokens
2889
+ );
2890
+ span.setAttribute(
2891
+ "axl.agent.completion_tokens",
2892
+ usageCapture.value.completion_tokens
2893
+ );
2894
+ if (usageCapture.value.cached_tokens)
2895
+ span.setAttribute(
2896
+ "axl.agent.cached_tokens",
2897
+ usageCapture.value.cached_tokens
2898
+ );
2899
+ }
2900
+ return r;
2901
+ }
2902
+ ) : await doCall();
2903
+ outcome = { ok: true, result };
2904
+ const costAfter = this.budgetContext?.totalCost ?? 0;
2905
+ if (this.onAgentCallComplete) {
2906
+ try {
2907
+ this.onAgentCallComplete({
2908
+ agent: agent2._name,
2909
+ prompt,
2910
+ response: typeof result === "string" ? result : JSON.stringify(result),
2911
+ model: agent2.resolveModel(resolveCtx),
2912
+ cost: costAfter - costBefore,
2913
+ duration: Date.now() - askStart,
2914
+ promptVersion: agent2._config.version,
2915
+ temperature: options?.temperature ?? agent2._config.temperature,
2916
+ maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
2917
+ effort: options?.effort ?? agent2._config.effort,
2918
+ thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
2919
+ includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
2920
+ toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
2921
+ stop: options?.stop ?? agent2._config.stop,
2922
+ providerOptions: options?.providerOptions ?? agent2._config.providerOptions
2923
+ });
2924
+ } catch (hookErr) {
2925
+ console.error(
2926
+ "[axl] onAgentCallComplete hook threw; ask outcome unchanged:",
2927
+ hookErr instanceof Error ? hookErr.message : String(hookErr)
2928
+ );
2929
+ }
2930
+ }
2931
+ return result;
2932
+ } catch (err) {
2933
+ outcome = {
2934
+ ok: false,
2935
+ error: err instanceof Error ? err.message : String(err)
2936
+ };
2937
+ throw err;
2938
+ } finally {
2939
+ this.emitEvent({
2940
+ type: "ask_end",
2941
+ outcome: outcome ?? {
2942
+ ok: false,
2943
+ error: "ask_end emitted without outcome \u2014 internal bug"
2944
+ },
2945
+ cost: frame.askCost.value,
2946
+ duration: Date.now() - askStart
2947
+ });
2348
2948
  }
2349
- return r;
2350
- }
2351
- ) : await doCall();
2352
- const costAfter = this.budgetContext?.totalCost ?? 0;
2353
- this.onAgentCallComplete?.({
2354
- agent: agent2._name,
2355
- prompt,
2356
- response: typeof result === "string" ? result : JSON.stringify(result),
2357
- model: agent2.resolveModel(resolveCtx),
2358
- cost: costAfter - costBefore,
2359
- duration: Date.now() - startTime,
2360
- promptVersion: agent2._config.version,
2361
- temperature: options?.temperature ?? agent2._config.temperature,
2362
- maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
2363
- effort: options?.effort ?? agent2._config.effort,
2364
- thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
2365
- includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
2366
- toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
2367
- stop: options?.stop ?? agent2._config.stop,
2368
- providerOptions: options?.providerOptions ?? agent2._config.providerOptions
2369
- });
2370
- return result;
2371
- });
2949
+ });
2950
+ },
2951
+ { agent: agentName }
2952
+ );
2372
2953
  }
2373
2954
  async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
2374
2955
  if (this.budgetContext?.exceeded) {
2375
2956
  const { limit, totalCost: spent, policy } = this.budgetContext;
2376
2957
  if (policy === "warn") {
2377
- this.emitTrace({
2958
+ this.emitEvent({
2378
2959
  type: "log",
2379
2960
  data: { warning: "Budget exceeded", limit, spent, policy }
2380
2961
  });
@@ -2463,7 +3044,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2463
3044
  const guardrails = agent2._config.guardrails;
2464
3045
  if (guardrails?.input) {
2465
3046
  const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
2466
- this.emitTrace({
3047
+ this.emitEvent({
2467
3048
  type: "guardrail",
2468
3049
  agent: agent2._name,
2469
3050
  data: {
@@ -2497,12 +3078,6 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2497
3078
  const maxTurns = agent2._config.maxTurns ?? 25;
2498
3079
  const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
2499
3080
  const startTime = Date.now();
2500
- if (this.onToken && options?.validate) {
2501
- throw new AxlError(
2502
- "INVALID_CONFIG",
2503
- "Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
2504
- );
2505
- }
2506
3081
  const currentMessages = [...messages];
2507
3082
  let turns = 0;
2508
3083
  let guardrailOutputRetries = 0;
@@ -2511,11 +3086,42 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2511
3086
  const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
2512
3087
  let pendingRetryReason;
2513
3088
  const verboseTrace = this.config.trace?.level === "full";
3089
+ let lastStartStage = "initial";
3090
+ let lastStartAttempt = 1;
3091
+ let lastStartMaxAttempts = 1;
2514
3092
  while (turns < maxTurns) {
2515
3093
  if (Date.now() - startTime > timeoutMs) {
2516
3094
  throw new TimeoutError("ctx.ask()", timeoutMs);
2517
3095
  }
2518
3096
  turns++;
3097
+ const isFirstTurn = turns === 1;
3098
+ const isRetryTurn = pendingRetryReason !== void 0;
3099
+ if (isFirstTurn || isRetryTurn) {
3100
+ const stage = pendingRetryReason ?? "initial";
3101
+ let pipelineAttempt = 1;
3102
+ let pipelineMaxAttempts = 1;
3103
+ if (stage === "guardrail") {
3104
+ pipelineAttempt = guardrailOutputRetries + 1;
3105
+ pipelineMaxAttempts = maxGuardrailRetries + 1;
3106
+ } else if (stage === "schema") {
3107
+ pipelineAttempt = schemaRetries + 1;
3108
+ pipelineMaxAttempts = (options?.retries ?? 3) + 1;
3109
+ } else if (stage === "validate") {
3110
+ pipelineAttempt = validateRetries + 1;
3111
+ pipelineMaxAttempts = (options?.validateRetries ?? 2) + 1;
3112
+ }
3113
+ lastStartStage = stage;
3114
+ lastStartAttempt = pipelineAttempt;
3115
+ lastStartMaxAttempts = pipelineMaxAttempts;
3116
+ this.emitEvent({
3117
+ type: "pipeline",
3118
+ agent: agent2._name,
3119
+ status: "start",
3120
+ stage,
3121
+ attempt: pipelineAttempt,
3122
+ maxAttempts: pipelineMaxAttempts
3123
+ });
3124
+ }
2519
3125
  const turnStart = Date.now();
2520
3126
  const chatOptions = {
2521
3127
  model,
@@ -2533,72 +3139,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2533
3139
  if (options?.schema && toolDefs.length === 0) {
2534
3140
  chatOptions.responseFormat = { type: "json_object" };
2535
3141
  }
2536
- this.onAgentStart?.({ agent: agent2._name, model: modelUri });
2537
- let response;
2538
- if (this.onToken) {
2539
- let content2 = "";
2540
- const toolCalls = [];
2541
- const toolCallBuffers = /* @__PURE__ */ new Map();
2542
- let streamProviderMetadata;
2543
- let thinkingContent = "";
2544
- for await (const chunk of provider.stream(currentMessages, chatOptions)) {
2545
- if (chunk.type === "text_delta") {
2546
- content2 += chunk.content;
2547
- this.onToken(chunk.content);
2548
- } else if (chunk.type === "thinking_delta") {
2549
- thinkingContent += chunk.content;
2550
- } else if (chunk.type === "tool_call_delta") {
2551
- let buffer = toolCallBuffers.get(chunk.id);
2552
- if (!buffer) {
2553
- buffer = { id: chunk.id, name: "", arguments: "" };
2554
- toolCallBuffers.set(chunk.id, buffer);
2555
- }
2556
- if (chunk.name) buffer.name = chunk.name;
2557
- if (chunk.arguments) buffer.arguments += chunk.arguments;
2558
- } else if (chunk.type === "done") {
2559
- streamProviderMetadata = chunk.providerMetadata;
2560
- if (chunk.usage) {
2561
- response = {
2562
- content: content2,
2563
- tool_calls: void 0,
2564
- usage: chunk.usage,
2565
- cost: chunk.cost
2566
- };
2567
- }
2568
- }
2569
- }
2570
- for (const buffer of toolCallBuffers.values()) {
2571
- toolCalls.push({
2572
- id: buffer.id,
2573
- type: "function",
2574
- function: {
2575
- name: buffer.name,
2576
- arguments: buffer.arguments
2577
- }
2578
- });
2579
- }
2580
- response ??= {
2581
- content: content2,
2582
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
2583
- };
2584
- if (toolCalls.length > 0) {
2585
- response.tool_calls = toolCalls;
2586
- }
2587
- if (streamProviderMetadata) {
2588
- response.providerMetadata = streamProviderMetadata;
2589
- }
2590
- if (thinkingContent) {
2591
- response.thinking_content = thinkingContent;
2592
- }
2593
- } else {
2594
- response = await provider.chat(currentMessages, chatOptions);
2595
- }
2596
- if (usageCapture && response.usage) {
2597
- usageCapture.value = response.usage;
2598
- }
2599
- if (response.cost) {
2600
- this._accumulateBudgetCost(response.cost);
2601
- }
3142
+ const callbackMeta = this.currentCallbackMeta(agent2._name);
3143
+ const callParams = {
3144
+ ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
3145
+ maxTokens: chatOptions.maxTokens,
3146
+ ...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
3147
+ ...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
3148
+ ...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
3149
+ ...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
3150
+ ...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
3151
+ };
2602
3152
  let messagesSnapshot;
2603
3153
  if (verboseTrace) {
2604
3154
  try {
@@ -2613,8 +3163,148 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2613
3163
  }
2614
3164
  const retryReason = pendingRetryReason;
2615
3165
  pendingRetryReason = void 0;
2616
- this.emitTrace({
2617
- type: "agent_call",
3166
+ this.emitEvent({
3167
+ type: "agent_call_start",
3168
+ agent: agent2._name,
3169
+ model: modelUri,
3170
+ turn: turns,
3171
+ data: {
3172
+ prompt,
3173
+ ...systemPrompt ? { system: systemPrompt } : {},
3174
+ params: callParams,
3175
+ turn: turns,
3176
+ ...retryReason ? { retryReason } : {},
3177
+ ...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
3178
+ ...messagesSnapshot ? { messages: messagesSnapshot } : {}
3179
+ }
3180
+ });
3181
+ this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
3182
+ let response;
3183
+ try {
3184
+ if (this.onToken) {
3185
+ let content2 = "";
3186
+ const toolCalls = [];
3187
+ const toolCallBuffers = /* @__PURE__ */ new Map();
3188
+ let streamProviderMetadata;
3189
+ let thinkingContent = "";
3190
+ const partialObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
3191
+ const currentAttempt = schemaRetries + 1;
3192
+ let inString = false;
3193
+ let escaped = false;
3194
+ let boundaryPending = false;
3195
+ for await (const chunk of provider.stream(currentMessages, chatOptions)) {
3196
+ if (chunk.type === "text_delta") {
3197
+ content2 += chunk.content;
3198
+ this.emitEvent({ type: "token", data: chunk.content });
3199
+ this.onToken(chunk.content, callbackMeta);
3200
+ if (partialObjectEnabled) {
3201
+ for (const ch of chunk.content) {
3202
+ if (escaped) {
3203
+ escaped = false;
3204
+ continue;
3205
+ }
3206
+ if (ch === "\\") {
3207
+ escaped = true;
3208
+ continue;
3209
+ }
3210
+ if (ch === '"') {
3211
+ inString = !inString;
3212
+ continue;
3213
+ }
3214
+ if (!inString && (ch === "," || ch === "}" || ch === "]")) {
3215
+ boundaryPending = true;
3216
+ }
3217
+ }
3218
+ if (boundaryPending) {
3219
+ boundaryPending = false;
3220
+ let parsed;
3221
+ try {
3222
+ parsed = parsePartialJson(extractJson(content2));
3223
+ } catch {
3224
+ parsed = void 0;
3225
+ }
3226
+ if (parsed !== void 0) {
3227
+ this.emitEvent({
3228
+ type: "partial_object",
3229
+ agent: agent2._name,
3230
+ attempt: currentAttempt,
3231
+ data: { object: parsed }
3232
+ });
3233
+ }
3234
+ }
3235
+ }
3236
+ } else if (chunk.type === "thinking_delta") {
3237
+ thinkingContent += chunk.content;
3238
+ } else if (chunk.type === "tool_call_delta") {
3239
+ let buffer = toolCallBuffers.get(chunk.id);
3240
+ if (!buffer) {
3241
+ buffer = { id: chunk.id, name: "", arguments: "" };
3242
+ toolCallBuffers.set(chunk.id, buffer);
3243
+ }
3244
+ if (chunk.name) buffer.name = chunk.name;
3245
+ if (chunk.arguments) buffer.arguments += chunk.arguments;
3246
+ } else if (chunk.type === "done") {
3247
+ streamProviderMetadata = chunk.providerMetadata;
3248
+ if (chunk.usage) {
3249
+ response = {
3250
+ content: content2,
3251
+ tool_calls: void 0,
3252
+ usage: chunk.usage,
3253
+ cost: chunk.cost
3254
+ };
3255
+ }
3256
+ }
3257
+ }
3258
+ for (const buffer of toolCallBuffers.values()) {
3259
+ toolCalls.push({
3260
+ id: buffer.id,
3261
+ type: "function",
3262
+ function: {
3263
+ name: buffer.name,
3264
+ arguments: buffer.arguments
3265
+ }
3266
+ });
3267
+ }
3268
+ response ??= {
3269
+ content: content2,
3270
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
3271
+ };
3272
+ if (toolCalls.length > 0) {
3273
+ response.tool_calls = toolCalls;
3274
+ }
3275
+ if (streamProviderMetadata) {
3276
+ response.providerMetadata = streamProviderMetadata;
3277
+ }
3278
+ if (thinkingContent) {
3279
+ response.thinking_content = thinkingContent;
3280
+ }
3281
+ } else {
3282
+ response = await provider.chat(currentMessages, chatOptions);
3283
+ }
3284
+ } catch (err) {
3285
+ this.emitEvent({
3286
+ type: "agent_call_end",
3287
+ agent: agent2._name,
3288
+ model: modelUri,
3289
+ promptVersion: agent2._config.version,
3290
+ duration: Date.now() - turnStart,
3291
+ data: {
3292
+ response: "",
3293
+ turn: turns,
3294
+ ...retryReason ? { retryReason } : {},
3295
+ error: err instanceof Error ? err.message : String(err)
3296
+ }
3297
+ });
3298
+ throw err;
3299
+ }
3300
+ if (usageCapture && response.usage) {
3301
+ usageCapture.value = response.usage;
3302
+ }
3303
+ if (response.cost) {
3304
+ this._accumulateBudgetCost(response.cost);
3305
+ }
3306
+ this.emitEvent({
3307
+ type: "agent_call_end",
2618
3308
  agent: agent2._name,
2619
3309
  model: modelUri,
2620
3310
  promptVersion: agent2._config.version,
@@ -2626,22 +3316,10 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2626
3316
  } : void 0,
2627
3317
  duration: Date.now() - turnStart,
2628
3318
  data: {
2629
- prompt,
2630
3319
  response: response.content,
2631
- ...systemPrompt ? { system: systemPrompt } : {},
2632
3320
  ...response.thinking_content ? { thinking: response.thinking_content } : {},
2633
- params: {
2634
- ...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
2635
- maxTokens: chatOptions.maxTokens,
2636
- ...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
2637
- ...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
2638
- ...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
2639
- ...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
2640
- ...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
2641
- },
2642
3321
  turn: turns,
2643
- ...retryReason ? { retryReason } : {},
2644
- ...messagesSnapshot ? { messages: messagesSnapshot } : {}
3322
+ ...retryReason ? { retryReason } : {}
2645
3323
  }
2646
3324
  });
2647
3325
  if (response.tool_calls && response.tool_calls.length > 0) {
@@ -2667,6 +3345,19 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2667
3345
  }
2668
3346
  }
2669
3347
  const handoffStart = Date.now();
3348
+ const sourceFrame = askStorage.getStore();
3349
+ const handoffFromAskId = sourceFrame?.askId ?? this.executionId;
3350
+ const handoffSourceDepth = sourceFrame?.depth ?? 0;
3351
+ const handoffToAskId = randomUUID();
3352
+ const handoffTargetDepth = handoffSourceDepth + 1;
3353
+ const targetFrame = {
3354
+ askId: handoffToAskId,
3355
+ parentAskId: handoffFromAskId,
3356
+ depth: handoffTargetDepth,
3357
+ agent: descriptor.agent._name,
3358
+ stepRef: sourceFrame?.stepRef ?? this.stepRefRoot,
3359
+ askCost: { value: 0 }
3360
+ };
2670
3361
  const handoffOptions = options ? {
2671
3362
  schema: options.schema,
2672
3363
  retries: options.retries,
@@ -2674,13 +3365,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2674
3365
  validate: options.validate,
2675
3366
  validateRetries: options.validateRetries
2676
3367
  } : void 0;
2677
- const handoffFn = () => this.executeAgentCall(
2678
- descriptor.agent,
2679
- handoffPrompt,
2680
- handoffOptions,
2681
- currentMessages,
2682
- usageCapture
2683
- );
3368
+ const handoffFn = () => askStorage.run(targetFrame, async () => {
3369
+ this.emitEvent({ type: "ask_start", prompt: handoffPrompt });
3370
+ const targetAskStart = Date.now();
3371
+ try {
3372
+ const result = await this.executeAgentCall(
3373
+ descriptor.agent,
3374
+ handoffPrompt,
3375
+ handoffOptions,
3376
+ currentMessages,
3377
+ usageCapture
3378
+ );
3379
+ this.emitEvent({
3380
+ type: "ask_end",
3381
+ outcome: { ok: true, result },
3382
+ cost: targetFrame.askCost.value,
3383
+ duration: Date.now() - targetAskStart
3384
+ });
3385
+ return result;
3386
+ } catch (err) {
3387
+ this.emitEvent({
3388
+ type: "ask_end",
3389
+ outcome: {
3390
+ ok: false,
3391
+ error: err instanceof Error ? err.message : String(err)
3392
+ },
3393
+ cost: targetFrame.askCost.value,
3394
+ duration: Date.now() - targetAskStart
3395
+ });
3396
+ throw err;
3397
+ }
3398
+ });
3399
+ this.emitEvent({
3400
+ type: "handoff_start",
3401
+ agent: agent2._name,
3402
+ fromAskId: handoffFromAskId,
3403
+ toAskId: handoffToAskId,
3404
+ sourceDepth: handoffSourceDepth,
3405
+ targetDepth: handoffTargetDepth,
3406
+ data: {
3407
+ source: agent2._name,
3408
+ target: targetName,
3409
+ mode,
3410
+ ...mode === "roundtrip" && handoffPrompt !== prompt ? { message: handoffPrompt } : {}
3411
+ }
3412
+ });
2684
3413
  if (mode === "roundtrip") {
2685
3414
  const executeRoundtrip = async () => {
2686
3415
  const result = await handoffFn();
@@ -2701,36 +3430,41 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2701
3430
  "axl.handoff.mode": mode
2702
3431
  },
2703
3432
  async (span) => {
2704
- const result = await executeRoundtrip();
2705
- const duration = Date.now() - handoffStart;
2706
- span.setAttribute("axl.handoff.duration", duration);
2707
- this.emitTrace({
2708
- type: "handoff",
2709
- agent: agent2._name,
2710
- data: {
2711
- target: targetName,
2712
- mode,
2713
- duration,
2714
- source: agent2._name,
2715
- ...handoffPrompt !== prompt ? { message: handoffPrompt } : {}
2716
- }
2717
- });
2718
- return result;
3433
+ try {
3434
+ return await executeRoundtrip();
3435
+ } finally {
3436
+ const duration = Date.now() - handoffStart;
3437
+ span.setAttribute("axl.handoff.duration", duration);
3438
+ this.emitEvent({
3439
+ type: "handoff_return",
3440
+ agent: agent2._name,
3441
+ fromAskId: handoffFromAskId,
3442
+ toAskId: handoffToAskId,
3443
+ sourceDepth: handoffSourceDepth,
3444
+ targetDepth: handoffTargetDepth,
3445
+ data: { source: agent2._name, target: targetName, duration }
3446
+ });
3447
+ }
2719
3448
  }
2720
3449
  );
2721
3450
  } else {
2722
- await executeRoundtrip();
2723
- this.emitTrace({
2724
- type: "handoff",
2725
- agent: agent2._name,
2726
- data: {
2727
- target: targetName,
2728
- mode,
2729
- duration: Date.now() - handoffStart,
2730
- source: agent2._name,
2731
- ...handoffPrompt !== prompt ? { message: handoffPrompt } : {}
2732
- }
2733
- });
3451
+ try {
3452
+ await executeRoundtrip();
3453
+ } finally {
3454
+ this.emitEvent({
3455
+ type: "handoff_return",
3456
+ agent: agent2._name,
3457
+ fromAskId: handoffFromAskId,
3458
+ toAskId: handoffToAskId,
3459
+ sourceDepth: handoffSourceDepth,
3460
+ targetDepth: handoffTargetDepth,
3461
+ data: {
3462
+ source: agent2._name,
3463
+ target: targetName,
3464
+ duration: Date.now() - handoffStart
3465
+ }
3466
+ });
3467
+ }
2734
3468
  }
2735
3469
  continue;
2736
3470
  }
@@ -2744,29 +3478,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2744
3478
  },
2745
3479
  async (span) => {
2746
3480
  const result = await handoffFn();
2747
- const duration = Date.now() - handoffStart;
2748
- span.setAttribute("axl.handoff.duration", duration);
2749
- this.emitTrace({
2750
- type: "handoff",
2751
- agent: agent2._name,
2752
- data: { target: targetName, mode, duration, source: agent2._name }
2753
- });
3481
+ span.setAttribute("axl.handoff.duration", Date.now() - handoffStart);
2754
3482
  return result;
2755
3483
  }
2756
3484
  );
2757
3485
  }
2758
- const onewayResult = await handoffFn();
2759
- this.emitTrace({
2760
- type: "handoff",
2761
- agent: agent2._name,
2762
- data: {
2763
- target: targetName,
2764
- mode,
2765
- duration: Date.now() - handoffStart,
2766
- source: agent2._name
2767
- }
2768
- });
2769
- return onewayResult;
3486
+ return await handoffFn();
2770
3487
  }
2771
3488
  }
2772
3489
  const toolOverride = this.toolOverrides?.get(toolName);
@@ -2782,7 +3499,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2782
3499
  });
2783
3500
  continue;
2784
3501
  }
2785
- this.onToolCall?.({ name: toolName, args: toolArgs2, callId: toolCall.id });
3502
+ this.emitEvent({
3503
+ type: "tool_call_start",
3504
+ tool: toolName,
3505
+ callId: toolCall.id,
3506
+ data: { args: toolArgs2 }
3507
+ });
3508
+ this.onToolCall?.(
3509
+ { name: toolName, args: toolArgs2, callId: toolCall.id },
3510
+ callbackMeta
3511
+ );
2786
3512
  const toolStart2 = Date.now();
2787
3513
  const executeOverride = async () => {
2788
3514
  let toolResult3;
@@ -2810,8 +3536,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2810
3536
  }
2811
3537
  ) : await executeOverride();
2812
3538
  const resultContent2 = JSON.stringify(toolResult2);
2813
- this.emitTrace({
2814
- type: "tool_call",
3539
+ this.emitEvent({
3540
+ type: "tool_call_end",
2815
3541
  agent: agent2._name,
2816
3542
  tool: toolName,
2817
3543
  duration: Date.now() - toolStart2,
@@ -2827,7 +3553,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2827
3553
  const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
2828
3554
  const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
2829
3555
  if (!tool2 && !isMcpTool) {
2830
- this.emitTrace({ type: "tool_denied", agent: agent2._name, tool: toolName });
3556
+ this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
2831
3557
  currentMessages.push({
2832
3558
  role: "tool",
2833
3559
  content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
@@ -2846,7 +3572,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2846
3572
  });
2847
3573
  continue;
2848
3574
  }
2849
- this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id });
3575
+ this.emitEvent({
3576
+ type: "tool_call_start",
3577
+ tool: toolName,
3578
+ callId: toolCall.id,
3579
+ data: { args: toolArgs }
3580
+ });
3581
+ this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
2850
3582
  const toolStart = Date.now();
2851
3583
  if (tool2 && tool2.requireApproval) {
2852
3584
  const approvalFn = async () => {
@@ -2883,7 +3615,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2883
3615
  } else {
2884
3616
  approvalOutcome = await approvalFn();
2885
3617
  }
2886
- this.emitTrace({
3618
+ this.emitEvent({
2887
3619
  type: "tool_approval",
2888
3620
  agent: agent2._name,
2889
3621
  tool: toolName,
@@ -2926,7 +3658,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2926
3658
  resultContent2 = JSON.stringify(toolResult2);
2927
3659
  }
2928
3660
  } else if (tool2) {
2929
- const childCtx = this.createChildContext(toolCall.id);
3661
+ const childCtx = this.createChildContext();
2930
3662
  try {
2931
3663
  toolResult2 = await tool2._execute(toolArgs, childCtx);
2932
3664
  } catch (err) {
@@ -2968,8 +3700,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2968
3700
  return r;
2969
3701
  }
2970
3702
  ) : await executeTool();
2971
- this.emitTrace({
2972
- type: "tool_call",
3703
+ this.emitEvent({
3704
+ type: "tool_call_end",
2973
3705
  agent: agent2._name,
2974
3706
  tool: traceName,
2975
3707
  duration: Date.now() - toolStart,
@@ -2993,7 +3725,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
2993
3725
  if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
2994
3726
  feedbackMessage = `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`;
2995
3727
  }
2996
- this.emitTrace({
3728
+ this.emitEvent({
2997
3729
  type: "guardrail",
2998
3730
  agent: agent2._name,
2999
3731
  data: {
@@ -3014,6 +3746,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3014
3746
  });
3015
3747
  if (outputResult.block) {
3016
3748
  if (feedbackMessage) {
3749
+ this.emitEvent({
3750
+ type: "pipeline",
3751
+ agent: agent2._name,
3752
+ status: "failed",
3753
+ stage: "guardrail",
3754
+ attempt: guardrailOutputRetries + 1,
3755
+ maxAttempts: maxGuardrailRetries + 1,
3756
+ reason: feedbackMessage
3757
+ });
3017
3758
  guardrailOutputRetries++;
3018
3759
  appendRetryMessages(
3019
3760
  currentMessages,
@@ -3052,7 +3793,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3052
3793
  schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
3053
3794
  }
3054
3795
  }
3055
- this.emitTrace({
3796
+ this.emitEvent({
3056
3797
  type: "schema_check",
3057
3798
  agent: agent2._name,
3058
3799
  data: {
@@ -3071,6 +3812,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3071
3812
  });
3072
3813
  if (!schemaValid) {
3073
3814
  if (schemaFeedback) {
3815
+ this.emitEvent({
3816
+ type: "pipeline",
3817
+ agent: agent2._name,
3818
+ status: "failed",
3819
+ stage: "schema",
3820
+ attempt: schemaRetries + 1,
3821
+ maxAttempts: (options.retries ?? 3) + 1,
3822
+ reason: schemaFeedback
3823
+ });
3074
3824
  schemaRetries++;
3075
3825
  appendRetryMessages(
3076
3826
  currentMessages,
@@ -3108,7 +3858,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3108
3858
  if (!validateResult.valid && validateRetries < maxValidateRetries) {
3109
3859
  validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
3110
3860
  }
3111
- this.emitTrace({
3861
+ this.emitEvent({
3112
3862
  type: "validate",
3113
3863
  agent: agent2._name,
3114
3864
  data: {
@@ -3127,6 +3877,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3127
3877
  });
3128
3878
  if (!validateResult.valid) {
3129
3879
  if (validateFeedback) {
3880
+ this.emitEvent({
3881
+ type: "pipeline",
3882
+ agent: agent2._name,
3883
+ status: "failed",
3884
+ stage: "validate",
3885
+ attempt: validateRetries + 1,
3886
+ maxAttempts: (options.validateRetries ?? 2) + 1,
3887
+ reason: validateFeedback
3888
+ });
3130
3889
  validateRetries++;
3131
3890
  appendRetryMessages(
3132
3891
  currentMessages,
@@ -3144,6 +3903,14 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3144
3903
  );
3145
3904
  }
3146
3905
  }
3906
+ this.emitEvent({
3907
+ type: "pipeline",
3908
+ agent: agent2._name,
3909
+ status: "committed",
3910
+ stage: lastStartStage,
3911
+ attempt: lastStartAttempt,
3912
+ maxAttempts: lastStartMaxAttempts
3913
+ });
3147
3914
  this.pushAssistantToSessionHistory(content, response.providerMetadata);
3148
3915
  return validated ?? content;
3149
3916
  }
@@ -3272,43 +4039,100 @@ ${summaryResponse.content}`
3272
4039
  /**
3273
4040
  * Execute a function with checkpoint-replay semantics.
3274
4041
  *
3275
- * On first execution, runs `fn()`, saves the result, and returns it.
3276
- * On replay (resume after restart), returns the saved result without re-executing.
3277
- * This prevents duplicate side effects (double API calls, double refunds, etc.).
4042
+ * On first execution, runs `fn()`, saves the result under `name`, and
4043
+ * returns it. On replay (resume after restart), returns the saved result
4044
+ * without re-executing. This prevents duplicate side effects (double API
4045
+ * calls, double refunds, etc.).
4046
+ *
4047
+ * `name` must be a stable, caller-supplied identifier — the same call
4048
+ * site MUST pass the same name across runs of the same execution for
4049
+ * replay to work. Names are scoped to a single execution and must be
4050
+ * unique within it; reusing a name (e.g. inside a loop) gives
4051
+ * last-write-wins behavior. For loops, compose names from the iterator:
4052
+ * `ctx.checkpoint(`process-${id}`, fn)`.
4053
+ *
4054
+ * Names starting with `__auto/` are reserved for runtime-internal
4055
+ * auto-checkpointing of `ctx.ask` / spawn / race / parallel / map.
3278
4056
  */
3279
- async checkpoint(fn) {
3280
- return this._checkpoint(fn);
4057
+ async checkpoint(name, fn) {
4058
+ if (typeof name !== "string" || name.length === 0) {
4059
+ throw new Error("ctx.checkpoint name must be a non-empty string.");
4060
+ }
4061
+ if (name !== name.trim()) {
4062
+ throw new Error(
4063
+ `ctx.checkpoint name "${name}" has leading/trailing whitespace \u2014 trim before passing.`
4064
+ );
4065
+ }
4066
+ if (name.toLowerCase().startsWith("__auto/")) {
4067
+ throw new Error(
4068
+ `ctx.checkpoint name "${name}" is reserved \u2014 names starting with "__auto/" (case-insensitive) are used by the runtime for ask/spawn/race/parallel/map auto-checkpointing.`
4069
+ );
4070
+ }
4071
+ return this._checkpoint(name, fn);
3281
4072
  }
3282
4073
  /**
3283
- * Internal checkpoint implementation shared by both the public checkpoint()
3284
- * and the automatic checkpointing in ask/spawn/race/parallel/map.
4074
+ * Internal checkpoint implementation shared by both the public
4075
+ * `checkpoint(name, fn)` and the automatic checkpointing in
4076
+ * ask/spawn/race/parallel/map. Internal callers pass an `__auto/...`
4077
+ * name composed by `_autoCheckpointName`.
4078
+ *
4079
+ * `agent` is the originating agent for ask auto-checkpoints — passing
4080
+ * it explicitly stamps `event.agent` on the trace events even though
4081
+ * the emit fires before/after `fn()` (where the ask's ALS frame
4082
+ * either doesn't exist yet or has already torn down). Without this,
4083
+ * post-fn `checkpoint_save` rows show no agent in the trace UI.
3285
4084
  */
3286
- async _checkpoint(fn) {
3287
- const step = this.checkpointCounter++;
4085
+ async _checkpoint(name, fn, options) {
3288
4086
  if (!this.stateStore) {
3289
4087
  return fn();
3290
4088
  }
3291
- const saved = await this.stateStore.getCheckpoint(this.executionId, step);
4089
+ const agentStamp = options?.agent ? { agent: options.agent } : {};
4090
+ const saved = await this.stateStore.getCheckpoint(this.executionId, name);
3292
4091
  if (saved !== null) {
3293
- this.emitTrace({
3294
- type: "log",
3295
- data: { event: "checkpoint_replay", step }
4092
+ this.emitEvent({
4093
+ type: "checkpoint_replay",
4094
+ ...agentStamp,
4095
+ data: { name }
3296
4096
  });
3297
- this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.step": step });
4097
+ this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.name": name });
3298
4098
  return saved;
3299
4099
  }
3300
4100
  const result = await fn();
3301
- await this.stateStore.saveCheckpoint(this.executionId, step, result);
3302
- this.emitTrace({
3303
- type: "log",
3304
- data: { event: "checkpoint_save", step }
4101
+ await this.stateStore.saveCheckpoint(this.executionId, name, result);
4102
+ this.emitEvent({
4103
+ type: "checkpoint_save",
4104
+ ...agentStamp,
4105
+ data: { name }
3305
4106
  });
3306
- this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.step": step });
4107
+ this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.name": name });
3307
4108
  return result;
3308
4109
  }
4110
+ /** Generate a stable auto-checkpoint name for an internal primitive.
4111
+ *
4112
+ * For `ctx.ask`, the originating agent is known and gets a per-agent
4113
+ * counter — the resulting name reads as "orchestrator's first ask"
4114
+ * (`__auto/orchestrator-agent/ask/0`) instead of a global ordinal.
4115
+ * For `spawn` / `race` / `parallel` / `map`, no agent context is
4116
+ * available (those wrap arbitrary fn callbacks), so the name uses
4117
+ * the root counter: `__auto/spawn/0`, `__auto/race/3`, etc.
4118
+ *
4119
+ * All counters live on a shared ref passed through `createChildContext`,
4120
+ * so a tool handler's nested `ctx.ask()` (which runs in a child
4121
+ * WorkflowContext) can never collide with the parent's auto-names in
4122
+ * the state store. */
4123
+ _autoCheckpointName(primitive, agent2) {
4124
+ const counters = this.autoCheckpointCounters;
4125
+ if (primitive === "ask" && agent2) {
4126
+ const n2 = counters.byAgent.get(agent2) ?? 0;
4127
+ counters.byAgent.set(agent2, n2 + 1);
4128
+ return `__auto/${agent2}/ask/${n2}`;
4129
+ }
4130
+ const n = counters.root++;
4131
+ return `__auto/${primitive}/${n}`;
4132
+ }
3309
4133
  // ── ctx.spawn() ───────────────────────────────────────────────────────
3310
4134
  async spawn(n, fn, options) {
3311
- return this._checkpoint(() => {
4135
+ return this._checkpoint(this._autoCheckpointName("spawn"), () => {
3312
4136
  if (this.spanManager) {
3313
4137
  return this.spanManager.withSpanAsync(
3314
4138
  "axl.ctx.spawn",
@@ -3504,7 +4328,7 @@ ${summaryResponse.content}`
3504
4328
  const maxRetries = options?.retries ?? 3;
3505
4329
  let lastRetry = void 0;
3506
4330
  const emitVerifyOutcome = (passed, attempts, lastError) => {
3507
- this.emitTrace({
4331
+ this.emitEvent({
3508
4332
  type: "verify",
3509
4333
  agent: void 0,
3510
4334
  data: {
@@ -3644,7 +4468,7 @@ ${summaryResponse.content}`
3644
4468
  }
3645
4469
  // ── ctx.race() ────────────────────────────────────────────────────────
3646
4470
  async race(fns, options) {
3647
- return this._checkpoint(() => {
4471
+ return this._checkpoint(this._autoCheckpointName("race"), () => {
3648
4472
  if (this.spanManager) {
3649
4473
  return this.spanManager.withSpanAsync(
3650
4474
  "axl.ctx.race",
@@ -3749,11 +4573,17 @@ ${summaryResponse.content}`
3749
4573
  }
3750
4574
  // ── ctx.parallel() ────────────────────────────────────────────────────
3751
4575
  async parallel(fns) {
3752
- return this._checkpoint(() => Promise.all(fns.map((fn) => fn())));
4576
+ return this._checkpoint(
4577
+ this._autoCheckpointName("parallel"),
4578
+ () => Promise.all(fns.map((fn) => fn()))
4579
+ );
3753
4580
  }
3754
4581
  // ── ctx.map() ─────────────────────────────────────────────────────────
3755
4582
  async map(items, fn, options) {
3756
- return this._checkpoint(() => this._mapImpl(items, fn, options));
4583
+ return this._checkpoint(
4584
+ this._autoCheckpointName("map"),
4585
+ () => this._mapImpl(items, fn, options)
4586
+ );
3757
4587
  }
3758
4588
  async _mapImpl(items, fn, options) {
3759
4589
  const concurrency = options?.concurrency ?? 5;
@@ -3831,10 +4661,14 @@ ${summaryResponse.content}`
3831
4661
  }
3832
4662
  async _awaitHumanImpl(options) {
3833
4663
  if (this.awaitHumanHandler) {
4664
+ this.emitEvent({
4665
+ type: "await_human",
4666
+ data: { channel: options.channel, prompt: options.prompt }
4667
+ });
3834
4668
  const decision2 = await this.awaitHumanHandler(options);
3835
- this.emitTrace({
3836
- type: "log",
3837
- data: { event: "await_human_resolved", channel: options.channel, decision: decision2 }
4669
+ this.emitEvent({
4670
+ type: "await_human_resolved",
4671
+ data: { channel: options.channel, decision: decision2 }
3838
4672
  });
3839
4673
  return decision2;
3840
4674
  }
@@ -3854,7 +4688,7 @@ ${summaryResponse.content}`
3854
4688
  await this.stateStore.saveExecutionState(this.executionId, {
3855
4689
  workflow: this.workflowName ?? "unknown",
3856
4690
  input: this.input,
3857
- step: this.stepCounter,
4691
+ step: this.stepRefRoot.value,
3858
4692
  status: "waiting",
3859
4693
  metadata: {
3860
4694
  ...this.metadata,
@@ -3863,18 +4697,22 @@ ${summaryResponse.content}`
3863
4697
  }
3864
4698
  });
3865
4699
  }
3866
- this.emitTrace({
3867
- type: "log",
3868
- data: { event: "await_human", channel: options.channel, prompt: options.prompt }
4700
+ this.emitEvent({
4701
+ type: "await_human",
4702
+ data: { channel: options.channel, prompt: options.prompt }
3869
4703
  });
3870
4704
  const decision = await new Promise((resolve) => {
3871
4705
  this.pendingDecisions.set(this.executionId, resolve);
3872
4706
  });
4707
+ this.emitEvent({
4708
+ type: "await_human_resolved",
4709
+ data: { channel: options.channel, decision }
4710
+ });
3873
4711
  if (this.stateStore) {
3874
4712
  await this.stateStore.saveExecutionState(this.executionId, {
3875
4713
  workflow: this.workflowName ?? "unknown",
3876
4714
  input: this.input,
3877
- step: this.stepCounter,
4715
+ step: this.stepRefRoot.value,
3878
4716
  status: "running"
3879
4717
  });
3880
4718
  }
@@ -3927,17 +4765,28 @@ ${summaryResponse.content}`
3927
4765
  // the previous `ctx.log('workflow_start', ...)` indirection — so consumers
3928
4766
  // that narrow via `event.type === 'workflow_start'` actually see them.
3929
4767
  // Internal: the runtime is the only caller, user workflows never call these.
3930
- /** @internal */
4768
+ /** @internal — idempotent; no-ops on second+ call within one ctx. */
3931
4769
  _emitWorkflowStart(input) {
3932
- this.emitTrace({
4770
+ if (this._workflowStartEmitted) return;
4771
+ this._workflowStartEmitted = true;
4772
+ this.emitEvent({
3933
4773
  type: "workflow_start",
3934
4774
  workflow: this.workflowName,
3935
4775
  data: { input }
3936
4776
  });
3937
4777
  }
3938
- /** @internal */
4778
+ /** @internal — idempotent; no-ops on second+ call within one ctx.
4779
+ * Protects against the post-emit-side-effect double-fire (reviewer
4780
+ * bug B1): `runtime.execute()` / `runtime.stream()` emit
4781
+ * `workflow_end(completed)`, then call `deleteCheckpoints` /
4782
+ * `persistExecution`. If either throws, the outer catch would
4783
+ * otherwise fire a second `workflow_end(failed)` with conflicting
4784
+ * status. First-wins semantics: the completed event stands, the
4785
+ * inner side-effect failure propagates as a normal thrown error. */
3939
4786
  _emitWorkflowEnd(info) {
3940
- this.emitTrace({
4787
+ if (this._workflowEndEmitted) return;
4788
+ this._workflowEndEmitted = true;
4789
+ this.emitEvent({
3941
4790
  type: "workflow_end",
3942
4791
  workflow: this.workflowName,
3943
4792
  duration: info.duration,
@@ -3952,7 +4801,7 @@ ${summaryResponse.content}`
3952
4801
  }
3953
4802
  // ── ctx.log() ─────────────────────────────────────────────────────────
3954
4803
  log(event, data) {
3955
- this.emitTrace({
4804
+ this.emitEvent({
3956
4805
  type: "log",
3957
4806
  data: {
3958
4807
  event,
@@ -4006,12 +4855,11 @@ ${summaryResponse.content}`
4006
4855
  if (usage?.cost != null) {
4007
4856
  this._accumulateBudgetCost(usage.cost);
4008
4857
  }
4009
- this.emitTrace({
4010
- type: "log",
4858
+ this.emitEvent({
4859
+ type: "memory_remember",
4011
4860
  ...usage?.cost != null ? { cost: usage.cost } : {},
4012
4861
  ...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
4013
4862
  data: {
4014
- event: "memory_remember",
4015
4863
  key,
4016
4864
  scope,
4017
4865
  embed: options?.embed === true,
@@ -4023,12 +4871,11 @@ ${summaryResponse.content}`
4023
4871
  if (partialUsage?.cost != null) {
4024
4872
  this._accumulateBudgetCost(partialUsage.cost);
4025
4873
  }
4026
- this.emitTrace({
4027
- type: "log",
4874
+ this.emitEvent({
4875
+ type: "memory_remember",
4028
4876
  ...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
4029
4877
  ...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
4030
4878
  data: {
4031
- event: "memory_remember",
4032
4879
  key,
4033
4880
  scope,
4034
4881
  embed: options?.embed === true,
@@ -4080,26 +4927,24 @@ ${summaryResponse.content}`
4080
4927
  if (usage?.cost != null) {
4081
4928
  this._accumulateBudgetCost(usage.cost);
4082
4929
  }
4083
- this.emitTrace({
4084
- type: "log",
4930
+ this.emitEvent({
4931
+ type: "memory_recall",
4085
4932
  ...usage?.cost != null ? { cost: usage.cost } : {},
4086
4933
  ...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
4087
4934
  data: {
4088
- event: "memory_recall",
4089
4935
  key,
4090
4936
  scope,
4091
4937
  semantic,
4092
4938
  hit,
4093
- ...resultCount !== void 0 ? { resultCount } : {},
4939
+ ...resultCount !== void 0 ? { resultCount, count: resultCount } : {},
4094
4940
  ...usage ? { usage } : {}
4095
4941
  }
4096
4942
  });
4097
4943
  return data;
4098
4944
  } catch (err) {
4099
- this.emitTrace({
4100
- type: "log",
4945
+ this.emitEvent({
4946
+ type: "memory_recall",
4101
4947
  data: {
4102
- event: "memory_recall",
4103
4948
  key,
4104
4949
  scope,
4105
4950
  semantic,
@@ -4124,15 +4969,14 @@ ${summaryResponse.content}`
4124
4969
  const scope = options?.scope ?? (sessionId ? "session" : "global");
4125
4970
  try {
4126
4971
  await this.memoryManager.forget(key, this.stateStore, sessionId, options);
4127
- this.emitTrace({
4128
- type: "log",
4129
- data: { event: "memory_forget", key, scope }
4972
+ this.emitEvent({
4973
+ type: "memory_forget",
4974
+ data: { key, scope }
4130
4975
  });
4131
4976
  } catch (err) {
4132
- this.emitTrace({
4133
- type: "log",
4977
+ this.emitEvent({
4978
+ type: "memory_forget",
4134
4979
  data: {
4135
- event: "memory_forget",
4136
4980
  key,
4137
4981
  scope,
4138
4982
  error: err instanceof Error ? err.message : String(err)
@@ -4167,7 +5011,7 @@ ${summaryResponse.content}`
4167
5011
  names.add(a._name);
4168
5012
  }
4169
5013
  if (agents.length === 1) {
4170
- this.emitTrace({
5014
+ this.emitEvent({
4171
5015
  type: "delegate",
4172
5016
  agent: agents[0]._name,
4173
5017
  data: {
@@ -4211,7 +5055,7 @@ ${summaryResponse.content}`
4211
5055
  resolveModel: () => routerModelUri,
4212
5056
  resolveSystem: () => routerSystem
4213
5057
  };
4214
- this.emitTrace({
5058
+ this.emitEvent({
4215
5059
  type: "delegate",
4216
5060
  agent: "_delegate_router",
4217
5061
  data: {
@@ -4232,107 +5076,42 @@ ${summaryResponse.content}`
4232
5076
  /**
4233
5077
  * Internal emitter input — intentionally loose so call sites don't need to
4234
5078
  * build a perfectly-narrowed discriminated-union member. The resulting
4235
- * `TraceEvent` (exported type) remains strict, and TypeScript narrows it at
5079
+ * `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
4236
5080
  * consumer sites via the `type` discriminator.
4237
5081
  */
4238
- emitTrace(partial) {
4239
- let data = partial.data;
4240
- if (this.config.trace?.redact && data) {
4241
- if (partial.type === "agent_call") {
4242
- const d = data;
4243
- const redacted = {
4244
- ...d,
4245
- prompt: "[redacted]",
4246
- response: "[redacted]"
4247
- };
4248
- if (d.system !== void 0) redacted.system = "[redacted]";
4249
- if (d.thinking !== void 0) redacted.thinking = "[redacted]";
4250
- if (Array.isArray(d.messages)) {
4251
- redacted.messages = [
4252
- { role: "system", content: `[${d.messages.length} messages redacted]` }
4253
- ];
4254
- }
4255
- data = redacted;
4256
- } else if (partial.type === "guardrail" || partial.type === "schema_check" || partial.type === "validate") {
4257
- const d = data;
4258
- const needsRedact = d.feedbackMessage !== void 0 || d.reason !== void 0;
4259
- if (needsRedact) {
4260
- data = {
4261
- ...d,
4262
- ...d.reason !== void 0 ? { reason: "[redacted]" } : {},
4263
- ...d.feedbackMessage !== void 0 ? { feedbackMessage: "[redacted]" } : {}
4264
- };
4265
- }
4266
- } else if (partial.type === "tool_call") {
4267
- const d = data;
4268
- data = {
4269
- ...d,
4270
- args: "[redacted]",
4271
- result: "[redacted]"
4272
- };
4273
- } else if (partial.type === "tool_approval") {
4274
- const d = data;
4275
- data = {
4276
- ...d,
4277
- args: "[redacted]",
4278
- ...d.reason !== void 0 ? { reason: "[redacted]" } : {}
4279
- };
4280
- } else if (partial.type === "handoff") {
4281
- const d = data;
4282
- if (d.message !== void 0) {
4283
- data = { ...d, message: "[redacted]" };
4284
- }
4285
- } else if (partial.type === "workflow_start") {
4286
- const d = data;
4287
- if (d.input !== void 0) {
4288
- data = { ...d, input: "[redacted]" };
4289
- }
4290
- } else if (partial.type === "workflow_end") {
4291
- const d = data;
4292
- const redacted = { ...d };
4293
- if (d.result !== void 0) redacted.result = "[redacted]";
4294
- if (d.error !== void 0) redacted.error = "[redacted]";
4295
- data = redacted;
4296
- } else if (partial.type === "log") {
4297
- const d = data;
4298
- const redacted = {};
4299
- for (const [k, v] of Object.entries(d)) {
4300
- if (k === "event" || typeof v === "number" || typeof v === "boolean") {
4301
- redacted[k] = v;
4302
- } else if (typeof v === "string") {
4303
- redacted[k] = "[redacted]";
4304
- } else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
4305
- const inner = v;
4306
- const innerRedacted = {};
4307
- for (const [ik, iv] of Object.entries(inner)) {
4308
- if (typeof iv === "number" || typeof iv === "boolean") {
4309
- innerRedacted[ik] = iv;
4310
- } else if (typeof iv === "string") {
4311
- innerRedacted[ik] = "[redacted]";
4312
- } else {
4313
- innerRedacted[ik] = "[redacted]";
4314
- }
4315
- }
4316
- redacted[k] = innerRedacted;
4317
- } else {
4318
- redacted[k] = "[redacted]";
4319
- }
4320
- }
4321
- data = redacted;
4322
- }
5082
+ emitEvent(partial) {
5083
+ const data = partial.data;
5084
+ const frame = askStorage.getStore();
5085
+ const step = (frame?.stepRef ?? this.stepRefRoot).value++;
5086
+ const cost = partial.cost;
5087
+ if (frame && typeof cost === "number" && Number.isFinite(cost) && cost >= 0 && COST_BEARING_LEAF_TYPES.includes(partial.type)) {
5088
+ frame.askCost.value += cost;
4323
5089
  }
4324
5090
  const event = {
4325
5091
  executionId: this.executionId,
4326
- step: this.stepCounter++,
5092
+ step,
4327
5093
  timestamp: Date.now(),
4328
5094
  ...this.workflowName ? { workflow: this.workflowName } : {},
4329
- ...this.parentToolCallId ? { parentToolCallId: this.parentToolCallId } : {},
5095
+ // Stamp ask correlation from ALS. Variants like `workflow_start` /
5096
+ // `workflow_end` and out-of-ask events (log/memory/checkpoint/await_human
5097
+ // when emitted at workflow scope) may legitimately have no frame —
5098
+ // they still get `depth: 0` so consumers can render them at root
5099
+ // level without having to guess. `askId`/`parentAskId`/`agent` stay
5100
+ // unset for non-ask-scoped events, matching the `Partial<AskScoped>`
5101
+ // shape on those union members.
5102
+ ...frame ? {
5103
+ askId: frame.askId,
5104
+ ...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
5105
+ depth: frame.depth,
5106
+ ...frame.agent ? { agent: frame.agent } : {}
5107
+ } : { depth: 0 },
4330
5108
  ...partial,
4331
5109
  data
4332
5110
  };
5111
+ const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
4333
5112
  if (this.onTrace) {
4334
5113
  try {
4335
- this.onTrace(event);
5114
+ this.onTrace(finalEvent);
4336
5115
  } catch (err) {
4337
5116
  console.error(
4338
5117
  "[axl] onTrace handler threw; trace event dropped:",
@@ -4344,7 +5123,7 @@ ${summaryResponse.content}`
4344
5123
  };
4345
5124
 
4346
5125
  // src/agent.ts
4347
- import { randomUUID } from "crypto";
5126
+ import { randomUUID as randomUUID2 } from "crypto";
4348
5127
  var agentCounter = 0;
4349
5128
  function agent(config) {
4350
5129
  agentCounter++;
@@ -4364,7 +5143,7 @@ function agent(config) {
4364
5143
  const registry = new ProviderRegistry();
4365
5144
  const ctx = new WorkflowContext({
4366
5145
  input: prompt,
4367
- executionId: randomUUID(),
5146
+ executionId: randomUUID2(),
4368
5147
  config: {},
4369
5148
  providerRegistry: registry
4370
5149
  });
@@ -4388,7 +5167,7 @@ function workflow(config) {
4388
5167
 
4389
5168
  // src/runtime.ts
4390
5169
  import { EventEmitter as EventEmitter2 } from "events";
4391
- import { randomUUID as randomUUID2 } from "crypto";
5170
+ import { randomUUID as randomUUID3 } from "crypto";
4392
5171
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
4393
5172
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
4394
5173
  import { dirname } from "path";
@@ -4411,29 +5190,20 @@ var MemoryStore = class {
4411
5190
  constructor() {
4412
5191
  this.loadPersistedState();
4413
5192
  }
4414
- async saveCheckpoint(executionId, step, data) {
4415
- let steps = this.checkpoints.get(executionId);
4416
- if (!steps) {
4417
- steps = /* @__PURE__ */ new Map();
4418
- this.checkpoints.set(executionId, steps);
5193
+ async saveCheckpoint(executionId, name, data) {
5194
+ let entries = this.checkpoints.get(executionId);
5195
+ if (!entries) {
5196
+ entries = /* @__PURE__ */ new Map();
5197
+ this.checkpoints.set(executionId, entries);
4419
5198
  }
4420
- steps.set(step, structuredClone(data));
5199
+ entries.set(name, structuredClone(data));
4421
5200
  }
4422
- async getCheckpoint(executionId, step) {
4423
- const steps = this.checkpoints.get(executionId);
4424
- if (!steps) return null;
4425
- const data = steps.get(step);
5201
+ async getCheckpoint(executionId, name) {
5202
+ const entries = this.checkpoints.get(executionId);
5203
+ if (!entries) return null;
5204
+ const data = entries.get(name);
4426
5205
  return data !== void 0 ? structuredClone(data) : null;
4427
5206
  }
4428
- async getLatestCheckpoint(executionId) {
4429
- const steps = this.checkpoints.get(executionId);
4430
- if (!steps || steps.size === 0) return null;
4431
- let maxStep = -1;
4432
- for (const step of steps.keys()) {
4433
- if (step > maxStep) maxStep = step;
4434
- }
4435
- return { step: maxStep, data: structuredClone(steps.get(maxStep)) };
4436
- }
4437
5207
  async saveSession(sessionId, history) {
4438
5208
  this.sessions.set(sessionId, structuredClone(history));
4439
5209
  }
@@ -4606,7 +5376,7 @@ function rowToExecutionInfo(row) {
4606
5376
  completedAt: row.completed_at ?? void 0,
4607
5377
  duration: row.duration,
4608
5378
  error: row.error ?? void 0,
4609
- steps: safeJsonParse(row.steps) ?? []
5379
+ events: safeJsonParse(row.events) ?? []
4610
5380
  };
4611
5381
  }
4612
5382
  var SQLiteStore = class {
@@ -4622,15 +5392,80 @@ var SQLiteStore = class {
4622
5392
  }
4623
5393
  this.db = new BetterSqlite3(filePath);
4624
5394
  this.db.pragma("journal_mode = WAL");
5395
+ this.migrate();
4625
5396
  this.initTables();
4626
5397
  }
5398
+ /**
5399
+ * Idempotent schema migration. Tracks version via `PRAGMA user_version`
5400
+ * so reopens skip applied steps. Wrapped in `BEGIN IMMEDIATE` so
5401
+ * concurrent `SQLiteStore` constructors serialize at the file level
5402
+ * and rollback on failure leaves the DB in a clean pre-migration
5403
+ * state.
5404
+ *
5405
+ * Migration history:
5406
+ * - v1 (spec/16): rename `execution_history.steps` → `events`. The
5407
+ * JS-level `ExecutionInfo.steps` was renamed to `.events` in PR 1
5408
+ * commit 1; this brings the on-disk schema in line.
5409
+ * - v2 (named checkpoints): rename `checkpoints.step` (INTEGER) →
5410
+ * `checkpoints.name` (TEXT). The runtime now requires
5411
+ * `ctx.checkpoint(name, fn)` so the per-context numeric counter is
5412
+ * gone — names are caller-supplied stable identifiers. Existing
5413
+ * numeric step values become stringified names ("0", "1", …) to
5414
+ * preserve replay continuity for in-flight executions across the
5415
+ * upgrade boundary.
5416
+ *
5417
+ * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
5418
+ * IF NOT EXISTS` runs against the post-migration column name. Fresh
5419
+ * installs just create the table with `events` directly and bump
5420
+ * `user_version` to the current version.
5421
+ *
5422
+ * TOCTOU guard (review B-3): the initial `user_version` read is
5423
+ * informational only — a concurrent process can complete the
5424
+ * migration between that read and our `BEGIN IMMEDIATE`. We
5425
+ * re-read `user_version` inside the transaction and short-circuit
5426
+ * if it's already at target, so the non-idempotent half (writes
5427
+ * that went beyond a simple ALTER) never double-applies under a
5428
+ * race. The current v0→v1 step IS idempotent via the column-
5429
+ * presence check, but future migrations may not be, and the
5430
+ * transactional re-read costs nothing.
5431
+ */
5432
+ migrate() {
5433
+ const TARGET_VERSION = 2;
5434
+ const current = this.db.pragma("user_version", { simple: true });
5435
+ if (current >= TARGET_VERSION) return;
5436
+ this.db.exec("BEGIN IMMEDIATE");
5437
+ try {
5438
+ const committed = this.db.pragma("user_version", { simple: true });
5439
+ if (committed >= TARGET_VERSION) {
5440
+ this.db.exec("COMMIT");
5441
+ return;
5442
+ }
5443
+ if (committed < 1) {
5444
+ const cols = this.db.pragma("table_info(execution_history)");
5445
+ if (cols.some((c) => c.name === "steps") && !cols.some((c) => c.name === "events")) {
5446
+ this.db.exec("ALTER TABLE execution_history RENAME COLUMN steps TO events");
5447
+ }
5448
+ }
5449
+ if (committed < 2) {
5450
+ const cols = this.db.pragma("table_info(checkpoints)");
5451
+ if (cols.some((c) => c.name === "step") && !cols.some((c) => c.name === "name")) {
5452
+ this.db.exec("ALTER TABLE checkpoints RENAME COLUMN step TO name");
5453
+ }
5454
+ }
5455
+ this.db.pragma(`user_version = ${TARGET_VERSION}`);
5456
+ this.db.exec("COMMIT");
5457
+ } catch (err) {
5458
+ this.db.exec("ROLLBACK");
5459
+ throw err;
5460
+ }
5461
+ }
4627
5462
  initTables() {
4628
5463
  this.db.exec(`
4629
5464
  CREATE TABLE IF NOT EXISTS checkpoints (
4630
5465
  execution_id TEXT NOT NULL,
4631
- step INTEGER NOT NULL,
5466
+ name TEXT NOT NULL,
4632
5467
  data TEXT NOT NULL,
4633
- PRIMARY KEY (execution_id, step)
5468
+ PRIMARY KEY (execution_id, name)
4634
5469
  );
4635
5470
 
4636
5471
  CREATE TABLE IF NOT EXISTS sessions (
@@ -4679,7 +5514,7 @@ var SQLiteStore = class {
4679
5514
  completed_at INTEGER,
4680
5515
  duration INTEGER NOT NULL DEFAULT 0,
4681
5516
  error TEXT,
4682
- steps TEXT NOT NULL
5517
+ events TEXT NOT NULL
4683
5518
  );
4684
5519
 
4685
5520
  CREATE TABLE IF NOT EXISTS eval_history (
@@ -4694,26 +5529,19 @@ var SQLiteStore = class {
4694
5529
  `);
4695
5530
  }
4696
5531
  // ── Checkpoints ────────────────────────────────────────────────────────
4697
- async saveCheckpoint(executionId, step, data) {
5532
+ async saveCheckpoint(executionId, name, data) {
4698
5533
  const stmt = this.db.prepare(
4699
- "INSERT OR REPLACE INTO checkpoints (execution_id, step, data) VALUES (?, ?, ?)"
5534
+ "INSERT OR REPLACE INTO checkpoints (execution_id, name, data) VALUES (?, ?, ?)"
4700
5535
  );
4701
- stmt.run(executionId, step, JSON.stringify(data));
5536
+ stmt.run(executionId, name, JSON.stringify(data));
4702
5537
  }
4703
- async getCheckpoint(executionId, step) {
5538
+ async getCheckpoint(executionId, name) {
4704
5539
  const stmt = this.db.prepare(
4705
- "SELECT data FROM checkpoints WHERE execution_id = ? AND step = ?"
5540
+ "SELECT data FROM checkpoints WHERE execution_id = ? AND name = ?"
4706
5541
  );
4707
- const row = stmt.get(executionId, step);
5542
+ const row = stmt.get(executionId, name);
4708
5543
  return row ? safeJsonParse(row.data) : null;
4709
5544
  }
4710
- async getLatestCheckpoint(executionId) {
4711
- const stmt = this.db.prepare(
4712
- "SELECT step, data FROM checkpoints WHERE execution_id = ? ORDER BY step DESC LIMIT 1"
4713
- );
4714
- const row = stmt.get(executionId);
4715
- return row ? { step: row.step, data: safeJsonParse(row.data) } : null;
4716
- }
4717
5545
  // ── Sessions ────────────────────────────────────────────────────────────
4718
5546
  async saveSession(sessionId, history) {
4719
5547
  const stmt = this.db.prepare(
@@ -4807,7 +5635,7 @@ var SQLiteStore = class {
4807
5635
  // ── Execution History ────────────────────────────────────────────────────
4808
5636
  async saveExecution(execution) {
4809
5637
  this.db.prepare(
4810
- "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, steps) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
5638
+ "INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
4811
5639
  ).run(
4812
5640
  execution.executionId,
4813
5641
  execution.workflow,
@@ -4817,7 +5645,7 @@ var SQLiteStore = class {
4817
5645
  execution.completedAt ?? null,
4818
5646
  execution.duration,
4819
5647
  execution.error ?? null,
4820
- JSON.stringify(execution.steps)
5648
+ JSON.stringify(execution.events)
4821
5649
  );
4822
5650
  }
4823
5651
  async getExecution(executionId) {
@@ -4991,7 +5819,8 @@ var Session = class _Session {
4991
5819
  }
4992
5820
  };
4993
5821
  axlStream.on("done", (event) => {
4994
- updateHistory(event.data).catch((err) => {
5822
+ const data = event.data;
5823
+ updateHistory(data.result).catch((err) => {
4995
5824
  this.runtime.emit("error", {
4996
5825
  type: "session_history_save_failed",
4997
5826
  sessionId: this.sessionId,
@@ -5033,9 +5862,88 @@ var Session = class _Session {
5033
5862
  // src/stream.ts
5034
5863
  import { Readable } from "stream";
5035
5864
  import { EventEmitter } from "events";
5865
+
5866
+ // src/types.ts
5867
+ var AXL_EVENT_TYPES = [
5868
+ // Workflow lifecycle
5869
+ "workflow_start",
5870
+ "workflow_end",
5871
+ // Ask boundary
5872
+ "ask_start",
5873
+ "ask_end",
5874
+ // Agent turn lifecycle
5875
+ "agent_call_start",
5876
+ "agent_call_end",
5877
+ // Content delivery (stream-only)
5878
+ "token",
5879
+ // Tool invocation lifecycle
5880
+ "tool_call_start",
5881
+ "tool_call_end",
5882
+ // Single-point tool events
5883
+ "tool_approval",
5884
+ "tool_denied",
5885
+ // Delegation
5886
+ "delegate",
5887
+ // Handoff (spans two asks; not AskScoped).
5888
+ // `handoff_start` emits before the target ask begins; always fired.
5889
+ // `handoff_return` emits when control returns to source; roundtrip only
5890
+ // (oneway handoffs have no return trip — the target's `ask_end` IS the
5891
+ // end of the chain).
5892
+ "handoff_start",
5893
+ "handoff_return",
5894
+ // Pipeline (retry/validation lifecycle) — added in PR 2; reserved here.
5895
+ "pipeline",
5896
+ // Progressive structured output — added in PR 2; reserved here.
5897
+ "partial_object",
5898
+ // Verification
5899
+ "verify",
5900
+ // Observability
5901
+ "log",
5902
+ "memory_remember",
5903
+ "memory_recall",
5904
+ "memory_forget",
5905
+ // Durable execution checkpoints (`ctx.checkpoint`)
5906
+ "checkpoint_save",
5907
+ "checkpoint_replay",
5908
+ // Human-in-the-loop (`ctx.awaitHuman`)
5909
+ "await_human",
5910
+ "await_human_resolved",
5911
+ // Legacy gate events — emitted by current code; collapsed into `pipeline`
5912
+ // in PR 2 (spec/16-streaming-wire-reliability §4.2).
5913
+ "guardrail",
5914
+ "schema_check",
5915
+ "validate",
5916
+ // Terminal workflow markers
5917
+ "done",
5918
+ "error"
5919
+ ];
5920
+
5921
+ // src/stream.ts
5036
5922
  var AxlStream = class _AxlStream extends Readable {
5037
5923
  bus = new EventEmitter();
5038
- tokens = [];
5924
+ /**
5925
+ * Per-ask token buffers split into "in-progress" and "committed"
5926
+ * halves so `fullText` only includes tokens from attempts that
5927
+ * actually won. Scoping is per-`askId` so concurrent root-level asks
5928
+ * (`ctx.parallel`, `ctx.spawn`, `ctx.race`, `ctx.map`) don't
5929
+ * interleave each other's tokens — each branch's chunks stay
5930
+ * contiguous, and a `pipeline(failed)` on one branch only discards
5931
+ * THAT branch's in-progress buffer (previously: shared buffer caused
5932
+ * a failure on one branch to discard a peer's in-flight successful
5933
+ * tokens). Insertion order in the Maps reflects which ask emitted
5934
+ * its first token first, which is what `fullText` joins on.
5935
+ *
5936
+ * On `pipeline(status: 'committed')` for an ask, that ask's
5937
+ * in-progress entry flushes to its committed entry. On
5938
+ * `pipeline(status: 'failed')` or `ask_end({ok:false})` for an ask,
5939
+ * that ask's in-progress entry is discarded. Spec/16 §4.3.
5940
+ *
5941
+ * Tokens emitted outside any ask (synthesized test fixtures with no
5942
+ * `askId`) fall back to an empty-string sentinel key — preserves the
5943
+ * single-buffer behavior for that legacy case.
5944
+ */
5945
+ attemptByAsk = /* @__PURE__ */ new Map();
5946
+ committedByAsk = /* @__PURE__ */ new Map();
5039
5947
  result = void 0;
5040
5948
  finished = false;
5041
5949
  promise;
@@ -5057,18 +5965,10 @@ var AxlStream = class _AxlStream extends Readable {
5057
5965
  this.promise.catch(() => {
5058
5966
  });
5059
5967
  }
5060
- static STREAM_EVENTS = /* @__PURE__ */ new Set([
5061
- "token",
5062
- "tool_call",
5063
- "tool_result",
5064
- "tool_approval",
5065
- "agent_start",
5066
- "agent_end",
5067
- "handoff",
5068
- "step",
5069
- "done",
5070
- "error"
5071
- ]);
5968
+ /** Wire-format event names callers can subscribe to via `.on(name, fn)`.
5969
+ * Derived from the canonical `AXL_EVENT_TYPES` tuple — adding a new
5970
+ * variant in `types.ts` automatically extends the subscribable set. */
5971
+ static STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
5072
5972
  on(event, handler) {
5073
5973
  if (_AxlStream.STREAM_EVENTS.has(event)) {
5074
5974
  this.bus.on(event, handler);
@@ -5118,21 +6018,83 @@ var AxlStream = class _AxlStream extends Readable {
5118
6018
  while (true) {
5119
6019
  const { value, done } = await iter.next();
5120
6020
  if (done) return { value: void 0, done: true };
5121
- if (value.type === "token") return { value: value.data, done: false };
6021
+ if (value.type === "token" && isRootLevel(value)) {
6022
+ return { value: value.data, done: false };
6023
+ }
6024
+ }
6025
+ }
6026
+ };
6027
+ }
6028
+ };
6029
+ }
6030
+ /**
6031
+ * Iterator over `{ askId, text }` pairs — one emission per token chunk,
6032
+ * tagged with the ask frame that produced it. Complements `.text`
6033
+ * (root-only stream): consumers building a split-pane UI that shows
6034
+ * each sub-agent's output in its own lane can group by `askId`
6035
+ * without iterating the raw stream and hand-filtering on `event.type`.
6036
+ *
6037
+ * Nested and root tokens both flow through. The `agent` field names
6038
+ * the producing agent for UI labelling; it's undefined when the token
6039
+ * was emitted outside any ask (rare — synthesized test fixtures).
6040
+ */
6041
+ get textByAsk() {
6042
+ const self = this;
6043
+ return {
6044
+ [Symbol.asyncIterator]() {
6045
+ const iter = self[Symbol.asyncIterator]();
6046
+ return {
6047
+ async next() {
6048
+ while (true) {
6049
+ const { value, done } = await iter.next();
6050
+ if (done) {
6051
+ return {
6052
+ value: void 0,
6053
+ done: true
6054
+ };
6055
+ }
6056
+ if (value.type === "token") {
6057
+ return {
6058
+ value: { askId: value.askId, agent: value.agent, text: value.data },
6059
+ done: false
6060
+ };
6061
+ }
5122
6062
  }
5123
6063
  }
5124
6064
  };
5125
6065
  }
5126
6066
  };
5127
6067
  }
5128
- get steps() {
5129
- const stepTypes = /* @__PURE__ */ new Set([
5130
- "agent_start",
5131
- "agent_end",
5132
- "tool_call",
5133
- "tool_result",
6068
+ /**
6069
+ * Iterator over structural lifecycle events only — skips per-token chatter
6070
+ * and progressive partial_object emissions. Useful for waterfall UIs and
6071
+ * any consumer that wants the "what happened" timeline without per-chunk
6072
+ * noise. Renamed from `.steps` in the unified-event-model migration
6073
+ * because these are events, not pipeline steps.
6074
+ */
6075
+ get lifecycle() {
6076
+ const lifecycleTypes = /* @__PURE__ */ new Set([
6077
+ "ask_start",
6078
+ "ask_end",
6079
+ "agent_call_start",
6080
+ "agent_call_end",
6081
+ "tool_call_start",
6082
+ "tool_call_end",
5134
6083
  "tool_approval",
5135
- "handoff"
6084
+ "tool_denied",
6085
+ "handoff_start",
6086
+ "handoff_return",
6087
+ "delegate",
6088
+ "pipeline",
6089
+ "verify",
6090
+ "workflow_start",
6091
+ "workflow_end",
6092
+ // Durable-execution checkpoints — structural points in the timeline.
6093
+ "checkpoint_save",
6094
+ "checkpoint_replay",
6095
+ // Human-in-the-loop — pause/resume are major timeline landmarks.
6096
+ "await_human",
6097
+ "await_human_resolved"
5136
6098
  ]);
5137
6099
  const self = this;
5138
6100
  return {
@@ -5143,7 +6105,7 @@ var AxlStream = class _AxlStream extends Readable {
5143
6105
  while (true) {
5144
6106
  const { value, done } = await iter.next();
5145
6107
  if (done) return { value: void 0, done: true };
5146
- if (stepTypes.has(value.type)) return { value, done: false };
6108
+ if (lifecycleTypes.has(value.type)) return { value, done: false };
5147
6109
  }
5148
6110
  }
5149
6111
  };
@@ -5163,10 +6125,34 @@ var AxlStream = class _AxlStream extends Readable {
5163
6125
  });
5164
6126
  return destination;
5165
6127
  }
5166
- /** Push a stream event. Called by the runtime. */
6128
+ /** Push an event onto the stream. Called by the runtime. */
5167
6129
  _push(event) {
5168
6130
  if (this.finished) return;
5169
- if (event.type === "token") this.tokens.push(event.data);
6131
+ if (event.type === "token" && isRootLevel(event)) {
6132
+ const key = event.askId ?? "";
6133
+ const attempt = this.attemptByAsk.get(key);
6134
+ if (attempt) {
6135
+ attempt.push(event.data);
6136
+ } else {
6137
+ this.attemptByAsk.set(key, [event.data]);
6138
+ }
6139
+ }
6140
+ if (event.type === "pipeline" && isRootLevel(event)) {
6141
+ const key = event.askId ?? "";
6142
+ if (event.status === "committed") {
6143
+ const attempt = this.attemptByAsk.get(key);
6144
+ if (attempt && attempt.length > 0) {
6145
+ const prev = this.committedByAsk.get(key) ?? "";
6146
+ this.committedByAsk.set(key, prev + attempt.join(""));
6147
+ this.attemptByAsk.set(key, []);
6148
+ }
6149
+ } else if (event.status === "failed") {
6150
+ this.attemptByAsk.set(key, []);
6151
+ }
6152
+ }
6153
+ if (event.type === "ask_end" && isRootLevel(event) && !event.outcome.ok) {
6154
+ this.attemptByAsk.set(event.askId ?? "", []);
6155
+ }
5170
6156
  this.bus.emit(event.type, event);
5171
6157
  this.push(event);
5172
6158
  const waiter = this.waiters.shift();
@@ -5176,12 +6162,25 @@ var AxlStream = class _AxlStream extends Readable {
5176
6162
  this.eventQueue.push(event);
5177
6163
  }
5178
6164
  }
5179
- /** Signal successful completion. */
5180
- _done(result) {
6165
+ /**
6166
+ * Signal successful completion.
6167
+ *
6168
+ * `executionId` is required — the runtime must allocate it before
6169
+ * calling `stream()` so terminal events always carry a real id.
6170
+ * Previously the default-empty parameter surfaced blank executionIds
6171
+ * on error paths that threw before `execInfo` was assigned (review S4).
6172
+ */
6173
+ _done(result, executionId) {
5181
6174
  if (this.finished) return;
5182
6175
  this.finished = true;
5183
6176
  this.result = result;
5184
- const doneEvent = { type: "done", data: result };
6177
+ const doneEvent = {
6178
+ type: "done",
6179
+ executionId,
6180
+ step: Number.MAX_SAFE_INTEGER,
6181
+ timestamp: Date.now(),
6182
+ data: { result }
6183
+ };
5185
6184
  this.bus.emit("done", doneEvent);
5186
6185
  this.push(doneEvent);
5187
6186
  this.push(null);
@@ -5197,11 +6196,19 @@ var AxlStream = class _AxlStream extends Readable {
5197
6196
  this.waiters.length = 0;
5198
6197
  this.bus.emit("__resolve", result);
5199
6198
  }
5200
- /** Signal an error. */
5201
- _error(error) {
6199
+ /** Signal an error. `executionId` is required for the same reason as
6200
+ * `_done`: terminal events must carry a real id even when the
6201
+ * failure happens before any real trace event fires (review S4). */
6202
+ _error(error, executionId) {
5202
6203
  if (this.finished) return;
5203
6204
  this.finished = true;
5204
- const errorEvent = { type: "error", message: error.message };
6205
+ const errorEvent = {
6206
+ type: "error",
6207
+ executionId,
6208
+ step: Number.MAX_SAFE_INTEGER,
6209
+ timestamp: Date.now(),
6210
+ data: { message: error.message, name: error.name }
6211
+ };
5205
6212
  this.bus.emit("error", errorEvent);
5206
6213
  this.push(errorEvent);
5207
6214
  this.push(null);
@@ -5217,8 +6224,31 @@ var AxlStream = class _AxlStream extends Readable {
5217
6224
  this.waiters.length = 0;
5218
6225
  this.bus.emit("__reject", error);
5219
6226
  }
6227
+ /** Concatenated root-only text from committed attempts plus the
6228
+ * current in-flight attempt(s). Retried (gate-rejected) attempts
6229
+ * are excluded — see spec/16 §4.3. Reading mid-attempt returns the
6230
+ * in-progress text; reading after `pipeline(committed)` (which
6231
+ * fires before `done`) returns the canonical winning text.
6232
+ *
6233
+ * With concurrent root-level asks (`ctx.parallel`, `ctx.spawn`,
6234
+ * `ctx.race`, `ctx.map`), each branch's tokens are scoped per-`askId`
6235
+ * and emitted contiguously in the order each ask first started
6236
+ * emitting tokens. A `pipeline(failed)` or `ask_end({ok:false})` on
6237
+ * one branch only discards THAT branch's in-progress buffer —
6238
+ * sibling branches are unaffected. For UIs that want each sub-agent
6239
+ * in its own lane, prefer `textByAsk`. */
5220
6240
  get fullText() {
5221
- return this.tokens.join("");
6241
+ let out = "";
6242
+ for (const [key, committed] of this.committedByAsk) {
6243
+ out += committed;
6244
+ const attempt = this.attemptByAsk.get(key);
6245
+ if (attempt && attempt.length > 0) out += attempt.join("");
6246
+ }
6247
+ for (const [key, attempt] of this.attemptByAsk) {
6248
+ if (this.committedByAsk.has(key)) continue;
6249
+ if (attempt.length > 0) out += attempt.join("");
6250
+ }
6251
+ return out;
5222
6252
  }
5223
6253
  };
5224
6254
 
@@ -5764,6 +6794,32 @@ function hashInput(input) {
5764
6794
  }
5765
6795
  return (hash >>> 0).toString(16);
5766
6796
  }
6797
+ var DEFAULT_MAX_EVENTS_PER_EXECUTION = 5e4;
6798
+ function isAbortError(err) {
6799
+ return typeof err === "object" && err !== null && err.name === "AbortError";
6800
+ }
6801
+ function pushEventBounded(execInfo, event, cap) {
6802
+ if (event.type === "token" || event.type === "partial_object") return;
6803
+ if (cap === Infinity || execInfo.events.length < cap) {
6804
+ execInfo.events.push(event);
6805
+ return;
6806
+ }
6807
+ const last = execInfo.events[execInfo.events.length - 1];
6808
+ const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
6809
+ if (!alreadyTruncated) {
6810
+ execInfo.events[execInfo.events.length - 1] = {
6811
+ type: "log",
6812
+ executionId: event.executionId,
6813
+ step: event.step,
6814
+ timestamp: Date.now(),
6815
+ data: {
6816
+ event: "events_truncated",
6817
+ cap,
6818
+ message: `ExecutionInfo.events truncated at ${cap} entries. Subsequent events are still delivered to runtime.on('trace') and the WS broadcast \u2014 only the in-memory array is bounded. Raise via config.state.maxEventsPerExecution.`
6819
+ }
6820
+ };
6821
+ }
6822
+ }
5767
6823
  var costScopeStorage = new AsyncLocalStorage2();
5768
6824
  var AxlRuntime = class extends EventEmitter2 {
5769
6825
  config;
@@ -5783,11 +6839,27 @@ var AxlRuntime = class extends EventEmitter2 {
5783
6839
  historicalExecutionsLoadPromise = null;
5784
6840
  evalHistory = [];
5785
6841
  evalHistoryLoadPromise = null;
6842
+ /** Resolved cap on `ExecutionInfo.events` per execution. Cached on
6843
+ * the instance so per-event onTrace handlers don't reach into
6844
+ * `this.config.state` on every emission. */
6845
+ maxEventsPerExecution;
5786
6846
  constructor(config) {
5787
6847
  super();
5788
6848
  this.config = resolveConfig(config ?? {});
5789
6849
  this.providerRegistry = new ProviderRegistry();
5790
6850
  this.stateStore = this.createStateStore();
6851
+ const requested = this.config.state?.maxEventsPerExecution;
6852
+ if (requested === void 0) {
6853
+ this.maxEventsPerExecution = DEFAULT_MAX_EVENTS_PER_EXECUTION;
6854
+ } else if (requested === Infinity) {
6855
+ this.maxEventsPerExecution = Infinity;
6856
+ } else if (typeof requested !== "number" || !Number.isFinite(requested) || !Number.isInteger(requested) || requested < 1) {
6857
+ throw new RangeError(
6858
+ `config.state.maxEventsPerExecution must be a positive integer or Infinity; got ${requested}`
6859
+ );
6860
+ } else {
6861
+ this.maxEventsPerExecution = requested;
6862
+ }
5791
6863
  if (this.config.memory) {
5792
6864
  this.memoryManager = new MemoryManager({
5793
6865
  vectorStore: this.config.memory.vectorStore,
@@ -5843,6 +6915,71 @@ var AxlRuntime = class extends EventEmitter2 {
5843
6915
  return new MemoryStore();
5844
6916
  }
5845
6917
  }
6918
+ /**
6919
+ * Run a workflow body inside an ALREADY-CONSTRUCTED `WorkflowContext`
6920
+ * with full lifecycle invariants: emit `workflow_start` → run handler
6921
+ * → parse output → emit `workflow_end` → delete checkpoints → persist.
6922
+ *
6923
+ * Shared between `execute()` and `stream()`. Centralizes the
6924
+ * "start iff end" pairing invariant so a future emit path
6925
+ * (e.g., `resumeExecution`) can inherit correct behavior by routing
6926
+ * through this helper. Also factors out the AbortError detection.
6927
+ *
6928
+ * Side effects on `execInfo`: sets `status` / `completedAt` /
6929
+ * `duration` / `result` (or `error`) before emitting `workflow_end`.
6930
+ * On throw: emits `workflow_end({status: 'failed', aborted?})` and
6931
+ * persists before rethrowing. `abortControllers.delete` runs in
6932
+ * `finally` regardless of outcome.
6933
+ *
6934
+ * Caller responsibilities:
6935
+ * - Allocate `executionId` and register it in `abortControllers` map
6936
+ * BEFORE calling this helper (so early-throw paths still have a
6937
+ * correlation id available for terminal events).
6938
+ * - Construct `execInfo` and `ctx` before calling.
6939
+ * - Pass `span` if the call site is inside a tracer span — this
6940
+ * helper sets `axl.workflow.cost` / `.duration` attributes on
6941
+ * success.
6942
+ */
6943
+ async runWorkflowBody(args) {
6944
+ const { workflow: workflow2, ctx, execInfo, validated, span } = args;
6945
+ try {
6946
+ ctx._emitWorkflowStart(validated);
6947
+ const raw = await workflow2.handler(ctx);
6948
+ const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
6949
+ execInfo.status = "completed";
6950
+ execInfo.completedAt = Date.now();
6951
+ execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6952
+ execInfo.result = result;
6953
+ ctx._emitWorkflowEnd({
6954
+ status: "completed",
6955
+ duration: execInfo.duration,
6956
+ result
6957
+ });
6958
+ if (this.stateStore.deleteCheckpoints) {
6959
+ await this.stateStore.deleteCheckpoints(execInfo.executionId);
6960
+ }
6961
+ span?.setAttribute("axl.workflow.cost", execInfo.totalCost);
6962
+ span?.setAttribute("axl.workflow.duration", execInfo.duration);
6963
+ this.persistExecution(execInfo);
6964
+ return result;
6965
+ } catch (err) {
6966
+ const aborted = isAbortError(err);
6967
+ execInfo.status = "failed";
6968
+ execInfo.completedAt = Date.now();
6969
+ execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6970
+ execInfo.error = err instanceof Error ? err.message : String(err);
6971
+ ctx._emitWorkflowEnd({
6972
+ status: "failed",
6973
+ duration: execInfo.duration,
6974
+ error: execInfo.error,
6975
+ ...aborted ? { aborted: true } : {}
6976
+ });
6977
+ this.persistExecution(execInfo);
6978
+ throw err;
6979
+ } finally {
6980
+ this.abortControllers.delete(execInfo.executionId);
6981
+ }
6982
+ }
5846
6983
  /**
5847
6984
  * Persist a completed/failed execution to the state store (fire-and-forget)
5848
6985
  * and move it from the active executions map to the historical cache.
@@ -5858,7 +6995,6 @@ var AxlRuntime = class extends EventEmitter2 {
5858
6995
  this.executions.delete(id);
5859
6996
  }
5860
6997
  /** Register a workflow with the runtime. */
5861
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5862
6998
  register(workflow2) {
5863
6999
  this.workflows.set(workflow2.name, workflow2);
5864
7000
  }
@@ -5984,7 +7120,7 @@ var AxlRuntime = class extends EventEmitter2 {
5984
7120
  }
5985
7121
  try {
5986
7122
  await this.saveEvalResult({
5987
- id: resultObj.id ?? randomUUID2(),
7123
+ id: resultObj.id ?? randomUUID3(),
5988
7124
  eval: name,
5989
7125
  timestamp: Date.now(),
5990
7126
  data: structuredClone(result)
@@ -6021,7 +7157,7 @@ var AxlRuntime = class extends EventEmitter2 {
6021
7157
  * and automatically emits trace events and tracks cost.
6022
7158
  */
6023
7159
  createContext(options) {
6024
- const executionId = randomUUID2();
7160
+ const executionId = randomUUID3();
6025
7161
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
6026
7162
  this.registerWithCostScope(executionId);
6027
7163
  return new WorkflowContext({
@@ -6037,10 +7173,12 @@ var AxlRuntime = class extends EventEmitter2 {
6037
7173
  sessionHistory: options?.sessionHistory,
6038
7174
  signal: options?.signal,
6039
7175
  onToken: options?.onToken,
7176
+ onToolCall: options?.onToolCall,
7177
+ onAgentStart: options?.onAgentStart,
6040
7178
  awaitHumanHandler: options?.awaitHumanHandler,
6041
7179
  onTrace: (event) => {
6042
7180
  this.emit("trace", event);
6043
- this.outputTraceEvent(event);
7181
+ this.outputAxlEvent(event);
6044
7182
  },
6045
7183
  budgetContext: {
6046
7184
  totalCost: 0,
@@ -6067,7 +7205,7 @@ var AxlRuntime = class extends EventEmitter2 {
6067
7205
  );
6068
7206
  }
6069
7207
  const validated = workflow2.inputSchema.parse(input);
6070
- const executionId = randomUUID2();
7208
+ const executionId = randomUUID3();
6071
7209
  const controller = new AbortController();
6072
7210
  this.abortControllers.set(executionId, controller);
6073
7211
  this.registerWithCostScope(executionId);
@@ -6075,7 +7213,7 @@ var AxlRuntime = class extends EventEmitter2 {
6075
7213
  executionId,
6076
7214
  workflow: name,
6077
7215
  status: "running",
6078
- steps: [],
7216
+ events: [],
6079
7217
  totalCost: 0,
6080
7218
  startedAt: Date.now(),
6081
7219
  duration: 0
@@ -6091,20 +7229,35 @@ var AxlRuntime = class extends EventEmitter2 {
6091
7229
  sessionHistory,
6092
7230
  signal: controller.signal,
6093
7231
  onTrace: (event) => {
6094
- execInfo.steps.push(event);
6095
- if (event.cost) execInfo.totalCost += event.cost;
7232
+ pushEventBounded(execInfo, event, this.maxEventsPerExecution);
7233
+ execInfo.totalCost += eventCostContribution(event);
6096
7234
  this.emit("trace", event);
6097
- this.outputTraceEvent(event);
6098
- if (event.type === "handoff") {
7235
+ this.outputAxlEvent(event);
7236
+ if (event.type === "handoff_start") {
6099
7237
  const sessionId = options?.metadata?.sessionId;
6100
7238
  if (sessionId) {
6101
- const data = event.data;
6102
7239
  this.appendHandoffRecord(sessionId, {
6103
- source: event.agent ?? "",
6104
- target: data?.target ?? "",
6105
- mode: data?.mode ?? "oneway",
7240
+ source: event.data.source,
7241
+ target: event.data.target,
7242
+ mode: event.data.mode,
6106
7243
  timestamp: event.timestamp,
6107
- duration: data?.duration ?? void 0
7244
+ toAskId: event.toAskId
7245
+ });
7246
+ }
7247
+ } else if (event.type === "handoff_return") {
7248
+ const sessionId = options?.metadata?.sessionId;
7249
+ if (sessionId) {
7250
+ this.updateHandoffDuration(sessionId, {
7251
+ toAskId: event.toAskId,
7252
+ duration: event.data.duration
7253
+ });
7254
+ }
7255
+ } else if (event.type === "ask_end") {
7256
+ const sessionId = options?.metadata?.sessionId;
7257
+ if (sessionId && event.askId && typeof event.duration === "number") {
7258
+ this.updateHandoffDuration(sessionId, {
7259
+ toAskId: event.askId,
7260
+ duration: event.duration
6108
7261
  });
6109
7262
  }
6110
7263
  }
@@ -6131,45 +7284,13 @@ var AxlRuntime = class extends EventEmitter2 {
6131
7284
  "axl.execution.id": executionId,
6132
7285
  "axl.workflow.input_hash": hashInput(validated)
6133
7286
  },
6134
- async (span) => {
6135
- try {
6136
- ctx._emitWorkflowStart(validated);
6137
- const result = await workflow2.handler(ctx);
6138
- const output = workflow2.outputSchema ? workflow2.outputSchema.parse(result) : result;
6139
- execInfo.status = "completed";
6140
- execInfo.completedAt = Date.now();
6141
- execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6142
- execInfo.result = output;
6143
- ctx._emitWorkflowEnd({
6144
- status: "completed",
6145
- duration: execInfo.duration,
6146
- result: output
6147
- });
6148
- if (this.stateStore.deleteCheckpoints) {
6149
- await this.stateStore.deleteCheckpoints(executionId);
6150
- }
6151
- span.setAttribute("axl.workflow.cost", execInfo.totalCost);
6152
- span.setAttribute("axl.workflow.duration", execInfo.duration);
6153
- this.persistExecution(execInfo);
6154
- return output;
6155
- } catch (err) {
6156
- const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
6157
- execInfo.status = "failed";
6158
- execInfo.completedAt = Date.now();
6159
- execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6160
- execInfo.error = err instanceof Error ? err.message : String(err);
6161
- ctx._emitWorkflowEnd({
6162
- status: "failed",
6163
- duration: execInfo.duration,
6164
- error: execInfo.error,
6165
- ...aborted ? { aborted: true } : {}
6166
- });
6167
- this.persistExecution(execInfo);
6168
- throw err;
6169
- } finally {
6170
- this.abortControllers.delete(executionId);
6171
- }
6172
- }
7287
+ (span) => this.runWorkflowBody({
7288
+ workflow: workflow2,
7289
+ ctx,
7290
+ execInfo,
7291
+ validated,
7292
+ span
7293
+ })
6173
7294
  );
6174
7295
  }
6175
7296
  /** Execute a workflow and return a stream. */
@@ -6177,21 +7298,20 @@ var AxlRuntime = class extends EventEmitter2 {
6177
7298
  const axlStream = new AxlStream();
6178
7299
  const controller = new AbortController();
6179
7300
  axlStream.on("close", () => controller.abort());
7301
+ const executionId = randomUUID3();
7302
+ this.abortControllers.set(executionId, controller);
6180
7303
  let execInfo;
6181
- let ctx;
6182
7304
  const run = async () => {
6183
7305
  const workflow2 = this.workflows.get(name);
6184
7306
  if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
6185
7307
  const validated = workflow2.inputSchema.parse(input);
6186
- const executionId = randomUUID2();
6187
- this.abortControllers.set(executionId, controller);
6188
7308
  const sessionHistory = options?.metadata?.sessionHistory ?? void 0;
6189
7309
  this.registerWithCostScope(executionId);
6190
7310
  execInfo = {
6191
7311
  executionId,
6192
7312
  workflow: name,
6193
7313
  status: "running",
6194
- steps: [],
7314
+ events: [],
6195
7315
  totalCost: 0,
6196
7316
  startedAt: Date.now(),
6197
7317
  duration: 0
@@ -6205,69 +7325,46 @@ var AxlRuntime = class extends EventEmitter2 {
6205
7325
  providerRegistry: this.providerRegistry,
6206
7326
  sessionHistory,
6207
7327
  signal: controller.signal,
7328
+ // Sentinel `onToken` so the streaming code path in WorkflowContext
7329
+ // is entered (it's gated on `this.onToken` being defined). The
7330
+ // actual `token` AxlEvents are emitted via `emitEvent` and reach
7331
+ // the wire through `onTrace` — this callback is just the gate.
7332
+ onToken: () => {
7333
+ },
6208
7334
  onTrace: (event) => {
6209
- execInfo.steps.push(event);
6210
- if (event.cost) execInfo.totalCost += event.cost;
7335
+ pushEventBounded(execInfo, event, this.maxEventsPerExecution);
7336
+ execInfo.totalCost += eventCostContribution(event);
6211
7337
  this.emit("trace", event);
6212
- this.outputTraceEvent(event);
6213
- if (event.type === "handoff") {
6214
- const data = event.data;
6215
- axlStream._push({
6216
- type: "handoff",
6217
- source: event.agent ?? "",
6218
- target: data?.target ?? "",
6219
- mode: data?.mode ?? void 0
6220
- });
7338
+ this.outputAxlEvent(event);
7339
+ axlStream._push(event);
7340
+ if (event.type === "handoff_start") {
6221
7341
  const sessionId = options?.metadata?.sessionId;
6222
7342
  if (sessionId) {
6223
7343
  this.appendHandoffRecord(sessionId, {
6224
- source: event.agent ?? "",
6225
- target: data?.target ?? "",
6226
- mode: data?.mode ?? "oneway",
7344
+ source: event.data.source,
7345
+ target: event.data.target,
7346
+ mode: event.data.mode,
6227
7347
  timestamp: event.timestamp,
6228
- duration: data?.duration ?? void 0
7348
+ toAskId: event.toAskId
7349
+ });
7350
+ }
7351
+ } else if (event.type === "handoff_return") {
7352
+ const sessionId = options?.metadata?.sessionId;
7353
+ if (sessionId) {
7354
+ this.updateHandoffDuration(sessionId, {
7355
+ toAskId: event.toAskId,
7356
+ duration: event.data.duration
7357
+ });
7358
+ }
7359
+ } else if (event.type === "ask_end") {
7360
+ const sessionId = options?.metadata?.sessionId;
7361
+ if (sessionId && event.askId && typeof event.duration === "number") {
7362
+ this.updateHandoffDuration(sessionId, {
7363
+ toAskId: event.askId,
7364
+ duration: event.duration
6229
7365
  });
6230
7366
  }
6231
- } else if (event.type === "tool_approval") {
6232
- const data = event.data;
6233
- axlStream._push({
6234
- type: "tool_approval",
6235
- name: event.tool ?? "",
6236
- args: data?.args,
6237
- approved: data?.approved === true,
6238
- ...data?.reason ? { reason: data.reason } : {}
6239
- });
6240
- } else if (event.type === "tool_denied") {
6241
- } else if (event.type === "agent_call") {
6242
- axlStream._push({
6243
- type: "agent_end",
6244
- agent: event.agent ?? "",
6245
- cost: event.cost,
6246
- duration: event.duration
6247
- });
6248
- } else if (event.type === "tool_call") {
6249
- axlStream._push({
6250
- type: "tool_result",
6251
- name: event.tool ?? "",
6252
- result: event.data?.result,
6253
- callId: event.data?.callId
6254
- });
6255
7367
  }
6256
- axlStream._push({ type: "step", step: event.step, data: event });
6257
- },
6258
- onToken: (token) => {
6259
- axlStream._push({ type: "token", data: token });
6260
- },
6261
- onToolCall: (call) => {
6262
- axlStream._push({
6263
- type: "tool_call",
6264
- name: call.name,
6265
- args: call.args,
6266
- callId: call.callId
6267
- });
6268
- },
6269
- onAgentStart: (info) => {
6270
- axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
6271
7368
  },
6272
7369
  pendingDecisions: this.pendingDecisionResolvers,
6273
7370
  awaitHumanHandler: options?.awaitHumanHandler,
@@ -6284,7 +7381,6 @@ var AxlRuntime = class extends EventEmitter2 {
6284
7381
  policy: "finish_and_stop"
6285
7382
  }
6286
7383
  });
6287
- ctx = wfCtx;
6288
7384
  return this.spanManager.withSpanAsync(
6289
7385
  "axl.workflow.execute",
6290
7386
  {
@@ -6292,49 +7388,25 @@ var AxlRuntime = class extends EventEmitter2 {
6292
7388
  "axl.execution.id": executionId,
6293
7389
  "axl.workflow.input_hash": hashInput(validated)
6294
7390
  },
6295
- async (span) => {
6296
- try {
6297
- wfCtx._emitWorkflowStart(validated);
6298
- const rawResult = await workflow2.handler(wfCtx);
6299
- const result = workflow2.outputSchema ? workflow2.outputSchema.parse(rawResult) : rawResult;
6300
- execInfo.status = "completed";
6301
- execInfo.completedAt = Date.now();
6302
- execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6303
- execInfo.result = result;
6304
- wfCtx._emitWorkflowEnd({
6305
- status: "completed",
6306
- duration: execInfo.duration,
6307
- result
6308
- });
6309
- if (this.stateStore.deleteCheckpoints) {
6310
- await this.stateStore.deleteCheckpoints(executionId);
6311
- }
6312
- span.setAttribute("axl.workflow.cost", execInfo.totalCost);
6313
- span.setAttribute("axl.workflow.duration", execInfo.duration);
6314
- this.persistExecution(execInfo);
6315
- return result;
6316
- } finally {
6317
- this.abortControllers.delete(executionId);
6318
- }
6319
- }
7391
+ (span) => this.runWorkflowBody({
7392
+ workflow: workflow2,
7393
+ ctx: wfCtx,
7394
+ execInfo,
7395
+ validated,
7396
+ span
7397
+ })
6320
7398
  );
6321
7399
  };
6322
- run().then((result) => axlStream._done(result)).catch((err) => {
6323
- if (execInfo) {
6324
- const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
7400
+ run().then((result) => axlStream._done(result, executionId)).catch((err) => {
7401
+ if (execInfo && execInfo.status === "running") {
6325
7402
  execInfo.status = "failed";
6326
7403
  execInfo.completedAt = Date.now();
6327
7404
  execInfo.duration = execInfo.completedAt - execInfo.startedAt;
6328
7405
  execInfo.error = err instanceof Error ? err.message : String(err);
6329
- ctx?._emitWorkflowEnd({
6330
- status: "failed",
6331
- duration: execInfo.duration,
6332
- error: execInfo.error,
6333
- ...aborted ? { aborted: true } : {}
6334
- });
6335
7406
  this.persistExecution(execInfo);
6336
7407
  }
6337
- axlStream._error(err instanceof Error ? err : new Error(String(err)));
7408
+ this.abortControllers.delete(executionId);
7409
+ axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
6338
7410
  });
6339
7411
  return axlStream;
6340
7412
  }
@@ -6594,11 +7666,11 @@ var AxlRuntime = class extends EventEmitter2 {
6594
7666
  * need embedder token counts, subscribe to `runtime.on('trace', ...)` and
6595
7667
  * read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
6596
7668
  *
6597
- * Pass `{ captureTraces: true }` to also collect the raw `TraceEvent[]` observed
7669
+ * Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
6598
7670
  * during `fn()`. This is opt-in because it keeps every event in memory for the
6599
7671
  * duration of the call — useful for eval per-item capture, debugging, and test
6600
7672
  * assertions, but overhead grows with trace volume. When enabled, verbose-mode
6601
- * `agent_call.data.messages` snapshots are omitted from captured events (still
7673
+ * `agent_call_start.data.messages` snapshots are omitted from captured events (still
6602
7674
  * broadcast via onTrace) to keep memory bounded — callers who need the full
6603
7675
  * verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
6604
7676
  *
@@ -6618,8 +7690,8 @@ var AxlRuntime = class extends EventEmitter2 {
6618
7690
  const capturedTraces = options?.captureTraces ? [] : void 0;
6619
7691
  const listener = (event) => {
6620
7692
  if (!scope.trackedIds.has(event.executionId)) return;
6621
- if (event.cost) scope.totalCost += event.cost;
6622
- if (event.type === "agent_call") {
7693
+ scope.totalCost += eventCostContribution(event);
7694
+ if (event.type === "agent_call_end") {
6623
7695
  if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
6624
7696
  agentCalls++;
6625
7697
  if (event.tokens) {
@@ -6632,7 +7704,8 @@ var AxlRuntime = class extends EventEmitter2 {
6632
7704
  workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
6633
7705
  }
6634
7706
  if (capturedTraces) {
6635
- if (event.type === "agent_call" && event.data) {
7707
+ if (event.type === "token" || event.type === "partial_object") return;
7708
+ if (event.type === "agent_call_start" && event.data) {
6636
7709
  const d = event.data;
6637
7710
  if ("messages" in d) {
6638
7711
  const rest = {};
@@ -6697,7 +7770,7 @@ var AxlRuntime = class extends EventEmitter2 {
6697
7770
  * The emit('trace', event) call happens before this method is called, so
6698
7771
  * programmatic subscribers always receive events regardless of trace config.
6699
7772
  */
6700
- outputTraceEvent(event) {
7773
+ outputAxlEvent(event) {
6701
7774
  const traceConfig = this.config.trace;
6702
7775
  if (!traceConfig?.enabled) return;
6703
7776
  const level = traceConfig.level ?? "steps";
@@ -6721,9 +7794,9 @@ var AxlRuntime = class extends EventEmitter2 {
6721
7794
  }
6722
7795
  return;
6723
7796
  }
6724
- this.logTraceEvent(event);
7797
+ this.logAxlEvent(event);
6725
7798
  }
6726
- logTraceEvent(event) {
7799
+ logAxlEvent(event) {
6727
7800
  const level = this.config.trace?.level ?? "steps";
6728
7801
  const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
6729
7802
  const parts = [`[axl] execution:${event.executionId}`];
@@ -6734,7 +7807,7 @@ var AxlRuntime = class extends EventEmitter2 {
6734
7807
  const status = d?.aborted ? "aborted" : d?.status ?? "completed";
6735
7808
  parts.push(`${workflowPrefix}${status}`);
6736
7809
  if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
6737
- } else if (event.type === "agent_call") {
7810
+ } else if (event.type === "agent_call_end") {
6738
7811
  parts.push(`${workflowPrefix}step:${event.step} agent_call`);
6739
7812
  if (event.agent) parts.push(`agent:${event.agent}`);
6740
7813
  if (event.promptVersion) parts.push(`version:${event.promptVersion}`);
@@ -6744,7 +7817,7 @@ var AxlRuntime = class extends EventEmitter2 {
6744
7817
  if (level === "full" && event.data) {
6745
7818
  parts.push(`data:${JSON.stringify(event.data)}`);
6746
7819
  }
6747
- } else if (event.type === "tool_call") {
7820
+ } else if (event.type === "tool_call_end") {
6748
7821
  parts.push(`${workflowPrefix}step:${event.step} tool_call`);
6749
7822
  if (event.tool) parts.push(`tool:${event.tool}`);
6750
7823
  if (event.duration) parts.push(`${event.duration}ms`);
@@ -6761,8 +7834,9 @@ var AxlRuntime = class extends EventEmitter2 {
6761
7834
  parts.push(`${workflowPrefix}log: ${JSON.stringify(event.data)}`);
6762
7835
  } else {
6763
7836
  parts.push(`${workflowPrefix}${event.type}`);
6764
- if (level === "full" && event.data) {
6765
- parts.push(`data:${JSON.stringify(event.data)}`);
7837
+ const data = event.data;
7838
+ if (level === "full" && data !== void 0) {
7839
+ parts.push(`data:${JSON.stringify(data)}`);
6766
7840
  }
6767
7841
  }
6768
7842
  console.log(parts.join(" | "));
@@ -6782,6 +7856,35 @@ var AxlRuntime = class extends EventEmitter2 {
6782
7856
  }).catch(() => {
6783
7857
  });
6784
7858
  }
7859
+ /**
7860
+ * Patch `duration` on the handoff record matching `toAskId` (the target
7861
+ * frame's askId, which the runtime stamps on `handoff_start` and which
7862
+ * appears as `askId` on the target's `ask_end`).
7863
+ *
7864
+ * Called from two places per handoff:
7865
+ * - target's `ask_end` — first-write source for ONEWAY (no return event).
7866
+ * - `handoff_return` — overwrites for ROUNDTRIP with the round-trip
7867
+ * wall-clock figure (intentional: round-trip
7868
+ * duration is the more useful metric for the
7869
+ * source-paused-for-N-ms semantic).
7870
+ *
7871
+ * No-op if no record matches `toAskId` (e.g., stream subscribed mid-
7872
+ * handoff, or session metadata got wiped) — `duration` stays undefined,
7873
+ * which is the documented default for `HandoffRecord`.
7874
+ */
7875
+ updateHandoffDuration(sessionId, match) {
7876
+ this.stateStore.getSessionMeta(sessionId, "handoffHistory").then((existing) => {
7877
+ const history = existing ?? [];
7878
+ for (const r of history) {
7879
+ if (r.toAskId === match.toAskId) {
7880
+ r.duration = match.duration;
7881
+ return this.stateStore.saveSessionMeta(sessionId, "handoffHistory", history);
7882
+ }
7883
+ }
7884
+ return void 0;
7885
+ }).catch(() => {
7886
+ });
7887
+ }
6785
7888
  };
6786
7889
 
6787
7890
  // src/state/redis.ts
@@ -6844,27 +7947,13 @@ var RedisStore = class _RedisStore {
6844
7947
  return "axl:eval-history-ids";
6845
7948
  }
6846
7949
  // ── Checkpoints ──────────────────────────────────────────────────────
6847
- async saveCheckpoint(executionId, step, data) {
6848
- await this.client.hSet(this.checkpointKey(executionId), String(step), JSON.stringify(data));
7950
+ async saveCheckpoint(executionId, name, data) {
7951
+ await this.client.hSet(this.checkpointKey(executionId), name, JSON.stringify(data));
6849
7952
  }
6850
- async getCheckpoint(executionId, step) {
6851
- const raw = await this.client.hGet(this.checkpointKey(executionId), String(step));
7953
+ async getCheckpoint(executionId, name) {
7954
+ const raw = await this.client.hGet(this.checkpointKey(executionId), name);
6852
7955
  return raw != null ? JSON.parse(raw) : null;
6853
7956
  }
6854
- async getLatestCheckpoint(executionId) {
6855
- const all = await this.client.hGetAll(this.checkpointKey(executionId));
6856
- if (!all || Object.keys(all).length === 0) return null;
6857
- let maxStep = -1;
6858
- let maxData = null;
6859
- for (const [stepStr, raw] of Object.entries(all)) {
6860
- const step = Number(stepStr);
6861
- if (step > maxStep) {
6862
- maxStep = step;
6863
- maxData = JSON.parse(raw);
6864
- }
6865
- }
6866
- return { step: maxStep, data: maxData };
6867
- }
6868
7957
  // ── Sessions ─────────────────────────────────────────────────────────
6869
7958
  async saveSession(sessionId, history) {
6870
7959
  await this.client.set(this.sessionKey(sessionId), JSON.stringify(history));
@@ -7149,11 +8238,13 @@ function cosineSimilarity2(a, b) {
7149
8238
  return denom === 0 ? 0 : dot / denom;
7150
8239
  }
7151
8240
  export {
8241
+ AXL_EVENT_TYPES,
7152
8242
  AnthropicProvider,
7153
8243
  AxlError,
7154
8244
  AxlRuntime,
7155
8245
  AxlStream,
7156
8246
  BudgetExceededError,
8247
+ COST_BEARING_LEAF_TYPES,
7157
8248
  GeminiProvider,
7158
8249
  GuardrailError,
7159
8250
  InMemoryVectorStore,
@@ -7168,6 +8259,8 @@ export {
7168
8259
  OpenAIResponsesProvider,
7169
8260
  ProviderRegistry,
7170
8261
  QuorumNotMet,
8262
+ REDACTED,
8263
+ REDACTION_RULES,
7171
8264
  RedisStore,
7172
8265
  SQLiteStore,
7173
8266
  Session,
@@ -7180,7 +8273,12 @@ export {
7180
8273
  agent,
7181
8274
  createSpanManager,
7182
8275
  defineConfig,
8276
+ eventCostContribution,
7183
8277
  extractJson,
8278
+ isCostBearingLeaf,
8279
+ isRootLevel,
8280
+ parsePartialJson,
8281
+ redactEvent,
7184
8282
  resolveThinkingOptions,
7185
8283
  tool,
7186
8284
  workflow,