@demlik/tea 0.17.0 → 0.18.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.
Files changed (119) hide show
  1. package/README.md +3 -2
  2. package/dist/agent/index.d.ts +182 -35
  3. package/dist/agent/index.js +11 -11
  4. package/dist/{chunk-KWNCM6XM.js → chunk-AHW2CLPT.js} +65 -18
  5. package/dist/chunk-AHW2CLPT.js.map +1 -0
  6. package/dist/{chunk-K4ETBNKS.js → chunk-AIIXV4XB.js} +38 -9
  7. package/dist/chunk-AIIXV4XB.js.map +1 -0
  8. package/dist/{chunk-LYVOGOBC.js → chunk-BRX3243I.js} +3 -3
  9. package/dist/{chunk-LYVOGOBC.js.map → chunk-BRX3243I.js.map} +1 -1
  10. package/dist/{chunk-GWWCCSDH.js → chunk-C3G2QFEZ.js} +4 -4
  11. package/dist/{chunk-GWWCCSDH.js.map → chunk-C3G2QFEZ.js.map} +1 -1
  12. package/dist/{chunk-C4U5D5HP.js → chunk-FNGZMVIT.js} +3 -3
  13. package/dist/chunk-FNGZMVIT.js.map +1 -0
  14. package/dist/{chunk-QWIPH4YH.js → chunk-H2ELKHI6.js} +16 -6
  15. package/dist/chunk-H2ELKHI6.js.map +1 -0
  16. package/dist/chunk-H3MWNH7A.js +119 -0
  17. package/dist/chunk-H3MWNH7A.js.map +1 -0
  18. package/dist/chunk-I7E7AD2S.js +213 -0
  19. package/dist/chunk-I7E7AD2S.js.map +1 -0
  20. package/dist/{chunk-OYR7ENKU.js → chunk-IHUNKHDX.js} +3 -3
  21. package/dist/{chunk-OYR7ENKU.js.map → chunk-IHUNKHDX.js.map} +1 -1
  22. package/dist/chunk-JVE2LBFJ.js +21 -0
  23. package/dist/chunk-JVE2LBFJ.js.map +1 -0
  24. package/dist/chunk-JVKU2UQC.js +34 -0
  25. package/dist/chunk-JVKU2UQC.js.map +1 -0
  26. package/dist/{chunk-OPH6Y4UT.js → chunk-JYNYG5SF.js} +4 -4
  27. package/dist/{chunk-OPH6Y4UT.js.map → chunk-JYNYG5SF.js.map} +1 -1
  28. package/dist/{chunk-XBZQT2TD.js → chunk-PCJA2HTZ.js} +437 -162
  29. package/dist/chunk-PCJA2HTZ.js.map +1 -0
  30. package/dist/{chunk-DFUEAA6L.js → chunk-PQSDUX6B.js} +3 -3
  31. package/dist/{chunk-DFUEAA6L.js.map → chunk-PQSDUX6B.js.map} +1 -1
  32. package/dist/{chunk-6XBEYZME.js → chunk-WTSOW2MD.js} +5 -5
  33. package/dist/{chunk-6XBEYZME.js.map → chunk-WTSOW2MD.js.map} +1 -1
  34. package/dist/{chunk-4PXNZ5UY.js → chunk-XVIKSRCK.js} +4 -4
  35. package/dist/{chunk-4PXNZ5UY.js.map → chunk-XVIKSRCK.js.map} +1 -1
  36. package/dist/{chunk-4NTCPTNQ.js → chunk-YIMOFXST.js} +3 -3
  37. package/dist/{chunk-4NTCPTNQ.js.map → chunk-YIMOFXST.js.map} +1 -1
  38. package/dist/{core-rDbOaJTN.d.ts → core-Ce9ncpuv.d.ts} +5 -4
  39. package/dist/do/index.d.ts +140 -64
  40. package/dist/do/index.js +17 -18
  41. package/dist/do/index.js.map +1 -1
  42. package/dist/drive-loop-CsUsW3Wo.d.ts +127 -0
  43. package/dist/{durable-effects-CNaw97Be.d.ts → durable-effects-BmUrRA-h.d.ts} +1 -1
  44. package/dist/effect/index.d.ts +6 -150
  45. package/dist/effect/index.js +4 -96
  46. package/dist/effect/index.js.map +1 -1
  47. package/dist/extension/index.d.ts +7 -5
  48. package/dist/extension/index.js +2 -3
  49. package/dist/extension/index.js.map +1 -1
  50. package/dist/flow/index.d.ts +13 -13
  51. package/dist/flow/index.js +13 -12
  52. package/dist/flow/index.js.map +1 -1
  53. package/dist/idempotency/index.d.ts +3 -3
  54. package/dist/idempotency/index.js +4 -4
  55. package/dist/{index-q1PoukOZ.d.ts → index-2kNxy6az.d.ts} +2 -2
  56. package/dist/{index-B_BxplWi.d.ts → index-B1Wzarkl.d.ts} +1 -1
  57. package/dist/{index-Cberb5Rr.d.ts → index-Bf9_bNql.d.ts} +2 -2
  58. package/dist/index-BnsAqn0-.d.ts +286 -0
  59. package/dist/{index-Cx0F2M34.d.ts → index-Ce5GYAuI.d.ts} +2 -2
  60. package/dist/{index-CWmsXNdX.d.ts → index-D8sYVzCI.d.ts} +1 -1
  61. package/dist/{index-CcrKLtse.d.ts → index-DWdg7R1l.d.ts} +1 -1
  62. package/dist/{index-BOrsbdAf.d.ts → index-Dy3379ve.d.ts} +5 -5
  63. package/dist/{index-DaX_P35Q.d.ts → index-e36uEyl4.d.ts} +3 -3
  64. package/dist/index.d.ts +109 -36
  65. package/dist/index.js +2 -2
  66. package/dist/jev/index.d.ts +8 -8
  67. package/dist/jev/index.js +6 -7
  68. package/dist/jev/index.js.map +1 -1
  69. package/dist/{machine-BcfCFMYF.d.ts → machine-CuojfZup.d.ts} +353 -57
  70. package/dist/machine-viz/index.d.ts +1 -1
  71. package/dist/machine-viz/index.js +2 -2
  72. package/dist/mem/index.d.ts +6 -6
  73. package/dist/mem/index.js +6 -2
  74. package/dist/mem/index.js.map +1 -1
  75. package/dist/node/index.d.ts +16 -10
  76. package/dist/node/index.js +22 -3
  77. package/dist/node/index.js.map +1 -1
  78. package/dist/otel/index.d.ts +161 -0
  79. package/dist/otel/index.js +403 -0
  80. package/dist/otel/index.js.map +1 -0
  81. package/dist/paginate/index.d.ts +4 -4
  82. package/dist/paginate/index.js +5 -5
  83. package/dist/parity/index.d.ts +3 -3
  84. package/dist/parity/index.js +3 -3
  85. package/dist/pbt/index.d.ts +166 -53
  86. package/dist/pbt/index.js +2 -2
  87. package/dist/pbt/index.js.map +1 -1
  88. package/dist/persistence/index.d.ts +5 -5
  89. package/dist/persistence/index.js +5 -5
  90. package/dist/promise/index.d.ts +2 -2
  91. package/dist/promise/index.js +4 -3
  92. package/dist/react/index.d.ts +2 -2
  93. package/dist/resilience/index.d.ts +13 -9
  94. package/dist/resilience/index.js +5 -5
  95. package/dist/resilience/index.js.map +1 -1
  96. package/dist/{runtime-types-B62Ucnxr.d.ts → runtime-types-BKg2CGMx.d.ts} +88 -14
  97. package/dist/testing/effect/index.d.ts +65 -0
  98. package/dist/testing/effect/index.js +148 -0
  99. package/dist/testing/effect/index.js.map +1 -0
  100. package/dist/testing/index.d.ts +12 -158
  101. package/dist/testing/index.js +4 -130
  102. package/dist/testing/index.js.map +1 -1
  103. package/dist/testing/promise/index.d.ts +41 -0
  104. package/dist/testing/promise/index.js +37 -0
  105. package/dist/testing/promise/index.js.map +1 -0
  106. package/dist/timing/index.d.ts +4 -4
  107. package/dist/timing/index.js +4 -4
  108. package/dist/{types-DPm5YCDQ.d.ts → types-SzNSHKlV.d.ts} +1 -1
  109. package/dist/work-queue/index.d.ts +4 -4
  110. package/dist/work-queue/index.js +4 -3
  111. package/dist/work-queue/index.js.map +1 -1
  112. package/package.json +25 -13
  113. package/dist/chunk-C4U5D5HP.js.map +0 -1
  114. package/dist/chunk-G5E74IJB.js +0 -8
  115. package/dist/chunk-G5E74IJB.js.map +0 -1
  116. package/dist/chunk-K4ETBNKS.js.map +0 -1
  117. package/dist/chunk-KWNCM6XM.js.map +0 -1
  118. package/dist/chunk-QWIPH4YH.js.map +0 -1
  119. package/dist/chunk-XBZQT2TD.js.map +0 -1
@@ -1,11 +1,10 @@
1
- import { describeError } from './chunk-G5E74IJB.js';
2
- import { createMonitoredRun } from './chunk-4PXNZ5UY.js';
1
+ import { createMonitoredRun } from './chunk-XVIKSRCK.js';
3
2
  import { createFanOut, initFanOut } from './chunk-QNGYQMZB.js';
4
- import { createResilientCall } from './chunk-6XBEYZME.js';
3
+ import { createResilientCall } from './chunk-WTSOW2MD.js';
4
+ import { deadlinesSub, subscribeDeadline } from './chunk-C3G2QFEZ.js';
5
5
  import { rejectAll, unchecked } from './chunk-4LPY67E4.js';
6
- import { deadlinesSub, subscribeDeadline } from './chunk-GWWCCSDH.js';
7
- import { run, driveToDone } from './chunk-QWIPH4YH.js';
8
- import { Cmd, Outcome, cmdEdgeOf, detachWorkOf, absurd, defineMachine, validateSync, malformedResult } from './chunk-K4ETBNKS.js';
6
+ import { run, driveToDone } from './chunk-H2ELKHI6.js';
7
+ import { Cmd, absurd, Outcome, cmdEdgeOf, detachWorkOf, defineMachine, validateSync, malformedResult, describeError, DriveFailedError } from './chunk-AIIXV4XB.js';
9
8
 
10
9
  // src/internal/llm-call/index.ts
11
10
  function createLlmCall(config, rng = Math.random) {
@@ -145,12 +144,47 @@ function isCompactionSummary(value) {
145
144
  var compactionSummarySchema = schemaFromGuard(isCompactionSummary, "a CompactionSummary");
146
145
 
147
146
  // src/agent/internal.ts
147
+ var NO_USAGE = { inputTokens: 0, outputTokens: 0 };
148
148
  function freshConversation() {
149
149
  return {
150
150
  turns: [],
151
151
  toolRecords: [],
152
152
  turnCount: 0,
153
- awaiting: { kind: "llm" }
153
+ awaiting: { kind: "llm" },
154
+ contextTokens: null
155
+ };
156
+ }
157
+ function addUsage(total, turn) {
158
+ const optional = (key) => total[key] === void 0 && turn[key] === void 0 ? {} : { [key]: (total[key] ?? 0) + (turn[key] ?? 0) };
159
+ return {
160
+ inputTokens: total.inputTokens + turn.inputTokens,
161
+ outputTokens: total.outputTokens + turn.outputTokens,
162
+ ...optional("reasoningTokens"),
163
+ ...optional("cachedInputTokens")
164
+ };
165
+ }
166
+ function withTurnUsage(s, conv, turn) {
167
+ const { usage } = turn;
168
+ if (usage === void 0) return [s.usage, { ...conv, contextTokens: null }];
169
+ return [
170
+ addUsage(s.usage, usage),
171
+ { ...conv, contextTokens: usage.inputTokens + usage.outputTokens }
172
+ ];
173
+ }
174
+ function withUsageDefaults(s) {
175
+ const persisted = s;
176
+ const conv = s.conversation;
177
+ const current = persisted.usage !== void 0 && (conv === null || conv.usage === void 0 && conv.contextTokens !== void 0);
178
+ if (current) return s;
179
+ let conversation = null;
180
+ if (conv !== null) {
181
+ const { usage: _lifted, ...rest } = conv;
182
+ conversation = { ...rest, contextTokens: rest.contextTokens ?? null };
183
+ }
184
+ return {
185
+ ...s,
186
+ usage: persisted.usage ?? conv?.usage ?? NO_USAGE,
187
+ conversation
154
188
  };
155
189
  }
156
190
  function dedupeByCallId(calls) {
@@ -171,7 +205,8 @@ function foldSummary(conv, folding, summaryTurn) {
171
205
  ...conv,
172
206
  turns: [summaryTurn, ...survivingTurns],
173
207
  toolRecords: survivingRecords,
174
- awaiting: { kind: "llm" }
208
+ awaiting: { kind: "llm" },
209
+ contextTokens: null
175
210
  };
176
211
  }
177
212
  function currentStage(s) {
@@ -194,6 +229,70 @@ function toCompactRunCmd(cmd) {
194
229
  };
195
230
  }
196
231
 
232
+ // src/agent/types.ts
233
+ function isTokenCount(value) {
234
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
235
+ }
236
+ function isTurnUsage(value) {
237
+ if (value === null || typeof value !== "object") return false;
238
+ const u = value;
239
+ return isTokenCount(u.inputTokens) && isTokenCount(u.outputTokens) && (u.reasoningTokens === void 0 || isTokenCount(u.reasoningTokens)) && (u.cachedInputTokens === void 0 || isTokenCount(u.cachedInputTokens));
240
+ }
241
+ function isStreamingModel(model) {
242
+ return model.length >= 2;
243
+ }
244
+ function isToolCall(value) {
245
+ if (value === null || typeof value !== "object") return false;
246
+ const t = value;
247
+ return typeof t.callId === "string" && typeof t.name === "string" && t.args !== null && typeof t.args === "object";
248
+ }
249
+ function isAgentTurn(value) {
250
+ if (value === null || typeof value !== "object") return false;
251
+ const t = value;
252
+ return typeof t.content === "string" && Array.isArray(t.toolCalls) && t.toolCalls.every(isToolCall) && (t.usage === void 0 || isTurnUsage(t.usage));
253
+ }
254
+ var agentTurnSchema = schemaFromGuard(
255
+ isAgentTurn,
256
+ "an AgentTurn"
257
+ );
258
+ var TOOL_TIMEOUT_TAG = "timeout";
259
+ var TOOL_RETRY_EXHAUSTED_TAG = "retry_exhausted";
260
+ function status(s) {
261
+ if (s.failure !== null) {
262
+ return { kind: "failed", failure: s.failure };
263
+ }
264
+ const run2 = s.run;
265
+ switch (run2.phase) {
266
+ // 2) Never started — monitored-run's `idle` phase is exactly the "has this
267
+ // actually started?" guard, and the status channel keeps that distinction.
268
+ case "idle":
269
+ return { kind: "idle" };
270
+ // 3) The monitored-run terminal failure (deadline / stage). `run.failure`
271
+ // rides ONLY the `failed` arm of the discriminated union, so narrowing on
272
+ // `phase === "failed"` surfaces it with no nullable side field and no
273
+ // fabricated fallback — the old "failed-yet-failure-null" state is
274
+ // unrepresentable.
275
+ case "failed":
276
+ return { kind: "failed", failure: run2.failure };
277
+ // 4) The pipeline finished — the terminal output landed on `state.output`.
278
+ case "done":
279
+ return { kind: "done", output: s.output, usage: s.usage ?? NO_USAGE };
280
+ // 5) Stopped from outside. Terminal, and deliberately NOT folded into
281
+ // `failed`: the unified failure channel answers "what went wrong", and
282
+ // nothing did.
283
+ case "cancelled":
284
+ return { kind: "cancelled", at: run2.at };
285
+ // 6) Live. Awaiting tools is the resumable shape — the outstanding calls are
286
+ // the in-flight fan-out batch (`tools.running`); anything else is plain
287
+ // `running`, which is also where `stale` reads (a soft mark the next
288
+ // progress clears, not an outcome).
289
+ case "running":
290
+ case "stale":
291
+ return s.conversation !== null && s.conversation.awaiting.kind === "tools" ? { kind: "suspended", pending: s.tools.running } : { kind: "running" };
292
+ }
293
+ return absurd(run2);
294
+ }
295
+
197
296
  // src/agent/machine.ts
198
297
  function agentBootMsg(at) {
199
298
  return { type: MsgType.AgentBoot, at };
@@ -201,39 +300,108 @@ function agentBootMsg(at) {
201
300
  function agentCancelMsg(at) {
202
301
  return { type: MsgType.AgentCancel, at };
203
302
  }
303
+ var AGENT_EVENT_TYPES = [
304
+ "BrainStarted",
305
+ "TurnSettled",
306
+ "ToolStarted",
307
+ "ToolSettled",
308
+ "ToolFailed",
309
+ "RunDone"
310
+ ];
204
311
  function agentEvents(opts) {
205
312
  const tools = opts?.tools;
313
+ const projected = /* @__PURE__ */ new WeakSet();
206
314
  return (msg, state) => {
207
315
  const events = [];
208
- if (state.run.phase === "cancelled") return events;
209
- const refused = new Set(state.refusedCalls ?? []);
210
- const routed = tools === void 0 ? null : tools.outcomeOf(msg);
211
- if (routed !== null) {
212
- if (routed.outcome.kind === "ok" && !refused.has(routed.callId)) {
213
- events.push({
214
- type: "ToolSettled",
215
- callId: routed.callId,
216
- result: routed.outcome.result
217
- });
316
+ const run2 = state.run;
317
+ const runId = run2.phase === "idle" ? null : run2.runId;
318
+ if (runId === null) return events;
319
+ if (run2.phase !== "cancelled") {
320
+ const refused = new Set(state.refusedCalls ?? []);
321
+ const routed = tools === void 0 ? null : tools.outcomeOf(msg);
322
+ if (routed !== null) {
323
+ if (routed.outcome.kind === "ok" && !refused.has(routed.callId)) {
324
+ events.push({
325
+ type: "ToolSettled",
326
+ runId,
327
+ // The router claimed it, so it is one of its settled Msgs.
328
+ at: msg.at,
329
+ callId: routed.callId,
330
+ result: routed.outcome.result
331
+ });
332
+ }
333
+ } else {
334
+ projectOwn(msg, runId, events, refused);
218
335
  }
219
- } else {
220
- projectOwn(msg, events, refused);
221
336
  }
222
- if (state.run.phase === "done") {
223
- events.push({ type: "RunDone", output: state.output });
337
+ const outbox = state.lifecycle ?? [];
338
+ if (outbox.length === 0 || projected.has(outbox)) return events;
339
+ projected.add(outbox);
340
+ for (const note of outbox) {
341
+ const event = projectNote(note, runId, state);
342
+ if (event !== null) events.push(event);
224
343
  }
225
344
  return events;
226
345
  };
227
346
  }
228
- function projectOwn(msg, events, refused) {
347
+ function turnSettled(turn, runId, at) {
348
+ return turn.usage === void 0 ? { type: "TurnSettled", runId, at, turn } : { type: "TurnSettled", runId, at, turn, usage: turn.usage };
349
+ }
350
+ function projectNote(note, runId, state) {
351
+ switch (note.kind) {
352
+ case "brain_started":
353
+ return {
354
+ type: "BrainStarted",
355
+ runId,
356
+ at: note.at,
357
+ turn: note.turn,
358
+ purpose: note.purpose,
359
+ model: note.model,
360
+ payload: inFlightPayload(state, note.purpose)
361
+ };
362
+ case "tool_started":
363
+ return {
364
+ type: "ToolStarted",
365
+ runId,
366
+ at: note.at,
367
+ callId: note.call.callId,
368
+ name: note.call.name,
369
+ args: note.call.args
370
+ };
371
+ case "tool_failed":
372
+ return {
373
+ type: "ToolFailed",
374
+ runId,
375
+ at: note.at,
376
+ callId: note.call.callId,
377
+ name: note.call.name,
378
+ failure: note.failure
379
+ };
380
+ case "run_ended": {
381
+ const status2 = ended(state);
382
+ return status2 === null ? null : { type: "RunDone", runId, at: note.at, status: status2 };
383
+ }
384
+ }
385
+ }
386
+ function inFlightPayload(state, key) {
387
+ const call = state.resilience.calls[key];
388
+ return call?.phase === "running" || call?.phase === "waiting_retry" ? call.input.payload : null;
389
+ }
390
+ function ended(state) {
391
+ const s = status(state);
392
+ return s.kind === "done" || s.kind === "failed" || s.kind === "cancelled" ? s : null;
393
+ }
394
+ function projectOwn(msg, runId, events, refused) {
229
395
  switch (msg.type) {
230
396
  case MsgType.ResilientOk:
231
- events.push({ type: "TurnSettled", turn: msg.value.output });
397
+ events.push(turnSettled(msg.value.output, runId, msg.at));
232
398
  break;
233
399
  case MsgType.AgentToolOk:
234
400
  if (!refused.has(msg.callId)) {
235
401
  events.push({
236
402
  type: "ToolSettled",
403
+ runId,
404
+ at: msg.at,
237
405
  callId: msg.callId,
238
406
  result: msg.result
239
407
  });
@@ -290,7 +458,7 @@ function brainHandler(llm, ports) {
290
458
  };
291
459
  }
292
460
 
293
- // src/agent/tool.ts
461
+ // src/agent/tool-cell.ts
294
462
  var REJECTED_TYPE = "tool_rejected";
295
463
  var SNAPSHOT_WRITE_TYPE = "snapshot_write";
296
464
  var SETTLE_SUFFIX = /_(?:ok|err|run)$/;
@@ -306,31 +474,22 @@ var reservedToolNames = /* @__PURE__ */ new Set([
306
474
  function isReservedToolName(name) {
307
475
  return reservedToolNames.has(name);
308
476
  }
309
- function tool(name, spec, handler) {
477
+ function definedTool(door, name, spec, declaredTags, cell) {
310
478
  if (isReservedToolName(name)) {
311
479
  throw new Error(
312
- `tool: "${name}" is reserved \u2014 it is an agent-owned Msg prefix`
480
+ `${door}: "${name}" is reserved \u2014 it is an agent-owned Msg prefix`
313
481
  );
314
482
  }
315
483
  const def = Cmd.define(name, {
316
- // The router parses `args` against `spec.input` before it builds the Cmd,
317
- // so the Cmd's own input schema only names the type.
318
484
  input: unchecked(),
319
485
  ok: spec.ok,
320
- err: [...spec.err, "thrown"]
486
+ err: [...declaredTags, "thrown"]
321
487
  });
322
- const declared = new Set(spec.err);
323
- const settle = {
324
- ok: Outcome.ok,
325
- fail: Outcome.err
326
- };
327
- const asDeclared = (thrown) => {
328
- if (isTagged(thrown) && declared.has(thrown._tag)) return thrown;
329
- return { _tag: "thrown", message: describeError(thrown) };
330
- };
488
+ const declared = new Set(declaredTags);
489
+ const asDeclared = (thrown) => isTagged(thrown) && declared.has(thrown._tag) ? thrown : { _tag: "thrown", message: describeError(thrown) };
331
490
  const interpret = async (cmd, ctx) => {
332
491
  try {
333
- return await handler(cmd.args, ctx, settle);
492
+ return await cell(cmd, ctx);
334
493
  } catch (thrown) {
335
494
  return Outcome.err(asDeclared(thrown));
336
495
  }
@@ -339,6 +498,7 @@ function tool(name, spec, handler) {
339
498
  description: spec.description,
340
499
  args: spec.input,
341
500
  resilience: resilienceOf(spec),
501
+ content: spec.content ?? null,
342
502
  interpret
343
503
  });
344
504
  }
@@ -352,6 +512,21 @@ function resilienceOf(spec) {
352
512
  function isTagged(value) {
353
513
  return typeof value === "object" && value !== null && typeof value._tag === "string";
354
514
  }
515
+
516
+ // src/agent/tool.ts
517
+ function tool(name, spec, handler) {
518
+ const settle = {
519
+ ok: Outcome.ok,
520
+ fail: Outcome.err
521
+ };
522
+ return definedTool(
523
+ "tool",
524
+ name,
525
+ spec,
526
+ spec.err,
527
+ (cmd, ctx) => handler(cmd.args, ctx, settle)
528
+ );
529
+ }
355
530
  var rejected = Cmd.define(REJECTED_TYPE, {
356
531
  input: unchecked(),
357
532
  ok: rejectAll,
@@ -426,7 +601,14 @@ function toolRouter(tools) {
426
601
  return null;
427
602
  };
428
603
  const resilienceOf2 = (call) => byName.get(call.name)?.resilience ?? null;
429
- return { defs, toolOf, interpret, outcomeOf, resilienceOf: resilienceOf2 };
604
+ const partsOf = (call, outcome) => {
605
+ const content = byName.get(call.name)?.content ?? null;
606
+ if (content === null || outcome.kind !== "ok") return null;
607
+ return content(
608
+ outcome.result
609
+ );
610
+ };
611
+ return { defs, toolOf, interpret, outcomeOf, resilienceOf: resilienceOf2, partsOf };
430
612
  }
431
613
  function fanOutInterpret(interpret) {
432
614
  let release = Promise.resolve();
@@ -459,62 +641,6 @@ function toolErrorReason(error) {
459
641
  return Object.keys(detail).length === 0 ? _tag : `${_tag} ${JSON.stringify(detail)}`;
460
642
  }
461
643
 
462
- // src/agent/types.ts
463
- function isStreamingModel(model) {
464
- return model.length >= 2;
465
- }
466
- function isToolCall(value) {
467
- if (value === null || typeof value !== "object") return false;
468
- const t = value;
469
- return typeof t.callId === "string" && typeof t.name === "string" && t.args !== null && typeof t.args === "object";
470
- }
471
- function isAgentTurn(value) {
472
- if (value === null || typeof value !== "object") return false;
473
- const t = value;
474
- return typeof t.content === "string" && Array.isArray(t.toolCalls) && t.toolCalls.every(isToolCall);
475
- }
476
- var agentTurnSchema = schemaFromGuard(
477
- isAgentTurn,
478
- "an AgentTurn"
479
- );
480
- var TOOL_TIMEOUT_TAG = "timeout";
481
- var TOOL_RETRY_EXHAUSTED_TAG = "retry_exhausted";
482
- function status(s) {
483
- if (s.failure !== null) {
484
- return { kind: "failed", failure: s.failure };
485
- }
486
- const run2 = s.run;
487
- switch (run2.phase) {
488
- // 2) Never started — monitored-run's `idle` phase is exactly the "has this
489
- // actually started?" guard, and the status channel keeps that distinction.
490
- case "idle":
491
- return { kind: "idle" };
492
- // 3) The monitored-run terminal failure (deadline / stage). `run.failure`
493
- // rides ONLY the `failed` arm of the discriminated union, so narrowing on
494
- // `phase === "failed"` surfaces it with no nullable side field and no
495
- // fabricated fallback — the old "failed-yet-failure-null" state is
496
- // unrepresentable.
497
- case "failed":
498
- return { kind: "failed", failure: run2.failure };
499
- // 4) The pipeline finished — the terminal output landed on `state.output`.
500
- case "done":
501
- return { kind: "done", output: s.output };
502
- // 5) Stopped from outside. Terminal, and deliberately NOT folded into
503
- // `failed`: the unified failure channel answers "what went wrong", and
504
- // nothing did.
505
- case "cancelled":
506
- return { kind: "cancelled", at: run2.at };
507
- // 6) Live. Awaiting tools is the resumable shape — the outstanding calls are
508
- // the in-flight fan-out batch (`tools.running`); anything else is plain
509
- // `running`, which is also where `stale` reads (a soft mark the next
510
- // progress clears, not an outcome).
511
- case "running":
512
- case "stale":
513
- return s.conversation !== null && s.conversation.awaiting.kind === "tools" ? { kind: "suspended", pending: s.tools.running } : { kind: "running" };
514
- }
515
- return absurd(run2);
516
- }
517
-
518
644
  // src/agent/tool-resilience.ts
519
645
  var TOOL_KEY_PREFIX = "$tool:";
520
646
  var NAME_CALL_SEP = "#";
@@ -718,6 +844,75 @@ function taggedFailure(tagged) {
718
844
  return { ...tagged, reason: toolErrorReason(tagged), kind: "error" };
719
845
  }
720
846
 
847
+ // src/agent/agent-tool.ts
848
+ var AGENT_TOOL_TAGS = [
849
+ "child_failed",
850
+ "child_cancelled"
851
+ ];
852
+ function agentTool(name, spec) {
853
+ const runChild = spec.agent.run;
854
+ const childCtx = spec.childCtx;
855
+ return definedTool(
856
+ "agentTool",
857
+ name,
858
+ { description: spec.description, input: spec.input, ok: spec.ok },
859
+ AGENT_TOOL_TAGS,
860
+ async (cmd, ctx) => {
861
+ const childRunId = `${spec.namespace(ctx)}/${cmd.callId}`;
862
+ const opts = {
863
+ store: spec.store(childRunId, ctx),
864
+ runId: childRunId,
865
+ ...childCtx === void 0 ? {} : { ctx: childCtx(ctx) }
866
+ };
867
+ let final;
868
+ try {
869
+ final = await runChild(spec.prompt(cmd.args), opts);
870
+ } catch (error) {
871
+ if (!(error instanceof DriveFailedError)) throw error;
872
+ return Outcome.err(childFailed(childRunId, error.state));
873
+ }
874
+ if (final.run.phase === "cancelled") {
875
+ return Outcome.err({ _tag: "child_cancelled", childRunId });
876
+ }
877
+ if (final.output === null) {
878
+ throw new Error(
879
+ `agentTool: child run "${childRunId}" ended done with no output`
880
+ );
881
+ }
882
+ return Outcome.ok(spec.result(final.output));
883
+ }
884
+ );
885
+ }
886
+ function childFailed(childRunId, state) {
887
+ const s = status(state);
888
+ if (s.kind !== "failed") {
889
+ throw new Error(
890
+ `agentTool: child run "${childRunId}" rejected on a ${s.kind} Model`
891
+ );
892
+ }
893
+ return { _tag: "child_failed", childRunId, failure: s.failure.reason };
894
+ }
895
+
896
+ // src/agent/content.ts
897
+ function contentParts(content) {
898
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
899
+ }
900
+ var IMAGE_OMITTED = "[image omitted]";
901
+ var FILE_OMITTED = "[file omitted]";
902
+ function omitMedia(parts) {
903
+ return parts.map(placeholderOf);
904
+ }
905
+ function placeholderOf(part) {
906
+ switch (part.type) {
907
+ case "text":
908
+ return part;
909
+ case "image":
910
+ return { type: "text", text: IMAGE_OMITTED };
911
+ case "file":
912
+ return { type: "text", text: FILE_OMITTED };
913
+ }
914
+ }
915
+
721
916
  // src/agent/define-agent.ts
722
917
  function defineAgent(config) {
723
918
  const router = toolRouter(config.tools);
@@ -737,7 +932,7 @@ function definedAgent(config, wiring, overlays) {
737
932
  model: brain,
738
933
  instructions: config.instructions,
739
934
  payloadOf: promptOf,
740
- loadMessages: messagesOf,
935
+ loadMessages: messagesOf(tools.partsOf),
741
936
  toolOf: tools.toolOf,
742
937
  // The per-tool timeout / retry knob (#117). The lid grows no option for
743
938
  // it: the policy is declared on the `tool()` that needs it, and the
@@ -797,9 +992,9 @@ function definedAgent(config, wiring, overlays) {
797
992
  };
798
993
  const signal = opts.signal;
799
994
  if (signal === void 0) {
800
- return ended(driveToDone(handle, begin, isEnded, { failed: isFailed }));
995
+ return ended2(driveToDone(handle, begin, isEnded, { failed: isFailed }));
801
996
  }
802
- return ended(
997
+ return ended2(
803
998
  driveToDone(handle, begin, isEnded, {
804
999
  failed: isFailed,
805
1000
  signal,
@@ -855,7 +1050,7 @@ function compactingMachine(core, knob, brain, tools) {
855
1050
  const agent = createAgent({ ...core, compaction: compactionPolicyOf(knob) });
856
1051
  const wiring = {
857
1052
  tools,
858
- toolInterpret: { compact_run: summarizeWith(brain) }
1053
+ toolInterpret: { compact_run: summarizeWith(brain, tools.partsOf) }
859
1054
  };
860
1055
  return agent.toMachine(
861
1056
  wiring
@@ -865,7 +1060,7 @@ var SUMMARIZE_INSTRUCTION = "Summarize the conversation so far. Preserve the fac
865
1060
  function compactionPolicyOf(knob) {
866
1061
  const keep = knob.keepTurns ?? 0;
867
1062
  return {
868
- planCompaction: (conversation) => conversation.turns.length >= knob.afterTurns ? Math.max(0, conversation.turns.length - keep) : 0,
1063
+ planCompaction: (conversation) => isCompactionDue(knob, conversation) ? Math.max(0, conversation.turns.length - keep) : 0,
869
1064
  payloadOf: (conversation, folding) => ({
870
1065
  instructions: SUMMARIZE_INSTRUCTION,
871
1066
  input: null,
@@ -877,10 +1072,15 @@ function compactionPolicyOf(knob) {
877
1072
  })
878
1073
  };
879
1074
  }
880
- function summarizeWith(model) {
1075
+ function isCompactionDue(knob, conversation) {
1076
+ const { afterTurns, afterContextTokens } = knob;
1077
+ const { contextTokens } = conversation;
1078
+ return afterTurns !== void 0 && conversation.turns.length >= afterTurns || afterContextTokens !== void 0 && contextTokens !== null && contextTokens >= afterContextTokens;
1079
+ }
1080
+ function summarizeWith(model, partsOf) {
881
1081
  return async (cmd) => {
882
1082
  const payload = cmd.input.payload;
883
- const turn = await model(renderPrompt(payload));
1083
+ const turn = await model(renderPrompt(payload, partsOf).map(withoutMedia));
884
1084
  return {
885
1085
  type: MsgType.CompactOk,
886
1086
  key: COMPACTION_PURPOSE,
@@ -896,8 +1096,10 @@ function summarizeWith(model) {
896
1096
  function promptOf(stage, conversation, instructions) {
897
1097
  return { instructions, input: stage ?? null, conversation };
898
1098
  }
899
- var messagesOf = async (call) => renderPrompt(call.payload);
900
- function renderPrompt(prompt) {
1099
+ function messagesOf(partsOf) {
1100
+ return async (call) => renderPrompt(call.payload, partsOf);
1101
+ }
1102
+ function renderPrompt(prompt, partsOf = noParts) {
901
1103
  const head = [];
902
1104
  if (prompt.instructions !== null) {
903
1105
  head.push({ role: "system", content: prompt.instructions });
@@ -908,15 +1110,32 @@ function renderPrompt(prompt) {
908
1110
  const { turns, toolRecords } = prompt.conversation;
909
1111
  const transcript2 = turns.flatMap((turn, i) => [
910
1112
  assistantOf(turn),
911
- ...toolRecords.filter((r) => r.turn === i).map((r) => ({
912
- role: "tool",
913
- callId: r.call.callId,
914
- name: r.call.name,
915
- outcome: r.outcome
916
- }))
1113
+ ...toolRecords.filter((r) => r.turn === i).map((r) => {
1114
+ const message = {
1115
+ role: "tool",
1116
+ callId: r.call.callId,
1117
+ name: r.call.name,
1118
+ outcome: r.outcome
1119
+ };
1120
+ const parts = partsOf(r.call, r.outcome);
1121
+ return parts === null ? message : { ...message, parts };
1122
+ })
917
1123
  ]);
918
1124
  return [...head, ...transcript2];
919
1125
  }
1126
+ function noParts() {
1127
+ return null;
1128
+ }
1129
+ function withoutMedia(message) {
1130
+ switch (message.role) {
1131
+ case "user":
1132
+ return typeof message.content === "string" ? message : { ...message, content: omitMedia(message.content) };
1133
+ case "tool":
1134
+ return message.parts === void 0 ? message : { ...message, parts: omitMedia(message.parts) };
1135
+ default:
1136
+ return message;
1137
+ }
1138
+ }
920
1139
  function assistantOf(turn) {
921
1140
  const { content, toolCalls } = turn;
922
1141
  return "provider" in turn ? { role: "assistant", content, toolCalls, provider: turn.provider } : { role: "assistant", content, toolCalls };
@@ -924,7 +1143,6 @@ function assistantOf(turn) {
924
1143
  function noHost() {
925
1144
  return {};
926
1145
  }
927
- var AGENT_EVENT_TYPES = ["TurnSettled", "ToolSettled", "RunDone"];
928
1146
  function forwardEvents(handle, onEvent) {
929
1147
  const deliver = contained("onEvent", onEvent);
930
1148
  for (const type of AGENT_EVENT_TYPES) {
@@ -997,7 +1215,7 @@ function isMidRun(s) {
997
1215
  function isEnded(s) {
998
1216
  return s.run.phase === "done" || s.run.phase === "cancelled";
999
1217
  }
1000
- function ended(drive) {
1218
+ function ended2(drive) {
1001
1219
  return drive;
1002
1220
  }
1003
1221
  function isFailed(s) {
@@ -1021,7 +1239,13 @@ function transcript(seed) {
1021
1239
  tools.push({ callId: event.callId, result: event.result });
1022
1240
  return;
1023
1241
  case "RunDone":
1024
- outcome = { kind: "done", output: event.output };
1242
+ outcome = event.status;
1243
+ return;
1244
+ // The started and failed events add nothing a transcript keeps: a
1245
+ // turn is kept once it settles, and a failed call is `onToolError`'s.
1246
+ case "BrainStarted":
1247
+ case "ToolStarted":
1248
+ case "ToolFailed":
1025
1249
  return;
1026
1250
  }
1027
1251
  },
@@ -1065,9 +1289,11 @@ function createAgent(config) {
1065
1289
  resilience: llm.init(),
1066
1290
  tools: initFanOut(),
1067
1291
  conversation: null,
1292
+ usage: NO_USAGE,
1068
1293
  compaction: compactRc.init(),
1069
1294
  toolResilience: ladder.init(),
1070
1295
  refusedCalls: [],
1296
+ lifecycle: [],
1071
1297
  failure: null,
1072
1298
  output: null,
1073
1299
  instructions: config.instructions ?? null
@@ -1086,7 +1312,21 @@ function createAgent(config) {
1086
1312
  function fireBrainCall(s, at) {
1087
1313
  const call = brainCall(s);
1088
1314
  const [resilience, cmds] = llm.attempt(s.resilience, call, at);
1089
- return [{ ...s, resilience }, cmds];
1315
+ const started = noted(s, {
1316
+ kind: "brain_started",
1317
+ turn: s.conversation?.turnCount ?? 0,
1318
+ purpose: call.purpose,
1319
+ model: call.model,
1320
+ at
1321
+ });
1322
+ return [{ ...started, resilience }, cmds];
1323
+ }
1324
+ function noted(s, ...notes) {
1325
+ if (notes.length === 0) return s;
1326
+ return { ...s, lifecycle: [...s.lifecycle ?? [], ...notes] };
1327
+ }
1328
+ function toolsStarted(calls, at) {
1329
+ return calls.map((call) => ({ kind: "tool_started", call, at }));
1090
1330
  }
1091
1331
  function compactionCall(conv, folding) {
1092
1332
  const payloadOf = config.compaction?.payloadOf;
@@ -1115,6 +1355,8 @@ function createAgent(config) {
1115
1355
  ...s,
1116
1356
  run: runSlice,
1117
1357
  conversation,
1358
+ // A new run has cost nothing yet (#332).
1359
+ usage: NO_USAGE,
1118
1360
  failure: null,
1119
1361
  // A restart clears the prior run's terminal output — `result()` reads
1120
1362
  // `undefined` again until THIS run finishes (#46).
@@ -1132,12 +1374,14 @@ function createAgent(config) {
1132
1374
  function turn(s, result, at) {
1133
1375
  const conv = requireAwaiting(s, "llm");
1134
1376
  if (conv === null) return [s, []];
1377
+ const [usage, measured] = withTurnUsage(s, conv, result);
1135
1378
  const withTurn = {
1136
- ...conv,
1379
+ ...measured,
1137
1380
  turns: [...conv.turns, result]
1138
1381
  };
1382
+ const counted = { ...s, usage };
1139
1383
  if (result.toolCalls.length === 0) {
1140
- return advanceStage({ ...s, conversation: withTurn }, result, at);
1384
+ return advanceStage({ ...counted, conversation: withTurn }, result, at);
1141
1385
  }
1142
1386
  const batch = dedupeByCallId(result.toolCalls);
1143
1387
  const [tools] = fan.scatter(initFanOut(), batch);
@@ -1146,9 +1390,23 @@ function createAgent(config) {
1146
1390
  awaiting: { kind: "tools", batchTurn: withTurn.turnCount }
1147
1391
  };
1148
1392
  const [runSlice] = run2.progress(s.run, void 0, at);
1149
- const [toolResilience, launchCmds] = armLaunch(s, [], tools.running, at);
1393
+ const [toolResilience, launchCmds, launched] = armLaunch(
1394
+ s,
1395
+ [],
1396
+ tools.running,
1397
+ at
1398
+ );
1150
1399
  return [
1151
- { ...s, run: runSlice, tools, toolResilience, conversation: nextConv },
1400
+ noted(
1401
+ {
1402
+ ...counted,
1403
+ run: runSlice,
1404
+ tools,
1405
+ toolResilience,
1406
+ conversation: nextConv
1407
+ },
1408
+ ...toolsStarted(launched, at)
1409
+ ),
1152
1410
  launchCmds
1153
1411
  ];
1154
1412
  }
@@ -1205,11 +1463,9 @@ function createAgent(config) {
1205
1463
  }
1206
1464
  function armLaunch(s, before, after, at) {
1207
1465
  const known = new Set(before.map((c) => c.callId));
1208
- return ladder.launch(
1209
- toolSlice(s),
1210
- after.filter((c) => !known.has(c.callId)),
1211
- at
1212
- );
1466
+ const launched = after.filter((c) => !known.has(c.callId));
1467
+ const [slice, cmds] = ladder.launch(toolSlice(s), launched, at);
1468
+ return [slice, cmds, launched];
1213
1469
  }
1214
1470
  function settleTool(prev, callId, outcome, at) {
1215
1471
  const conv = requireAwaiting(prev, "tools");
@@ -1217,14 +1473,18 @@ function createAgent(config) {
1217
1473
  const call = prev.tools.running.find((c) => c.callId === callId);
1218
1474
  if (call === void 0) return [prev, []];
1219
1475
  const [tools] = outcome.kind === "ok" ? fan.itemOk(prev.tools, callId, outcome) : fan.itemErr(prev.tools, callId, outcome);
1220
- const [toolResilience, launchCmds] = armLaunch(
1476
+ const [toolResilience, launchCmds, launched] = armLaunch(
1221
1477
  prev,
1222
1478
  prev.tools.running,
1223
1479
  tools.running,
1224
1480
  at
1225
1481
  );
1226
1482
  const refusedCalls = outcome.kind === "ok" ? refusedOf(prev) : [...refusedOf(prev), callId];
1227
- const s = { ...prev, toolResilience, refusedCalls };
1483
+ const s = noted(
1484
+ { ...prev, toolResilience, refusedCalls },
1485
+ ...outcome.kind === "ok" ? [] : [{ kind: "tool_failed", call, failure: outcome, at }],
1486
+ ...toolsStarted(launched, at)
1487
+ );
1228
1488
  const foldedConv = {
1229
1489
  ...conv,
1230
1490
  // Stamp the producing-turn index on the record (#85, A1). At settle time
@@ -1383,29 +1643,34 @@ function createAgent(config) {
1383
1643
  }
1384
1644
  function boot(s, at) {
1385
1645
  const [runSlice] = run2.boot(s.run, at);
1386
- const rebooted = { ...s, run: runSlice };
1387
- if (isSettled(rebooted)) return [rebooted, []];
1388
- const conv = rebooted.conversation;
1389
- if (conv === null) return [rebooted, []];
1646
+ const resumed = { ...s, run: runSlice };
1647
+ if (isSettled(resumed)) return [resumed, []];
1648
+ const conv = resumed.conversation;
1649
+ if (conv === null) return [resumed, []];
1390
1650
  if (conv.awaiting.kind === "llm") {
1391
- return fireBrainCall(rebooted, at);
1651
+ return fireBrainCall(resumed, at);
1392
1652
  }
1393
1653
  if (conv.awaiting.kind === "compacting") {
1394
1654
  const input = compactionCall(conv, conv.awaiting.folding);
1395
1655
  const [compaction, cmds] = compact.attempt(
1396
- rebooted.compaction,
1656
+ resumed.compaction,
1397
1657
  COMPACTION_PURPOSE,
1398
1658
  input,
1399
1659
  at
1400
1660
  );
1401
- return [{ ...rebooted, compaction }, cmds];
1661
+ return [{ ...resumed, compaction }, cmds];
1402
1662
  }
1403
1663
  const [toolResilience, outcome] = ladder.boot(
1404
- toolSlice(rebooted),
1405
- rebooted.tools.running,
1664
+ toolSlice(resumed),
1665
+ resumed.tools.running,
1666
+ at
1667
+ );
1668
+ return applyLadder(
1669
+ noted(resumed, ...toolsStarted(resumed.tools.running, at)),
1670
+ toolResilience,
1671
+ outcome,
1406
1672
  at
1407
1673
  );
1408
- return applyLadder(rebooted, toolResilience, outcome, at);
1409
1674
  }
1410
1675
  function cancel(s, at) {
1411
1676
  const [runSlice] = run2.cancel(s.run, at);
@@ -1435,31 +1700,32 @@ function createAgent(config) {
1435
1700
  if (settled === void 0 || settled === null) return [s, []];
1436
1701
  return settled.outcome.kind === "ok" ? toolOk(s, settled.callId, settled.outcome.result, m.at) : toolErr(s, settled.callId, settled.outcome, m.at);
1437
1702
  };
1703
+ const toolCell = (s, m) => transition(s, m.at, (entered) => foldTool(entered, m));
1438
1704
  const toolCells = {};
1439
1705
  for (const def of tools?.defs ?? []) {
1440
- toolCells[def.okType] = foldTool;
1441
- toolCells[def.errType] = foldTool;
1706
+ toolCells[def.okType] = toolCell;
1707
+ toolCells[def.errType] = toolCell;
1442
1708
  }
1443
1709
  const ownUpdate = {
1444
- [MsgType.AgentStart]: (s, m) => start(s, m.runId, m.at),
1445
- [MsgType.AgentToolOk]: (s, m) => toolOk(s, m.callId, m.result, m.at),
1710
+ [MsgType.AgentStart]: (s, m) => door.start(s, m.runId, m.at),
1711
+ [MsgType.AgentToolOk]: (s, m) => door.toolOk(s, m.callId, m.result, m.at),
1446
1712
  // `agent_tool_err` is `createAgent`'s own raw settle Msg and its wire
1447
1713
  // shape is a bare `reason` string — a consumer routing their own interpret
1448
1714
  // through it never declared a tag, so the failure it mints carries none.
1449
- [MsgType.AgentToolErr]: (s, m) => toolErr(s, m.callId, m.reason, m.at),
1715
+ [MsgType.AgentToolErr]: (s, m) => door.toolErr(s, m.callId, m.reason, m.at),
1450
1716
  // The engine-minted brain-call settle Msgs (from `brainHandlers`' outcome):
1451
1717
  // success runs `succeed` (reset retry + fold the turn), failure runs `fail`.
1452
- [MsgType.ResilientOk]: (s, m) => succeed(s, m, m.at),
1453
- [MsgType.ResilientErr]: (s, m) => fail(s, m, m.at),
1718
+ [MsgType.ResilientOk]: (s, m) => door.succeed(s, m, m.at),
1719
+ [MsgType.ResilientErr]: (s, m) => door.fail(s, m, m.at),
1454
1720
  // The re-entered compaction settle Msgs (from the consumer's `compact_run`
1455
1721
  // cell, #85): `compact_ok` folds the summary back (drop oldest turns +
1456
1722
  // their records, fire the next brain call), `compact_err` backs off via the
1457
1723
  // compaction retry or proceeds without compacting.
1458
- [MsgType.CompactOk]: (s, m) => compactOk(s, m.key, m, m.at),
1459
- [MsgType.CompactErr]: (s, m) => compactErr(s, m.key, m, m.at),
1460
- deadline_exceeded: (s, m) => onTimer(s, m),
1461
- [MsgType.AgentBoot]: (s, m) => boot(s, m.at),
1462
- [MsgType.AgentCancel]: (s, m) => cancel(s, m.at)
1724
+ [MsgType.CompactOk]: (s, m) => door.compactOk(s, m.key, m, m.at),
1725
+ [MsgType.CompactErr]: (s, m) => door.compactErr(s, m.key, m, m.at),
1726
+ deadline_exceeded: (s, m) => door.onTimer(s, m),
1727
+ [MsgType.AgentBoot]: (s, m) => door.boot(s, m.at),
1728
+ [MsgType.AgentCancel]: (s, m) => transition(s, m.at, (entered) => cancel(entered, m.at))
1463
1729
  };
1464
1730
  const update = { ...ownUpdate, ...toolCells };
1465
1731
  const machine = {
@@ -1477,18 +1743,27 @@ function createAgent(config) {
1477
1743
  subscribe: { deadline: subscribeDeadline }
1478
1744
  };
1479
1745
  }
1746
+ function transition(s, at, verb) {
1747
+ const current = withUsageDefaults(s);
1748
+ const entered = (current.lifecycle ?? []).length === 0 ? current : { ...current, lifecycle: [] };
1749
+ const [next, cmds] = verb(entered);
1750
+ return !isSettled(entered) && isSettled(next) ? [noted(next, { kind: "run_ended", at }), cmds] : [next, cmds];
1751
+ }
1752
+ const door = {
1753
+ start: (s, runId, at) => transition(s, at, (e) => start(e, runId, at)),
1754
+ turn: (s, result, at) => transition(s, at, (e) => turn(e, result, at)),
1755
+ toolOk: (s, callId, result, at) => transition(s, at, (e) => toolOk(e, callId, result, at)),
1756
+ toolErr: (s, callId, failure, at) => transition(s, at, (e) => toolErr(e, callId, failure, at)),
1757
+ succeed: (s, msg, at) => transition(s, at, (e) => succeed(e, msg, at)),
1758
+ fail: (s, msg, at) => transition(s, at, (e) => fail(e, msg, at)),
1759
+ compactOk: (s, key, msg, at) => transition(s, at, (e) => compactOk(e, key, msg, at)),
1760
+ compactErr: (s, key, msg, at) => transition(s, at, (e) => compactErr(e, key, msg, at)),
1761
+ onTimer: (s, msg) => transition(s, msg.atMs, (e) => onTimer(e, msg)),
1762
+ boot: (s, at) => transition(s, at, (e) => boot(e, at))
1763
+ };
1480
1764
  return {
1481
1765
  init,
1482
- start,
1483
- turn,
1484
- toolOk,
1485
- toolErr,
1486
- succeed,
1487
- fail,
1488
- compactOk,
1489
- compactErr,
1490
- onTimer,
1491
- boot,
1766
+ ...door,
1492
1767
  isSettled,
1493
1768
  currentStage,
1494
1769
  brainCall,
@@ -1501,6 +1776,6 @@ function createAgent(config) {
1501
1776
  };
1502
1777
  }
1503
1778
 
1504
- export { COMPACTION_PURPOSE, PLAIN_MODEL_MISROUTE_REASON, TOOL_RETRY_EXHAUSTED_TAG, TOOL_TIMEOUT_TAG, agentBootMsg, agentCancelMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, defineAgent, fanOutInterpret, isAgentTurn, isCompactionSummary, isReservedToolName, isStreamingModel, liftAgent, mergeInterpret, plainModel, renderPrompt, status, tool, toolErrorReason, toolRouter, transcript };
1505
- //# sourceMappingURL=chunk-XBZQT2TD.js.map
1506
- //# sourceMappingURL=chunk-XBZQT2TD.js.map
1779
+ export { AGENT_EVENT_TYPES, COMPACTION_PURPOSE, FILE_OMITTED, IMAGE_OMITTED, PLAIN_MODEL_MISROUTE_REASON, TOOL_RETRY_EXHAUSTED_TAG, TOOL_TIMEOUT_TAG, agentBootMsg, agentCancelMsg, agentEvents, agentTool, agentTurnSchema, compactionSummarySchema, contentParts, createAgent, defineAgent, fanOutInterpret, isAgentTurn, isCompactionSummary, isReservedToolName, isStreamingModel, isTurnUsage, liftAgent, mergeInterpret, omitMedia, plainModel, renderPrompt, status, tool, toolErrorReason, toolRouter, transcript };
1780
+ //# sourceMappingURL=chunk-PCJA2HTZ.js.map
1781
+ //# sourceMappingURL=chunk-PCJA2HTZ.js.map