@statelyai/agent 2.0.0-alpha.18 → 2.0.0-alpha.20

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 (38) hide show
  1. package/dist/ai-sdk.cjs +4 -2
  2. package/dist/ai-sdk.d.cts +1 -1
  3. package/dist/ai-sdk.d.mts +1 -1
  4. package/dist/ai-sdk.mjs +3 -2
  5. package/dist/{decision-C11xuud2.mjs → decision-BfhSgCc6.mjs} +48 -28
  6. package/dist/{decision-DnQCQPew.cjs → decision-DhsKLYAI.cjs} +64 -43
  7. package/dist/{event-log-store-CQJq8_v4.d.cts → event-log-store-Bz7HDBkE.d.cts} +11 -12
  8. package/dist/{event-log-store-B-1fcfkT.mjs → event-log-store-DmIDosD6.mjs} +22 -14
  9. package/dist/{event-log-store-yquOV1TX.cjs → event-log-store-a_TKy1gk.cjs} +22 -14
  10. package/dist/{event-log-store-BrC9Q1xW.d.mts → event-log-store-hrA1vqtN.d.mts} +11 -12
  11. package/dist/index.cjs +458 -109
  12. package/dist/index.d.cts +209 -81
  13. package/dist/index.d.mts +209 -81
  14. package/dist/index.mjs +455 -108
  15. package/dist/machines.cjs +13 -17
  16. package/dist/machines.d.cts +14 -17
  17. package/dist/machines.d.mts +14 -17
  18. package/dist/machines.mjs +13 -17
  19. package/dist/otel.cjs +1 -0
  20. package/dist/otel.d.cts +1 -1
  21. package/dist/otel.d.mts +1 -1
  22. package/dist/{run-agent-r9OD4z8F.d.cts → run-agent--4bbms-D.d.cts} +121 -48
  23. package/dist/{run-agent-DQIDikfd.d.mts → run-agent-CwmzAZwj.d.mts} +121 -48
  24. package/dist/{setup-agent-BrE2zFDy.mjs → setup-agent-BOcSpsIq.mjs} +38 -28
  25. package/dist/{setup-agent-C3ETi_HZ.cjs → setup-agent-gISRLxRe.cjs} +44 -33
  26. package/dist/sqlite.cjs +3 -9
  27. package/dist/sqlite.d.cts +1 -1
  28. package/dist/sqlite.d.mts +1 -1
  29. package/dist/sqlite.mjs +3 -9
  30. package/dist/{text-logic-VcWqO-Cl.d.cts → text-logic-Cavva1W6.d.cts} +24 -8
  31. package/dist/{text-logic-RvnlD-An.d.mts → text-logic-Er5KkTX6.d.mts} +24 -8
  32. package/dist/validate.cjs +436 -0
  33. package/dist/validate.d.cts +31 -0
  34. package/dist/validate.d.mts +31 -0
  35. package/dist/validate.mjs +411 -0
  36. package/package.json +17 -3
  37. package/schemas/agent-workflow.json +2 -2
  38. package/skills/generate-machine/SKILL.md +12 -14
package/dist/index.cjs CHANGED
@@ -1,9 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("./errors-DUBBzRLP.cjs");
3
- const require_setup_agent = require("./setup-agent-C3ETi_HZ.cjs");
4
- const require_decision = require("./decision-DnQCQPew.cjs");
5
- const require_event_log_store = require("./event-log-store-yquOV1TX.cjs");
3
+ const require_setup_agent = require("./setup-agent-gISRLxRe.cjs");
4
+ const require_decision = require("./decision-DhsKLYAI.cjs");
5
+ const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
6
+ require("./validate.cjs");
6
7
  let xstate = require("xstate");
8
+ let xstate_durable = require("xstate/durable");
7
9
  //#region src/internal/state-request-pass.ts
8
10
  async function runTextPhase(stateRequest, baseMessages, deps) {
9
11
  const { model, system } = stateRequest;
@@ -103,7 +105,7 @@ async function runAdvancePhase(plan, deps) {
103
105
  messages: [...deps.messages],
104
106
  events,
105
107
  attempts: []
106
- }, deps.decide, {
108
+ }, { decide: deps.decide }, {
107
109
  signal: deps.signal,
108
110
  canTake: (event) => deps.getSnapshot().can(event)
109
111
  });
@@ -139,8 +141,7 @@ async function runStateRequestPass(requests, deps) {
139
141
  * {@link getAcceptedEvents}). A programmer/integration error, in the same
140
142
  * class as runAgent's bind-time throws — it throws rather than settling an
141
143
  * `error` result. A type-legal event a guard rejects is NOT this error (the
142
- * machine simply takes no transition). Opt out with
143
- * {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
144
+ * machine simply takes no transition). Always enforced; there is no opt-out.
144
145
  */
145
146
  var AgentIllegalResumeEventError = class extends require_errors.AgentError {
146
147
  eventType;
@@ -258,7 +259,7 @@ function toJsonValue(value, ancestors) {
258
259
  * in a JSONL file). Live values are sanitized rather than trusted:
259
260
  *
260
261
  * - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
261
- * same JSON round-trip as {@link persistSnapshot}, so what lands on disk is
262
+ * same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
262
263
  * what a resume would see.
263
264
  * - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
264
265
  * unless `includeRaw` is set, in which case it is sanitized like everything
@@ -285,11 +286,92 @@ function serializeTraceEvent(event, options = {}) {
285
286
  }
286
287
  return out;
287
288
  }
289
+ function snapshotNodes(snapshot) {
290
+ return (snapshot.nodes ?? []).map((raw) => {
291
+ const node = raw;
292
+ return {
293
+ id: node.id ?? "",
294
+ key: node.key ?? "",
295
+ ...node.description !== void 0 ? { description: node.description } : {},
296
+ tags: [...node.tags ?? []],
297
+ ...node.meta !== void 0 ? { meta: node.meta } : {},
298
+ ownEvents: [...node.ownEvents ?? []],
299
+ leaf: Object.keys(node.states ?? {}).length === 0
300
+ };
301
+ });
302
+ }
303
+ /**
304
+ * The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
305
+ * descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
306
+ * your `getRequests` hook needs to build requests some other way, so host code
307
+ * never touches xstate's private `snapshot._nodes`.
308
+ */
309
+ function getSnapshotNodes(snapshot) {
310
+ return snapshotNodes(snapshot);
311
+ }
312
+ /**
313
+ * Builds the {@link AgentStateRequest}s for a snapshot straight from its active
314
+ * state nodes — the prompts-in-descriptions recipe as a function, so a
315
+ * {@link RunAgentOptions.getRequests} hook is one line and never reaches into
316
+ * xstate's private `snapshot._nodes`:
317
+ *
318
+ * ```ts
319
+ * runAgent(machine, {
320
+ * executors,
321
+ * getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
322
+ * });
323
+ * ```
324
+ *
325
+ * Each described active node becomes one request: `prompt` from the node's
326
+ * `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
327
+ * from `meta.role` when present, `allowedEvents` scoped to the node's own
328
+ * events, and an explicit `onDone` when the node has exactly one own event
329
+ * (single-outcome states advance deterministically; anything else falls
330
+ * through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
331
+ * run settles idle for a human. Override any of it with `filter`/`map`.
332
+ */
333
+ function getSnapshotRequests(snapshot, options) {
334
+ const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
335
+ const requests = [];
336
+ for (const node of snapshotNodes(snapshot)) {
337
+ if (!filter(node)) continue;
338
+ const system = node.meta?.role;
339
+ const request = {
340
+ model: options.model,
341
+ prompt: node.description ?? "",
342
+ kind: node.tags.includes("decision") ? "decision" : "text",
343
+ ...system !== void 0 ? { system } : {},
344
+ ...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
345
+ ...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
346
+ };
347
+ const mapped = options.map ? options.map(request, node) : request;
348
+ if (mapped) requests.push(mapped);
349
+ }
350
+ return requests;
351
+ }
288
352
  let nextRunAgentTraceId = 1;
353
+ /**
354
+ * Thrown into the invoke that would have made the call once
355
+ * {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
356
+ * through the normal error channel, so an invoke's `onError` can branch on it
357
+ * (`error.code === 'max-model-calls'`, the same string the settled result's
358
+ * `cause` uses) and route to a degraded/finish state instead of failing the
359
+ * run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
360
+ *
361
+ * ```ts
362
+ * onError: [
363
+ * { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
364
+ * { target: 'failed' },
365
+ * ]
366
+ * ```
367
+ */
289
368
  var AgentMaxModelCallsExceededError = class extends require_errors.AgentError {
290
- constructor() {
291
- super("max-model-calls-exceeded", "runAgent exceeded maxModelCalls.");
369
+ /** The budget that was exceeded (`options.maxModelCalls`). */
370
+ maxModelCalls;
371
+ constructor(maxModelCalls) {
372
+ super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
292
373
  this.name = "AgentMaxModelCallsExceededError";
374
+ this.maxModelCalls = maxModelCalls;
293
375
  }
294
376
  };
295
377
  function wrapsDecisionExhausted(error) {
@@ -329,7 +411,7 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
329
411
  * walk into invoked child machines (their internal agent requests are opaque
330
412
  * to the parent-level source walk otherwise).
331
413
  */
332
- function isStateMachine(logic) {
414
+ function isStateMachineLogic(logic) {
333
415
  return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
334
416
  }
335
417
  /**
@@ -361,7 +443,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
361
443
  const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
362
444
  for (const { stateName, src } of invokes) {
363
445
  if (typeof src !== "string") {
364
- if (isStateMachine(src)) {
446
+ if (isStateMachineLogic(src)) {
365
447
  assertChildMachineBindable(src, src, stateName, executors, ctx);
366
448
  continue;
367
449
  }
@@ -370,7 +452,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
370
452
  }
371
453
  const logic = effectiveSources[src];
372
454
  if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
373
- if (isStateMachine(logic)) {
455
+ if (isStateMachineLogic(logic)) {
374
456
  assertChildMachineBindable(logic, src, stateName, executors, ctx);
375
457
  continue;
376
458
  }
@@ -416,18 +498,16 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
416
498
  return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
417
499
  }
418
500
  /**
419
- * True when the snapshot's active states declare a transition for the reserved
420
- * `'@agent.usage'` type EXPLICITLY. A catch-all `on: { '*': … }` deliberately
421
- * does not count: a wildcard is a machine's own event vocabulary, not an
422
- * opt-in to a library-reserved event, and `snapshot.can(event)` alone cannot
423
- * tell the two apart (it answers "would this event be taken?", which a
424
- * wildcard makes true for everything). Gating delivery on the explicit
425
- * declaration is what keeps `@agent.usage` opt-in by construction — and keeps
426
- * a wildcard machine's context and event log byte-identical to a run without
427
- * the feature. @internal
501
+ * True when the snapshot's active states declare a transition that would
502
+ * receive the reserved `'@agent.usage'` type an explicit `on: { '@agent.usage'
503
+ * }` OR a catch-all `on: { '*': }`. Plain XState semantics apply unmodified:
504
+ * a wildcard matches every event delivered to the machine, reserved ones
505
+ * included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
506
+ * `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
507
+ * the reserved event as a decision candidate.) @internal
428
508
  */
429
509
  function declaresUsageTransition(snapshot) {
430
- return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === require_setup_agent.AGENT_USAGE_EVENT_TYPE);
510
+ return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
431
511
  }
432
512
  /**
433
513
  * Builds the ONE place a trace payload is emitted: it hands the payload to the
@@ -494,7 +574,7 @@ function bindTextLogic(logic, runCtx) {
494
574
  const self = selfArg;
495
575
  const { id, src } = selfIdAndSrc(self);
496
576
  const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
497
- if (!executor) throw new Error(`runAgent: no '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
577
+ if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
498
578
  const requestWithTools = {
499
579
  ...request,
500
580
  tools: request.tools ?? {}
@@ -566,17 +646,22 @@ function bindTextLogic(logic, runCtx) {
566
646
  });
567
647
  }
568
648
  function createCountingDecide(runCtx, self) {
569
- return async (attemptRequest) => {
649
+ return async (attemptRequest, info) => {
570
650
  runCtx.consumeModelCall();
571
651
  runCtx.emitTrace?.({
572
652
  type: "request.start",
573
653
  request: attemptRequest
574
654
  }, self);
575
655
  try {
656
+ const { id } = selfIdAndSrc(self);
576
657
  const result = await runCtx.decide(runCtx.runId !== void 0 ? {
577
658
  ...attemptRequest,
578
659
  runId: runCtx.runId
579
- } : attemptRequest);
660
+ } : attemptRequest, {
661
+ ...info,
662
+ ...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
663
+ ...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
664
+ });
580
665
  const usage = require_decision.getCallUsage(result);
581
666
  if (usage) {
582
667
  const { src } = selfIdAndSrc(self);
@@ -622,7 +707,7 @@ function createCountingDecide(runCtx, self) {
622
707
  */
623
708
  function bindDecisionLogic(logic, runCtx) {
624
709
  const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
625
- if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
710
+ if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
626
711
  const self = selfArg;
627
712
  const { id } = selfIdAndSrc(self);
628
713
  const declaredEventTypes = logic.allowedEventTypes?.(input);
@@ -636,7 +721,7 @@ function bindDecisionLogic(logic, runCtx) {
636
721
  ...logic.request(input),
637
722
  id,
638
723
  events
639
- }, createCountingDecide(runCtx, self), {
724
+ }, { decide: createCountingDecide(runCtx, self) }, {
640
725
  maxRetries: logic.maxRetries,
641
726
  signal,
642
727
  canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
@@ -681,7 +766,7 @@ function rootTraceState(root) {
681
766
  if (!state) {
682
767
  const logic = root.logic;
683
768
  const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
684
- const machineVersion = logic ? require_decision.getMachineStructuralHash(logic) : "";
769
+ const machineVersion = logic ? require_decision.resolveMachineVersion(logic) : "";
685
770
  state = {
686
771
  runId: `run_${nextProvideRunId++}`,
687
772
  seq: 0,
@@ -753,9 +838,9 @@ function provideBindContext(machine, executors, options) {
753
838
  * under a live `createActor` tree) on the `provideExecutors` path.
754
839
  *
755
840
  * Gating is identical on both: the target snapshot must be active, must declare
756
- * an `'@agent.usage'` transition EXPLICITLY (see {@link declaresUsageTransition}
757
- * — a catch-all `on: { '*' }` is not an opt-in), and must be able to take the
758
- * event. `onDropped` is the run path's straggler gate: it returns `true` for a
841
+ * an `'@agent.usage'` transition explicitly, or through a catch-all
842
+ * `on: { '*' }` (see {@link declaresUsageTransition}) and must be able to
843
+ * take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
759
844
  * call that settled after the cycle resolved, which drops the event (traced as
760
845
  * `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
761
846
  * settle, so it passes no gate and has no dropped stragglers.
@@ -797,6 +882,19 @@ function bindDecisionForProvide(machine, logic, executors, options) {
797
882
  return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
798
883
  }
799
884
  /**
885
+ * Recursively binds an invoked child state machine for {@link provideExecutors},
886
+ * with the same semantics `runAgent` applies ({@link rebindChildMachine}):
887
+ * string-keyed text/decision sources at any depth inherit the host executors,
888
+ * a source that carries its own executor is left alone, and a cycle is
889
+ * returned as-is. Each machine in the tree is bound with its own registered
890
+ * `setupAgent` schemas. Returns the original machine when nothing needed
891
+ * wrapping. @internal
892
+ */
893
+ function bindChildMachineForProvide(childMachine, executors, options, visited) {
894
+ const ctxFor = (target) => provideBindContext(target, executors, options);
895
+ return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
896
+ }
897
+ /**
800
898
  * Validates `input` against the machine's registered input schema, returning
801
899
  * the schema's output — so defaults are filled and transforms applied before
802
900
  * the value reaches `createActor` or the replayable event log.
@@ -830,9 +928,10 @@ function resolveMachineInput(machine, input) {
830
928
  * (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
831
929
  * Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
832
930
  */
833
- function rebindChildMachine(childMachine, runCtx, visited) {
931
+ function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
834
932
  if (visited.has(childMachine)) return childMachine;
835
933
  const childVisited = new Set([...visited, childMachine]);
934
+ runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
836
935
  const sources = childMachine.sources.actors;
837
936
  const wrapped = {};
838
937
  for (const [key, logic] of Object.entries(sources)) {
@@ -844,8 +943,8 @@ function rebindChildMachine(childMachine, runCtx, visited) {
844
943
  if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
845
944
  continue;
846
945
  }
847
- if (isStateMachine(logic)) {
848
- const rebound = rebindChildMachine(logic, runCtx, childVisited);
946
+ if (isStateMachineLogic(logic)) {
947
+ const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
849
948
  if (rebound !== logic) wrapped[key] = rebound;
850
949
  continue;
851
950
  }
@@ -928,7 +1027,7 @@ function createAgentSession(machine, options, lifecycle) {
928
1027
  let traceSeq = 0;
929
1028
  const resolvedInput = resolveMachineInput(machine, options.input);
930
1029
  const machineId = machine.config.id ?? machine.id ?? "(machine)";
931
- const machineVersion = options.machineVersion ?? machine.version ?? require_decision.getMachineStructuralHash(machine);
1030
+ const machineVersion = require_decision.resolveMachineVersion(machine);
932
1031
  const agentMeta = {
933
1032
  machineId,
934
1033
  version: machineVersion
@@ -950,10 +1049,10 @@ function createAgentSession(machine, options, lifecycle) {
950
1049
  onTransition: options.onTransition
951
1050
  });
952
1051
  const consumeModelCall = () => {
953
- if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
1052
+ if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
954
1053
  if (modelCallCount + 1 > maxModelCalls) {
955
1054
  budgetExceeded = true;
956
- throw new AgentMaxModelCallsExceededError();
1055
+ throw new AgentMaxModelCallsExceededError(maxModelCalls);
957
1056
  }
958
1057
  modelCallCount += 1;
959
1058
  };
@@ -1027,19 +1126,20 @@ function createAgentSession(machine, options, lifecycle) {
1027
1126
  if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
1028
1127
  continue;
1029
1128
  }
1030
- if (isStateMachine(logic)) {
1129
+ if (isStateMachineLogic(logic)) {
1031
1130
  const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
1032
1131
  if (rebound !== logic) wrappedSources[key] = rebound;
1033
1132
  continue;
1034
1133
  }
1035
1134
  }
1036
1135
  const boundMachine = provided.provide({ actors: wrappedSources });
1037
- const declaredSuspensionPredicate = options.isSuspended ?? require_decision.getMachineSuspensionPredicate(machine);
1038
- const isSuspended = declaredSuspensionPredicate ?? (() => false);
1136
+ const declaredIdlePredicate = options.isIdle ?? require_decision.getMachineIdlePredicate(machine);
1137
+ const isIdle = declaredIdlePredicate ?? (() => false);
1138
+ const machineDeclaresMigrate = typeof machine.config.migrate === "function";
1039
1139
  let effectiveSnapshot = options.snapshot;
1040
1140
  if (effectiveSnapshot !== void 0) {
1041
1141
  const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
1042
- if (from !== void 0 && from !== machineVersion) {
1142
+ if (from !== void 0 && from !== machineVersion && !(machineDeclaresMigrate && !options.migrateSnapshot)) {
1043
1143
  const info = {
1044
1144
  from,
1045
1145
  to: machineVersion
@@ -1058,10 +1158,11 @@ function createAgentSession(machine, options, lifecycle) {
1058
1158
  effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
1059
1159
  }
1060
1160
  const machineOwnVersion = machine.version;
1061
- if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
1161
+ if (!machineDeclaresMigrate && effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
1062
1162
  const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
1063
1163
  if (machineOwnVersion === void 0) delete aligned.version;
1064
1164
  else aligned.version = machineOwnVersion;
1165
+ delete aligned.machine;
1065
1166
  effectiveSnapshot = aligned;
1066
1167
  }
1067
1168
  const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
@@ -1072,7 +1173,7 @@ function createAgentSession(machine, options, lifecycle) {
1072
1173
  if (!options.getRequests && !options.messages && messages.length === 0) return;
1073
1174
  snapshot.messages = [...messages];
1074
1175
  };
1075
- if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
1176
+ if (effectiveSnapshot !== void 0 && options.event !== void 0) {
1076
1177
  const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
1077
1178
  const eventType = options.event.type;
1078
1179
  if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
@@ -1218,9 +1319,9 @@ function createAgentSession(machine, options, lifecycle) {
1218
1319
  const current = actor.getSnapshot();
1219
1320
  if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1220
1321
  if (!maybeInterpret(current)) {
1221
- if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1322
+ if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1222
1323
  warnedHeuristicIdle = true;
1223
- console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no suspension predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isSuspended }) or pass runAgent(machine, { isSuspended }), e.g. (s) => s.hasTag('waiting').");
1324
+ console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no idle predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isIdle }) or pass runAgent(machine, { isIdle }), e.g. (s) => s.hasTag('waiting').");
1224
1325
  }
1225
1326
  settleIdle(current);
1226
1327
  }
@@ -1275,11 +1376,11 @@ function createAgentSession(machine, options, lifecycle) {
1275
1376
  });
1276
1377
  return;
1277
1378
  }
1278
- if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1379
+ if (!deliveringResumeEvent && isIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1279
1380
  queueMicrotask(() => {
1280
1381
  if (settled) return;
1281
1382
  const current = actor.getSnapshot();
1282
- if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1383
+ if (isIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1283
1384
  if (!maybeInterpret(current)) settleIdle(current);
1284
1385
  } else scheduleIdleCheck();
1285
1386
  });
@@ -1576,9 +1677,12 @@ function createAgentRun(machine, options) {
1576
1677
  * Throws at bind time if a source needs an executor kind that `executors` does
1577
1678
  * not provide.
1578
1679
  *
1579
- * `provideExecutors` does not descend into invoked child state machines: a string-keyed child
1580
- * machine source is left untouched, so a child with its own agent invokes needs
1581
- * its own `provideExecutors(...)` (or `runAgent`, which does rebind children).
1680
+ * Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
1681
+ * invoked child machine is rebound too, so its own text/decision requests — at
1682
+ * any depth reach the same host executors. A direct-object invoke `src`
1683
+ * cannot be swapped via `.provide`, so nothing under one inherits; bind those
1684
+ * with `.withExecutor(...)` or register the child as a string-keyed source. A
1685
+ * source that already carries its own executor is never overwritten.
1582
1686
  */
1583
1687
  function provideExecutors(machine, executors, options = {}) {
1584
1688
  const bindOptions = {
@@ -1592,21 +1696,18 @@ function provideExecutors(machine, executors, options = {}) {
1592
1696
  const invokedSrcs = getConfiguredInvokeSrcs(provided);
1593
1697
  for (const [key, logic] of Object.entries(effectiveSources)) {
1594
1698
  if (key === "agent.userInput") continue;
1595
- let binding;
1596
- if (require_decision.isDecisionLogic(logic)) binding = {
1597
- executorKey: "decide",
1598
- kind: "decision",
1599
- bind: () => bindDecisionForProvide(provided, logic, executors, bindOptions)
1600
- };
1601
- else if (require_decision.isTextLogic(logic)) {
1602
- const streaming = logic.mode === "stream";
1603
- binding = {
1604
- executorKey: streaming ? "streamText" : "generateText",
1605
- kind: streaming ? "streaming text" : "text",
1606
- bind: () => bindTextForProvide(provided, logic, executors, bindOptions)
1607
- };
1699
+ if (isStateMachineLogic(logic)) {
1700
+ if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
1701
+ const rebound = bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
1702
+ if (rebound !== logic) wrappedSources[key] = rebound;
1703
+ continue;
1608
1704
  }
1609
- if (!binding) continue;
1705
+ const requirement = executorRequirementOf(logic);
1706
+ if (!requirement) continue;
1707
+ const binding = {
1708
+ ...requirement,
1709
+ bind: () => require_decision.isDecisionLogic(logic) ? bindDecisionForProvide(provided, logic, executors, bindOptions) : bindTextForProvide(provided, logic, executors, bindOptions)
1710
+ };
1610
1711
  if (require_decision.executorBoundLogics.has(logic)) continue;
1611
1712
  if (!executors[binding.executorKey]) {
1612
1713
  if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
@@ -1616,12 +1717,50 @@ function provideExecutors(machine, executors, options = {}) {
1616
1717
  }
1617
1718
  return withActors(provided, wrappedSources);
1618
1719
  }
1720
+ /** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
1721
+ function executorRequirementOf(logic) {
1722
+ if (require_decision.isDecisionLogic(logic)) return {
1723
+ executorKey: "decide",
1724
+ kind: "decision"
1725
+ };
1726
+ if (require_decision.isTextLogic(logic)) return logic.mode === "stream" ? {
1727
+ executorKey: "streamText",
1728
+ kind: "streaming text"
1729
+ } : {
1730
+ executorKey: "generateText",
1731
+ kind: "text"
1732
+ };
1733
+ }
1734
+ /**
1735
+ * Walks an invoked child machine's own invoked sources (recursively, at any
1736
+ * depth) and throws the same missing-executor error `provideExecutors` throws
1737
+ * for the top-level machine — before any actor starts. Mirrors runAgent's
1738
+ * `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
1739
+ */
1740
+ function assertChildBindable(childMachine, executors, path, visited) {
1741
+ if (visited.has(childMachine)) return;
1742
+ const nextVisited = new Set([...visited, childMachine]);
1743
+ const sources = childMachine.sources.actors;
1744
+ for (const src of getConfiguredInvokeSrcs(childMachine)) {
1745
+ if (src === "agent.userInput") continue;
1746
+ const logic = sources[src];
1747
+ if (!logic) continue;
1748
+ if (isStateMachineLogic(logic)) {
1749
+ assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
1750
+ continue;
1751
+ }
1752
+ const requirement = executorRequirementOf(logic);
1753
+ if (!requirement || require_decision.executorBoundLogics.has(logic)) continue;
1754
+ if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
1755
+ }
1756
+ }
1619
1757
  function missingExecutorError(src, kind, executor) {
1620
1758
  return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
1621
1759
  }
1622
1760
  //#endregion
1623
1761
  //#region src/verify.ts
1624
1762
  const DECIDE_SRC = "agent.decide";
1763
+ const USER_INPUT_SRC = "agent.userInput";
1625
1764
  function normalizeInvokes(invoke) {
1626
1765
  if (invoke === void 0 || invoke === null) return [];
1627
1766
  return Array.isArray(invoke) ? invoke : [invoke];
@@ -1908,11 +2047,23 @@ const LINT_CHECKS = [
1908
2047
  * call this to catch dead states, undeliverable decisions, un-rebindable
1909
2048
  * invoke srcs, and output-contract gaps before ever running it.
1910
2049
  *
2050
+ * Pass `{ throw: true }` for the one-liner form used in tests and generation
2051
+ * loops: it returns silently when the machine is clean and throws
2052
+ * {@link AgentLintError} (findings on `.diagnostics`) on error-severity
2053
+ * findings, or on warnings too with `{ throw: true, warnings: true }`.
2054
+ *
1911
2055
  * @example
1912
2056
  * ```ts
1913
2057
  * const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
1914
2058
  * if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
1915
2059
  * ```
2060
+ *
2061
+ * @example Throwing form
2062
+ * ```ts
2063
+ * test('agent machine is structurally sound', () => {
2064
+ * lintAgentMachine(machine, { throw: true });
2065
+ * });
2066
+ * ```
1916
2067
  */
1917
2068
  function lintAgentMachine(machine, options = {}) {
1918
2069
  const config = machine.config ?? {};
@@ -1927,10 +2078,16 @@ function lintAgentMachine(machine, options = {}) {
1927
2078
  actors: registered.actors ?? machine.sources?.actors ?? {}
1928
2079
  };
1929
2080
  const disabled = new Set(options.disable ?? []);
1930
- return LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
2081
+ const diagnostics = LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
2082
+ if (options.throw) {
2083
+ const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
2084
+ if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
2085
+ }
2086
+ return diagnostics;
1931
2087
  }
1932
2088
  /**
1933
- * Thrown by {@link assertAgentMachine} when lint finds failing diagnostics.
2089
+ * Thrown by `lintAgentMachine(machine, { throw: true })` when lint finds
2090
+ * failing diagnostics.
1934
2091
  * `diagnostics` holds the findings; the message lists them one per finding,
1935
2092
  * so a test runner's failure output reads like the CLI's lint report.
1936
2093
  */
@@ -1943,24 +2100,6 @@ var AgentLintError = class extends require_errors.AgentError {
1943
2100
  this.diagnostics = diagnostics;
1944
2101
  }
1945
2102
  };
1946
- /**
1947
- * Asserts a machine passes {@link lintAgentMachine}: returns silently when
1948
- * clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
1949
- * otherwise. Fails on error-severity findings; set `warnings: true` to fail on
1950
- * warnings too. The one-liner for tests and generation loops:
1951
- *
1952
- * @example
1953
- * ```ts
1954
- * test('agent machine is structurally sound', () => {
1955
- * assertAgentMachine(machine);
1956
- * });
1957
- * ```
1958
- */
1959
- function assertAgentMachine(machine, options = {}) {
1960
- const diagnostics = lintAgentMachine(machine, options);
1961
- const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
1962
- if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
1963
- }
1964
2103
  function pendingInvokes(step) {
1965
2104
  const out = [];
1966
2105
  for (const action of step.actions) {
@@ -2006,6 +2145,7 @@ async function simulateAgent(machine, options) {
2006
2145
  decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
2007
2146
  invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
2008
2147
  };
2148
+ if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
2009
2149
  let step = require_setup_agent.initialAgentStep(machine, options.input);
2010
2150
  const trail = [];
2011
2151
  for (let i = 0; i < maxSteps; i++) {
@@ -2072,13 +2212,16 @@ function mapValues(obj, fn) {
2072
2212
  }
2073
2213
  function scriptDryError(kind, src, id, request) {
2074
2214
  const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
2075
- return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add a '${kind}' entry for '${src}' to the script.${events}`);
2215
+ const key = kind === "text" ? `text['${src}']` : kind === "decision" ? `decisions['${src}']` : src === USER_INPUT_SRC ? "userInput" : `invokes['${src}']`;
2216
+ return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add an entry to the script's \`${key}\` queue.${events}`);
2076
2217
  }
2077
2218
  const MAX_ADVANCE_STEPS = 1e3;
2078
2219
  async function explore(machine, options, stopWhen) {
2079
2220
  const maxDepth = options.maxDepth ?? 8;
2080
2221
  const maxPaths = options.maxPaths ?? 200;
2081
- const textOutputs = options.textOutputs ?? {};
2222
+ const textScript = options.text ?? {};
2223
+ const invokeOutputs = { ...options.invokes ?? {} };
2224
+ if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
2082
2225
  const reachedStates = /* @__PURE__ */ new Set();
2083
2226
  const reachedValues = [];
2084
2227
  const terminals = [];
@@ -2103,22 +2246,22 @@ async function explore(machine, options, stopWhen) {
2103
2246
  if (current.done) return { step: current };
2104
2247
  const request = current.requests[0];
2105
2248
  if (request && request.kind === "text") {
2106
- if (!(request.src in textOutputs)) return {
2249
+ if (!(request.src in textScript)) return {
2107
2250
  step: current,
2108
2251
  blockedSrc: request.src
2109
2252
  };
2110
- current = require_setup_agent.resolveAgentStep(machine, current, request, textOutputs[request.src]);
2253
+ current = require_setup_agent.resolveAgentStep(machine, current, request, textScript[request.src]);
2111
2254
  recordState(current.snapshot);
2112
2255
  continue;
2113
2256
  }
2114
2257
  if (request && request.kind === "decision") return { step: current };
2115
2258
  const [invoke] = pendingInvokes(current);
2116
2259
  if (invoke) {
2117
- if (!(invoke.src in textOutputs)) return {
2260
+ if (!(invoke.src in invokeOutputs)) return {
2118
2261
  step: current,
2119
2262
  blockedSrc: invoke.src
2120
2263
  };
2121
- current = require_setup_agent.resolveAgentStep(machine, current, invoke.id, textOutputs[invoke.src]);
2264
+ current = require_setup_agent.resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
2122
2265
  recordState(current.snapshot);
2123
2266
  continue;
2124
2267
  }
@@ -2210,9 +2353,10 @@ async function explore(machine, options, stopWhen) {
2210
2353
  * depth, model-free, and reports which states are reached and how each path
2211
2354
  * terminates. At each decision request it forks one branch per candidate event
2212
2355
  * (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
2213
- * an idle wait it forks per externally-accepted event. Text/`userInput` invokes
2214
- * are resolved from `textOutputs` (a by-src canned-output map) — a missing src
2215
- * halts that branch with a `needs-output` terminal rather than throwing.
2356
+ * an idle wait it forks per externally-accepted event. Text requests resolve
2357
+ * from `text`, other invokes from `invokes` (or `userInput` for
2358
+ * `agent.userInput`) all by-src canned-output maps, and a missing src halts
2359
+ * that branch with a `needs-output` terminal rather than throwing.
2216
2360
  *
2217
2361
  * Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
2218
2362
  * 200, reported via `hitPathCap`).
@@ -2228,13 +2372,14 @@ async function explorePaths(machine, options = {}) {
2228
2372
  }
2229
2373
  /**
2230
2374
  * Answers "can the machine reach `statePath`?" by exploring its branches (a
2231
- * thin wrapper over {@link explorePaths}). Returns `{ canReach: true, witness }`
2232
- * with the event sequence that reaches it, or `{ canReach: false }`.
2375
+ * thin wrapper over {@link explorePaths}). Returns
2376
+ * `{ reachable: true, witness }` with the event sequence that reaches it, or
2377
+ * `{ reachable: false }`.
2233
2378
  *
2234
2379
  * @example
2235
2380
  * ```ts
2236
- * const { canReach, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
2237
- * // canReach → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
2381
+ * const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
2382
+ * // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
2238
2383
  * ```
2239
2384
  */
2240
2385
  async function canReach(machine, statePath, options = {}) {
@@ -2246,9 +2391,9 @@ async function canReach(machine, statePath, options = {}) {
2246
2391
  }
2247
2392
  });
2248
2393
  return witness !== void 0 ? {
2249
- canReach: true,
2394
+ reachable: true,
2250
2395
  witness
2251
- } : { canReach: false };
2396
+ } : { reachable: false };
2252
2397
  }
2253
2398
  //#endregion
2254
2399
  //#region src/internal/is-record.ts
@@ -2409,7 +2554,8 @@ function matchesTrajectory(actual, expected, options = {}) {
2409
2554
  * Scripted executors — a keyless, deterministic stand-in for a model host.
2410
2555
  *
2411
2556
  * `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
2412
- * set that plays back canned answers from FIFO queues, so `runAgent` (or
2557
+ * set (plus a `userInput` handler) that plays back canned answers from FIFO
2558
+ * queues, so `runAgent` (or
2413
2559
  * `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
2414
2560
  * network. It is the fastest way to see a machine run, and the least ceremonial
2415
2561
  * way to test one: same machine, same executor contract, scripted answers.
@@ -2485,15 +2631,27 @@ function emitScriptedChunk(result, info) {
2485
2631
  * decisions: [(request) => ({ type: request.events[0]!.type })],
2486
2632
  * });
2487
2633
  * ```
2634
+ *
2635
+ * @example Scripted human input
2636
+ * ```ts
2637
+ * const scripted = createScriptedExecutors({ userInput: ['ship it'] });
2638
+ * await runAgent(machine, { executors: scripted, userInput: scripted.userInput });
2639
+ * ```
2488
2640
  */
2489
2641
  function createScriptedExecutors(script = {}) {
2490
2642
  const decisions = [...script.decisions ?? []];
2491
2643
  const text = [...script.text ?? []];
2644
+ const userInput = [...script.userInput ?? []];
2492
2645
  const nextText = async (request, info) => {
2493
2646
  if (text.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
2494
2647
  return resolveScriptedTextEntry(text.shift(), request, info);
2495
2648
  };
2496
2649
  return {
2650
+ userInput: async (input) => {
2651
+ if (userInput.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending userInput request (prompt: ${input.prompt ? `'${input.prompt}'` : "(none)"}). Add another entry to the script's \`userInput\` queue.`);
2652
+ const entry = userInput.shift();
2653
+ return typeof entry === "function" ? await entry(input) : entry;
2654
+ },
2497
2655
  generateText: nextText,
2498
2656
  streamText: async (request, info) => {
2499
2657
  const result = await nextText(request, info);
@@ -2567,22 +2725,22 @@ async function runSeam(machine, options) {
2567
2725
  const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
2568
2726
  /**
2569
2727
  * Consumes this request's slot in the call plan, or resolves `undefined` when
2570
- * its queue is dry. The LAST entry repeats: a live seam that branches further
2571
- * still finds an answer instead of running dry.
2728
+ * its queue is dry. With `repeatLast`, the last entry is replayed instead of
2729
+ * running dry.
2572
2730
  */
2573
2731
  const takeScriptedSlot = async (request, info) => {
2574
2732
  const queue = queues.get(queueKeyOf(request));
2575
2733
  if (!queue?.length) return;
2576
- return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
2734
+ return resolveScriptedTextEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
2577
2735
  };
2578
2736
  const scriptedAnswer = async (request, info) => {
2579
2737
  const scripted = await takeScriptedSlot(request, info);
2580
- if (!scripted) throw new require_errors.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\` its last entry repeats, so one extra answer covers a longer branch.`);
2738
+ if (!scripted) throw new require_errors.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
2581
2739
  return scripted;
2582
2740
  };
2583
2741
  const route = async (request, info) => {
2584
2742
  const callIndex = calls++;
2585
- const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
2743
+ const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
2586
2744
  if (isSeam && candidate) {
2587
2745
  await takeScriptedSlot(request, info);
2588
2746
  seamReached = true;
@@ -2620,7 +2778,7 @@ async function runSeam(machine, options) {
2620
2778
  result = await runAgent(machine, {
2621
2779
  ...snapshot ? { snapshot } : { input: options.input },
2622
2780
  ...event ? { event } : {},
2623
- ...options.isSuspended ? { isSuspended: options.isSuspended } : {},
2781
+ ...options.isIdle ? { isIdle: options.isIdle } : {},
2624
2782
  ...options.actors ? { actors: options.actors } : {},
2625
2783
  events,
2626
2784
  executors,
@@ -2673,6 +2831,196 @@ async function runSeam(machine, options) {
2673
2831
  };
2674
2832
  }
2675
2833
  //#endregion
2834
+ //#region src/durable.ts
2835
+ /**
2836
+ * The durable host runner: {@link runDurableAgent} drives an executor-bound
2837
+ * agent machine on xstate's `createDurable` execution (`xstate/durable`),
2838
+ * with the agent event log as the journal.
2839
+ *
2840
+ * Where {@link replay} + `getAgentEffects` hand a host an effect list to run
2841
+ * itself, `runDurableAgent` owns the whole loop on the durable runtime:
2842
+ * invoked actors execute live through xstate's own runtime, every EXTERNAL
2843
+ * event (invoke completions included) is appended to the log, and a resume
2844
+ * folds the log back through pure transitions — an invoke whose completion is
2845
+ * already journaled is never re-started, so recorded model calls are never
2846
+ * re-executed. Crash recovery re-runs only the work that was still in flight.
2847
+ *
2848
+ * @module
2849
+ */
2850
+ const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
2851
+ const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
2852
+ function completionActorId(event) {
2853
+ if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
2854
+ const actorId = event.actorId;
2855
+ return typeof actorId === "string" ? actorId : void 0;
2856
+ }
2857
+ function createMailbox() {
2858
+ const queue = [];
2859
+ const waiters = [];
2860
+ return {
2861
+ push(event) {
2862
+ const waiter = waiters.shift();
2863
+ if (waiter) waiter(event);
2864
+ else queue.push(event);
2865
+ },
2866
+ take() {
2867
+ const next = queue.shift();
2868
+ if (next !== void 0) return Promise.resolve(next);
2869
+ return new Promise((resolve) => waiters.push(resolve));
2870
+ },
2871
+ size: () => queue.length
2872
+ };
2873
+ }
2874
+ /**
2875
+ * Runs an agent machine as a durable execution: journal in, journal out.
2876
+ *
2877
+ * A fresh call starts from `input` and appends a reserved init entry; a
2878
+ * resume call folds `entries` through pure transitions first — invokes whose
2879
+ * completions are journaled are suppressed (their recorded results replay
2880
+ * instead of re-executing), while work that was in flight at the crash
2881
+ * re-executes live. After the journal, an optional `options.event` is
2882
+ * delivered. The call settles:
2883
+ *
2884
+ * - `done` when the machine reaches a final state, with `output`;
2885
+ * - `idle` when the frontier needs an external event the host has not
2886
+ * supplied (no live work pending, or `isIdle` says the pending work is a
2887
+ * human wait). Persist `entries` and call again with them later.
2888
+ *
2889
+ * ```ts
2890
+ * const first = await runDurableAgent(machine, { input, executors });
2891
+ * // ... persist first.entries; later, in a new process:
2892
+ * const next = await runDurableAgent(machine, {
2893
+ * entries: first.entries,
2894
+ * event: { type: "APPROVE" },
2895
+ * executors,
2896
+ * });
2897
+ * ```
2898
+ *
2899
+ * @experimental Built on xstate's experimental `xstate/durable` entrypoint.
2900
+ */
2901
+ async function runDurableAgent(machine, options = {}) {
2902
+ const bound = options.executors ? provideExecutors(machine, options.executors, {
2903
+ actors: options.actors,
2904
+ onChunk: options.onChunk,
2905
+ onTrace: options.onTrace
2906
+ }) : options.actors ? machine.provide({ actors: options.actors }) : machine;
2907
+ const machineId = machine.config.id ?? machine.id ?? "(machine)";
2908
+ const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
2909
+ const priorEntries = options.entries ?? [];
2910
+ if (priorEntries.length > 0) require_setup_agent.validateReplayEntries(priorEntries, {
2911
+ machineId,
2912
+ machineVersion
2913
+ }, "Durable journal entries");
2914
+ const hasInit = priorEntries[0]?.event.type === require_setup_agent.AGENT_INIT_EVENT_TYPE;
2915
+ const input = hasInit ? priorEntries[0].event.input : options.input;
2916
+ const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
2917
+ const journaledCompletions = /* @__PURE__ */ new Map();
2918
+ for (const event of journal) {
2919
+ const actorId = completionActorId(event);
2920
+ if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
2921
+ }
2922
+ const mailbox = createMailbox();
2923
+ const rootAddress = machineId;
2924
+ const suppressedChildren = /* @__PURE__ */ new WeakSet();
2925
+ const startsSeen = /* @__PURE__ */ new Map();
2926
+ const liveInFlight = /* @__PURE__ */ new Set();
2927
+ const findChildRef = (effect) => {
2928
+ const raw = effect;
2929
+ const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
2930
+ for (const candidate of candidates) {
2931
+ const ref = candidate;
2932
+ if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
2933
+ }
2934
+ };
2935
+ let replaying = journal.length > 0;
2936
+ const execution = (0, xstate_durable.createDurable)(bound, {
2937
+ sendEvent(source, target, event) {
2938
+ if (target.address === rootAddress) {
2939
+ mailbox.push(event);
2940
+ return;
2941
+ }
2942
+ (0, xstate.deliverEvent)(source, target, event);
2943
+ },
2944
+ runtime(_metadata, effect) {
2945
+ const type = effect.type;
2946
+ if (type === "@xstate.spawn" || type === "@xstate.start") {
2947
+ const child = findChildRef(effect);
2948
+ if (!child) return {};
2949
+ if (type === "@xstate.spawn") {
2950
+ const seen = (startsSeen.get(child.id) ?? 0) + 1;
2951
+ startsSeen.set(child.id, seen);
2952
+ if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
2953
+ else liveInFlight.add(child.id);
2954
+ }
2955
+ if (suppressedChildren.has(child)) return {
2956
+ spawnActor() {},
2957
+ startActor() {}
2958
+ };
2959
+ }
2960
+ return {};
2961
+ },
2962
+ executeAction(action) {
2963
+ if (replaying) return;
2964
+ action.exec?.();
2965
+ },
2966
+ waitForEvent() {
2967
+ return mailbox.take();
2968
+ }
2969
+ });
2970
+ const entries = [...priorEntries];
2971
+ const entryOptions = {
2972
+ machineVersion,
2973
+ verification: options.verification ?? false
2974
+ };
2975
+ const appendEntry = (event) => {
2976
+ const entry = require_setup_agent.createReplayEntry(machine, entries, event, entryOptions);
2977
+ entries.push(entry);
2978
+ options.onEntry?.(entry);
2979
+ };
2980
+ if (!hasInit) {
2981
+ const entry = require_setup_agent.initEntry(machine, input, entryOptions);
2982
+ entries.push(entry);
2983
+ options.onEntry?.(entry);
2984
+ }
2985
+ const sessions = /* @__PURE__ */ new Map();
2986
+ let journalIndex = 0;
2987
+ let liveEventConsumed = false;
2988
+ let [snapshot, effects] = execution.initialTransition(input);
2989
+ for (;;) {
2990
+ const captured = await execution.executeEffects(effects);
2991
+ for (const rootEvent of captured) mailbox.push(rootEvent.event);
2992
+ const machineSnapshot = snapshot;
2993
+ if (machineSnapshot.status === "done") return {
2994
+ status: "done",
2995
+ output: machineSnapshot.output,
2996
+ snapshot,
2997
+ entries
2998
+ };
2999
+ if (machineSnapshot.status === "error") throw machineSnapshot.error;
3000
+ let event;
3001
+ let fromJournal = false;
3002
+ if (journalIndex < journal.length) {
3003
+ event = require_setup_agent.rebindActorSession(journal[journalIndex], machineSnapshot, sessions);
3004
+ journalIndex++;
3005
+ fromJournal = true;
3006
+ replaying = journalIndex < journal.length;
3007
+ } else if (mailbox.size() > 0) event = await mailbox.take();
3008
+ else if (liveInFlight.size > 0 && !(options.isIdle?.(snapshot) ?? false)) event = await mailbox.take();
3009
+ else if (!liveEventConsumed && options.event !== void 0) {
3010
+ event = options.event;
3011
+ liveEventConsumed = true;
3012
+ } else return {
3013
+ status: "idle",
3014
+ snapshot,
3015
+ entries
3016
+ };
3017
+ const completedId = completionActorId(event);
3018
+ if (completedId !== void 0) liveInFlight.delete(completedId);
3019
+ if (!fromJournal) appendEntry(event);
3020
+ [snapshot, effects] = execution.transition(snapshot, event);
3021
+ }
3022
+ }
3023
+ //#endregion
2676
3024
  exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
2677
3025
  exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
2678
3026
  exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
@@ -2683,13 +3031,13 @@ exports.AgentEventLogConflictError = require_event_log_store.AgentEventLogConfli
2683
3031
  exports.AgentIdleError = AgentIdleError;
2684
3032
  exports.AgentIllegalResumeEventError = AgentIllegalResumeEventError;
2685
3033
  exports.AgentLintError = AgentLintError;
3034
+ exports.AgentMaxModelCallsExceededError = AgentMaxModelCallsExceededError;
2686
3035
  exports.AgentReplayDivergenceError = require_setup_agent.AgentReplayDivergenceError;
2687
3036
  exports.AgentReplayMachineMismatchError = require_setup_agent.AgentReplayMachineMismatchError;
2688
3037
  exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
2689
3038
  exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
2690
3039
  exports.appendMessages = require_setup_agent.appendMessages;
2691
3040
  exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
2692
- exports.assertAgentMachine = assertAgentMachine;
2693
3041
  exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
2694
3042
  exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
2695
3043
  exports.assistantMessage = require_decision.assistantMessage;
@@ -2716,6 +3064,8 @@ exports.getCallUsage = require_decision.getCallUsage;
2716
3064
  exports.getJsonSchema = require_decision.getJsonSchema;
2717
3065
  exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
2718
3066
  exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
3067
+ exports.getSnapshotNodes = getSnapshotNodes;
3068
+ exports.getSnapshotRequests = getSnapshotRequests;
2719
3069
  exports.getStateMeta = require_decision.getStateMeta;
2720
3070
  exports.initEntry = require_setup_agent.initEntry;
2721
3071
  exports.inspectTransitions = inspectTransitions;
@@ -2727,12 +3077,12 @@ exports.parseAgentEvent = require_decision.parseAgentEvent;
2727
3077
  exports.parseModelRef = require_decision.parseModelRef;
2728
3078
  exports.parseOutput = require_decision.parseOutput;
2729
3079
  exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
2730
- exports.persistSnapshot = require_decision.persistSnapshot;
2731
3080
  exports.provideExecutors = provideExecutors;
2732
3081
  exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
2733
3082
  exports.replay = require_setup_agent.replay;
2734
3083
  exports.resolveDecision = require_decision.resolveDecision;
2735
3084
  exports.runAgent = runAgent;
3085
+ exports.runDurableAgent = runDurableAgent;
2736
3086
  exports.runSeam = runSeam;
2737
3087
  exports.serializeTraceEvent = serializeTraceEvent;
2738
3088
  Object.defineProperty(exports, "setupAgent", {
@@ -2746,4 +3096,3 @@ exports.systemMessage = require_decision.systemMessage;
2746
3096
  exports.toolMessage = require_decision.toolMessage;
2747
3097
  exports.traceTransitions = traceTransitions;
2748
3098
  exports.userMessage = require_decision.userMessage;
2749
- exports.verifyReplay = require_setup_agent.verifyReplay;