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