@statelyai/agent 2.0.0-alpha.15 → 2.0.0-alpha.16

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 (35) hide show
  1. package/dist/ai-sdk.cjs +1 -1
  2. package/dist/ai-sdk.d.cts +2 -2
  3. package/dist/ai-sdk.d.mts +2 -2
  4. package/dist/ai-sdk.mjs +1 -1
  5. package/dist/{decision-C0cUKvNt.cjs → decision-Ba8qrT8r.cjs} +42 -28
  6. package/dist/{decision-D9Zi7Xi5.mjs → decision-Bt2HYYRo.mjs} +31 -23
  7. package/dist/{event-log-store-D7pWtIhb.mjs → event-log-store-B-1fcfkT.mjs} +149 -141
  8. package/dist/{event-log-store-BkUNtyOF.d.mts → event-log-store-BrC9Q1xW.d.mts} +14 -12
  9. package/dist/{event-log-store-CVd2eyRy.d.cts → event-log-store-CQJq8_v4.d.cts} +14 -12
  10. package/dist/{event-log-store-CNT_7F0V.cjs → event-log-store-yquOV1TX.cjs} +148 -140
  11. package/dist/index.cjs +498 -520
  12. package/dist/index.d.cts +109 -104
  13. package/dist/index.d.mts +109 -104
  14. package/dist/index.mjs +497 -519
  15. package/dist/machines.cjs +1 -1
  16. package/dist/machines.d.cts +1 -1
  17. package/dist/machines.d.mts +1 -1
  18. package/dist/machines.mjs +1 -1
  19. package/dist/otel.d.cts +1 -1
  20. package/dist/otel.d.mts +1 -1
  21. package/dist/{run-agent-B_n4Qxye.d.cts → run-agent-BxjGaVpL.d.cts} +49 -109
  22. package/dist/{run-agent-BWzo4FLv.d.mts → run-agent-COHoCgQd.d.mts} +49 -109
  23. package/dist/{setup-agent-CpK0ZRWV.cjs → setup-agent-BLU77gqr.cjs} +141 -172
  24. package/dist/{setup-agent-DeHRW-qX.mjs → setup-agent-SbiiSbAU.mjs} +136 -167
  25. package/dist/sqlite.cjs +1 -1
  26. package/dist/sqlite.d.cts +2 -2
  27. package/dist/sqlite.d.mts +2 -2
  28. package/dist/sqlite.mjs +1 -1
  29. package/dist/{text-logic-DZW7XWy9.d.cts → text-logic-BFX5q7fM.d.cts} +23 -2
  30. package/dist/{text-logic-C7anC7qX.d.mts → text-logic-DQW8_DWW.d.mts} +23 -2
  31. package/dist/{types-DFD28AWe.d.cts → types-DYpK3QF4.d.mts} +7 -6
  32. package/dist/{types-CTBhMnFu.d.mts → types-pJ5Hn8fv.d.cts} +7 -6
  33. package/package.json +28 -28
  34. package/readme.md +6 -19
  35. package/schemas/agent-workflow.json +1 -4
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
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-CpK0ZRWV.cjs");
4
- const require_decision = require("./decision-C0cUKvNt.cjs");
5
- const require_event_log_store = require("./event-log-store-CNT_7F0V.cjs");
3
+ const require_setup_agent = require("./setup-agent-BLU77gqr.cjs");
4
+ const require_decision = require("./decision-Ba8qrT8r.cjs");
5
+ const require_event_log_store = require("./event-log-store-yquOV1TX.cjs");
6
6
  let xstate = require("xstate");
7
7
  //#region src/internal/state-request-pass.ts
8
8
  async function runTextPhase(stateRequest, baseMessages, deps) {
@@ -42,7 +42,7 @@ async function runTextPhase(stateRequest, baseMessages, deps) {
42
42
  output = await require_decision.normalizeGeneratorResult(raw, id, { request });
43
43
  const rawReasoning = raw?.reasoning;
44
44
  const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
45
- const usage = require_decision.extractCallUsage(raw);
45
+ const usage = require_decision.getCallUsage(raw);
46
46
  if (usage) deps.recordUsage?.(usage);
47
47
  deps.onResult?.(agentRequest, {
48
48
  output,
@@ -347,22 +347,22 @@ function isStateMachine(logic) {
347
347
  * its own executor (`.withExecutor(...)`, tracked in `executorBoundLogics`)
348
348
  * always runs itself; explicit binding shadows inheritance.
349
349
  */
350
- function assertBindable(machine, effectiveSources, options) {
351
- assertMachineBindable(machine, effectiveSources, options, {
350
+ function assertBindable(machine, effectiveSources, executors) {
351
+ assertMachineBindable(machine, effectiveSources, executors, {
352
352
  isChild: false,
353
353
  childPath: "",
354
354
  rebindable: true,
355
355
  visited: new Set([machine])
356
356
  });
357
357
  }
358
- function assertMachineBindable(machine, effectiveSources, options, ctx) {
358
+ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
359
359
  const invokes = [];
360
360
  collectConfiguredInvokeSrcs(machine.config, machine.config.id ?? "(root)", invokes);
361
361
  const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
362
362
  for (const { stateName, src } of invokes) {
363
363
  if (typeof src !== "string") {
364
364
  if (isStateMachine(src)) {
365
- assertChildMachineBindable(src, src, stateName, options, ctx);
365
+ assertChildMachineBindable(src, src, stateName, executors, ctx);
366
366
  continue;
367
367
  }
368
368
  if ((require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src)) throw new Error(`runAgent: ${where} '${stateName}' invokes a direct-object actor logic (kind: '${src.kind}'). Direct-object invoke srcs cannot be rebound by runAgent — either call '.withExecutor(...)' on the logic before invoking it, or register it as a string-keyed actor source instead (machine.provide({ actors: { name: logic } })) and invoke it by name.`);
@@ -371,21 +371,21 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
371
371
  const logic = effectiveSources[src];
372
372
  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
373
  if (isStateMachine(logic)) {
374
- assertChildMachineBindable(logic, src, stateName, options, ctx);
374
+ assertChildMachineBindable(logic, src, stateName, executors, ctx);
375
375
  continue;
376
376
  }
377
377
  if (src === "agent.userInput") continue;
378
378
  if (require_decision.isDecisionLogic(logic)) {
379
379
  if (require_decision.executorBoundLogics.has(logic)) continue;
380
380
  if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, "decision");
381
- if (!options.hasDecide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
381
+ if (!executors.decide) throw new Error(`runAgent: ${where} '${stateName}' invokes decision source '${src}' but no 'decide' executor was provided to runAgent(...).`);
382
382
  continue;
383
383
  }
384
384
  if (require_decision.isTextLogic(logic)) {
385
385
  if (require_decision.executorBoundLogics.has(logic)) continue;
386
386
  if (!ctx.rebindable) throw unrebindableChildRequestError(ctx.childPath, stateName, src, logic.mode === "stream" ? "streaming text" : "text");
387
- if (logic.mode === "stream" && !options.hasStreamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
388
- if (logic.mode !== "stream" && !options.hasGenerateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
387
+ if (logic.mode === "stream" && !executors.streamText) throw new Error(`runAgent: ${where} '${stateName}' invokes streaming text source '${src}' but no 'streamText' executor was provided to runAgent(...).`);
388
+ if (logic.mode !== "stream" && !executors.generateText) throw new Error(`runAgent: ${where} '${stateName}' invokes text source '${src}' but no 'generateText' executor was provided to runAgent(...).`);
389
389
  continue;
390
390
  }
391
391
  if (require_decision.isUnboundPlaceholder(logic)) throw new Error(`runAgent: ${where} '${stateName}' invokes actor source '${src}', which has no host execution. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
@@ -393,12 +393,12 @@ function assertMachineBindable(machine, effectiveSources, options, ctx) {
393
393
  }
394
394
  /** Descends the bind-time walk into an invoked child state machine, guarding
395
395
  * against a machine that (transitively) invokes itself. */
396
- function assertChildMachineBindable(childMachine, childSrc, stateName, options, ctx) {
396
+ function assertChildMachineBindable(childMachine, childSrc, stateName, executors, ctx) {
397
397
  if (ctx.visited.has(childMachine)) return;
398
398
  const childName = typeof childSrc === "string" ? childSrc : childMachine.config.id ?? "(child machine)";
399
399
  const childPath = ctx.childPath ? `${ctx.childPath} > ${childName}` : childName;
400
400
  const childSources = childMachine.sources.actors;
401
- assertMachineBindable(childMachine, childSources, options, {
401
+ assertMachineBindable(childMachine, childSources, executors, {
402
402
  isChild: true,
403
403
  childPath,
404
404
  rebindable: ctx.rebindable && typeof childSrc === "string",
@@ -429,12 +429,45 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
429
429
  function declaresUsageTransition(snapshot) {
430
430
  return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === require_setup_agent.AGENT_USAGE_EVENT_TYPE);
431
431
  }
432
+ /**
433
+ * Builds the ONE place a trace payload is emitted: it hands the payload to the
434
+ * envelope-stamping trace sink and, from that same payload, invokes the sugar
435
+ * callbacks that are projections of it — {@link RunAgentOptions.onChunk},
436
+ * {@link RunAgentOptions.onResult}, {@link RunAgentOptions.onTransition}. Each
437
+ * keeps its historical position relative to the trace: `onResult` fires just
438
+ * BEFORE its `request.end`, `onChunk`/`onTransition` just AFTER their
439
+ * `stream.chunk`/`machine.transition`. Sugar dispatch never depends on whether
440
+ * a trace sink is present, and the trace sink is never called when it is
441
+ * absent — so an `onTrace`-less run still mints no envelope (and advances no
442
+ * `seq`). @internal
443
+ */
444
+ function createTraceDispatch(sinks) {
445
+ return (payload, self) => {
446
+ switch (payload.type) {
447
+ case "stream.chunk":
448
+ sinks.onTrace?.(payload, self);
449
+ sinks.onChunk?.(payload.chunk, { request: payload.request });
450
+ return;
451
+ case "request.end":
452
+ sinks.onResult?.(payload.request, {
453
+ output: payload.output,
454
+ raw: payload.raw
455
+ });
456
+ sinks.onTrace?.(payload, self);
457
+ return;
458
+ case "machine.transition":
459
+ sinks.onTrace?.(payload, self);
460
+ sinks.onTransition?.(payload.snapshot, payload.event);
461
+ return;
462
+ default: sinks.onTrace?.(payload, self);
463
+ }
464
+ };
465
+ }
432
466
  /** Reads the durable invoke id/src off the async actor's own ref (`self`). */
433
467
  function selfIdAndSrc(self) {
434
- const ref = self;
435
468
  return {
436
- id: typeof ref?.id === "string" ? ref.id : "",
437
- src: typeof ref?.src === "string" ? ref.src : ""
469
+ id: typeof self?.id === "string" ? self.id : "",
470
+ src: typeof self?.src === "string" ? self.src : ""
438
471
  };
439
472
  }
440
473
  /**
@@ -456,8 +489,9 @@ function invokingActorOf(self, runCtx) {
456
489
  * Used by both `runAgent` and `provideExecutors` so the two paths produce
457
490
  * identical event shapes by construction. @internal
458
491
  */
459
- function wrapTextLogicForRunAgent(logic, runCtx) {
460
- return logic.withExecutor(async ({ request, self, signal }) => {
492
+ function bindTextLogic(logic, runCtx) {
493
+ return logic.withExecutor(async ({ request, self: selfArg, signal }) => {
494
+ const self = selfArg;
461
495
  const { id, src } = selfIdAndSrc(self);
462
496
  const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
463
497
  if (!executor) throw new Error(`runAgent: no '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
@@ -475,19 +509,18 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
475
509
  events: []
476
510
  };
477
511
  runCtx.consumeModelCall();
478
- runCtx.onTrace?.({
512
+ runCtx.emitTrace?.({
479
513
  type: "request.start",
480
514
  request: agentRequest
481
515
  }, self);
482
516
  try {
483
517
  const raw = await executor(requestWithTools, {
484
518
  onChunk: (chunk) => {
485
- runCtx.onTrace?.({
519
+ runCtx.emitTrace?.({
486
520
  type: "stream.chunk",
487
521
  request: agentRequest,
488
522
  chunk
489
523
  }, self);
490
- runCtx.onChunk?.(chunk, { request: agentRequest });
491
524
  },
492
525
  signal,
493
526
  ...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
@@ -496,17 +529,16 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
496
529
  const output = await require_decision.normalizeGeneratorResult(raw, id, {
497
530
  request,
498
531
  onChunk: (chunk) => {
499
- runCtx.onTrace?.({
532
+ runCtx.emitTrace?.({
500
533
  type: "stream.chunk",
501
534
  request: agentRequest,
502
535
  chunk
503
536
  }, self);
504
- runCtx.onChunk?.(chunk, { request: agentRequest });
505
537
  }
506
538
  });
507
539
  const rawReasoning = raw?.reasoning;
508
540
  const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
509
- const usage = require_decision.extractCallUsage(raw);
541
+ const usage = require_decision.getCallUsage(raw);
510
542
  if (usage) runCtx.recordUsage?.(usage, {
511
543
  kind: "text",
512
544
  ...id !== "" ? { id } : {},
@@ -514,11 +546,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
514
546
  model: request.model,
515
547
  ...request.name !== void 0 ? { name: request.name } : {}
516
548
  }, self);
517
- runCtx.onResult?.(agentRequest, {
518
- output,
519
- raw
520
- });
521
- runCtx.onTrace?.({
549
+ runCtx.emitTrace?.({
522
550
  type: "request.end",
523
551
  request: agentRequest,
524
552
  output,
@@ -528,7 +556,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
528
556
  }, self);
529
557
  return { output };
530
558
  } catch (error) {
531
- runCtx.onTrace?.({
559
+ runCtx.emitTrace?.({
532
560
  type: "request.error",
533
561
  request: agentRequest,
534
562
  error
@@ -540,7 +568,7 @@ function wrapTextLogicForRunAgent(logic, runCtx) {
540
568
  function createCountingDecide(runCtx, self) {
541
569
  return async (attemptRequest) => {
542
570
  runCtx.consumeModelCall();
543
- runCtx.onTrace?.({
571
+ runCtx.emitTrace?.({
544
572
  type: "request.start",
545
573
  request: attemptRequest
546
574
  }, self);
@@ -549,7 +577,7 @@ function createCountingDecide(runCtx, self) {
549
577
  ...attemptRequest,
550
578
  runId: runCtx.runId
551
579
  } : attemptRequest);
552
- const usage = require_decision.extractCallUsage(result);
580
+ const usage = require_decision.getCallUsage(result);
553
581
  if (usage) {
554
582
  const { src } = selfIdAndSrc(self);
555
583
  runCtx.recordUsage?.(usage, {
@@ -559,11 +587,7 @@ function createCountingDecide(runCtx, self) {
559
587
  model: attemptRequest.model
560
588
  }, self);
561
589
  }
562
- runCtx.onResult?.(attemptRequest, {
563
- output: result.event,
564
- raw: result
565
- });
566
- runCtx.onTrace?.({
590
+ runCtx.emitTrace?.({
567
591
  type: "request.end",
568
592
  request: attemptRequest,
569
593
  output: result.event,
@@ -572,7 +596,7 @@ function createCountingDecide(runCtx, self) {
572
596
  }, self);
573
597
  return result;
574
598
  } catch (error) {
575
- runCtx.onTrace?.({
599
+ runCtx.emitTrace?.({
576
600
  type: "request.error",
577
601
  request: attemptRequest,
578
602
  error
@@ -596,9 +620,10 @@ function createCountingDecide(runCtx, self) {
596
620
  * send-then-complete note inside `run` for how exit-cancels-invoke interacts
597
621
  * with `onDone`.
598
622
  */
599
- function createRunAgentDecisionLogic(logic, runCtx) {
600
- const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self }) => {
623
+ function bindDecisionLogic(logic, runCtx) {
624
+ const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
601
625
  if (!runCtx.decide) throw new Error("runAgent: no 'decide' executor provided.");
626
+ const self = selfArg;
602
627
  const { id } = selfIdAndSrc(self);
603
628
  const declaredEventTypes = logic.allowedEventTypes?.(input);
604
629
  await Promise.resolve();
@@ -624,7 +649,7 @@ function createRunAgentDecisionLogic(logic, runCtx) {
624
649
  kind: "statelyai.decisionLogic",
625
650
  maxRetries: logic.maxRetries,
626
651
  request: logic.request,
627
- withExecutor: (nextExecute) => createRunAgentDecisionLogic(logic.withExecutor(nextExecute), runCtx)
652
+ withExecutor: (nextExecute) => bindDecisionLogic(logic.withExecutor(nextExecute), runCtx)
628
653
  });
629
654
  }
630
655
  /**
@@ -647,8 +672,7 @@ let nextProvideRunId = 1;
647
672
  /** Walks `self._parent` from an invoked async leaf actor up to its root actor. */
648
673
  function rootActorOf(self) {
649
674
  let ref = self;
650
- if (!ref) return;
651
- while (ref._parent) ref = ref._parent;
675
+ while (ref?._parent) ref = ref._parent;
652
676
  return ref;
653
677
  }
654
678
  /** The per-root envelope state, minted on first use (runId `run_<n>`, matching runAgent). */
@@ -681,7 +705,7 @@ function stampRootTrace(root, payload) {
681
705
  ...payload
682
706
  };
683
707
  }
684
- /** Adapts a public `onTrace` into the payload-level {@link RunAgentBindContext.onTrace} sink used by the shared emission helpers. */
708
+ /** Adapts a public `onTrace` into the payload-level trace sink a {@link TraceDispatch} fans out to. */
685
709
  function provideTraceSink(onTrace) {
686
710
  if (!onTrace) return;
687
711
  return (payload, self) => {
@@ -700,43 +724,48 @@ function provideTraceSink(onTrace) {
700
724
  *
701
725
  * `recordUsage` has no run-level aggregate to fold into here (there is no
702
726
  * run), so it does one thing: deliver the reserved `@agent.usage` event, gated
703
- * exactly like runAgent's — see {@link deliverUsageToInvokingActor}.
727
+ * exactly like runAgent's — see {@link deliverUsageEvent}.
704
728
  */
705
729
  function provideBindContext(machine, executors, options) {
730
+ const traceSink = provideTraceSink(options.onTrace);
731
+ const onChunk = options.onChunk;
706
732
  return {
707
733
  generateText: executors.generateText,
708
734
  streamText: executors.streamText,
709
735
  decide: executors.decide,
710
- onChunk: options.onChunk ? (chunk) => options.onChunk(chunk) : void 0,
711
- onTrace: provideTraceSink(options.onTrace),
736
+ emitTrace: traceSink || onChunk ? createTraceDispatch({
737
+ onTrace: traceSink,
738
+ onChunk: onChunk ? (chunk) => onChunk(chunk) : void 0
739
+ }) : void 0,
712
740
  consumeModelCall: () => {},
713
741
  recordUsage: (usage, source, self) => {
714
- deliverUsageToInvokingActor(usage, source ?? {}, self);
742
+ deliverUsageEvent(usage, source ?? {}, () => self?._parent);
715
743
  },
716
744
  actorHolder: { actorRef: void 0 },
717
745
  schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
718
746
  };
719
747
  }
720
748
  /**
721
- * `provideExecutors`' counterpart to runAgent's `deliverUsageEvent`: after a
722
- * bound call settles with reported usage, send the reserved
723
- * `@agent.usage` event to the machine actor that INVOKED the request — read
724
- * off the settling request actor's `self._parent`, which under a live
725
- * `createActor` tree is always the invoking machine (there is no run-scoped
726
- * root actor on this path).
749
+ * The single reserved-`@agent.usage` delivery seam, shared by both bind paths:
750
+ * after a bound call settles with reported usage, send the event to the machine
751
+ * actor `resolveActorRef` names the run's root actor on the `runAgent` path,
752
+ * the settling request actor's `self._parent` (always the invoking machine
753
+ * under a live `createActor` tree) on the `provideExecutors` path.
727
754
  *
728
- * Gated identically to runAgent: the invoking snapshot must be active, must
729
- * declare an `'@agent.usage'` transition EXPLICITLY (see
730
- * {@link declaresUsageTransition} — a catch-all `on: { '*' }` is not an opt-in),
731
- * and must be able to take the event. There is no cycle to settle in
732
- * uncontrolled mode, so there are no dropped stragglers.
755
+ * 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
759
+ * call that settled after the cycle resolved, which drops the event (traced as
760
+ * `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
761
+ * settle, so it passes no gate and has no dropped stragglers.
733
762
  *
734
- * Delivery follows `provideExecutors`' binding boundary: only sources IT bound
735
- * report here, so an invoked child machine that was not itself passed through
763
+ * Delivery follows each path's binding boundary: only sources IT bound report
764
+ * here, so an invoked child machine that was not itself passed through
736
765
  * `provideExecutors` reports nothing. @internal
737
766
  */
738
- function deliverUsageToInvokingActor(usage, source, self) {
739
- const actorRef = self?._parent;
767
+ function deliverUsageEvent(usage, source, resolveActorRef, onDropped) {
768
+ const actorRef = resolveActorRef();
740
769
  if (!actorRef) return;
741
770
  const snapshot = actorRef.getSnapshot();
742
771
  if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
@@ -745,17 +774,18 @@ function deliverUsageToInvokingActor(usage, source, self) {
745
774
  ...source,
746
775
  usage
747
776
  };
777
+ if (onDropped?.(event)) return;
748
778
  if (!snapshot.can(event)) return;
749
779
  actorRef.send(event);
750
780
  }
751
781
  /**
752
782
  * Host-binds one text/stream source for {@link provideExecutors} using the SAME
753
- * emission helper as `runAgent` ({@link wrapTextLogicForRunAgent}), so a bound
783
+ * emission helper as `runAgent` ({@link bindTextLogic}), so a bound
754
784
  * text request emits request.start/stream.chunk/request.end/request.error with
755
785
  * identical shapes. @internal
756
786
  */
757
787
  function bindTextForProvide(machine, logic, executors, options) {
758
- return wrapTextLogicForRunAgent(logic, provideBindContext(machine, executors, options));
788
+ return bindTextLogic(logic, provideBindContext(machine, executors, options));
759
789
  }
760
790
  /**
761
791
  * Host-binds one `DecisionLogic`/`agent.decide` source for
@@ -764,7 +794,7 @@ function bindTextForProvide(machine, logic, executors, options) {
764
794
  * request-level tracing runAgent emits, minus run-scoped counting. @internal
765
795
  */
766
796
  function bindDecisionForProvide(machine, logic, executors, options) {
767
- return createRunAgentDecisionLogic(logic, provideBindContext(machine, executors, options));
797
+ return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
768
798
  }
769
799
  /**
770
800
  * Validates `input` against the machine's registered input schema, returning
@@ -807,11 +837,11 @@ function rebindChildMachine(childMachine, runCtx, visited) {
807
837
  const wrapped = {};
808
838
  for (const [key, logic] of Object.entries(sources)) {
809
839
  if (require_decision.isDecisionLogic(logic)) {
810
- if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = createRunAgentDecisionLogic(logic, runCtx);
840
+ if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindDecisionLogic(logic, runCtx);
811
841
  continue;
812
842
  }
813
843
  if (require_decision.isTextLogic(logic)) {
814
- if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = wrapTextLogicForRunAgent(logic, runCtx);
844
+ if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
815
845
  continue;
816
846
  }
817
847
  if (isStateMachine(logic)) {
@@ -903,20 +933,22 @@ function createAgentSession(machine, options, lifecycle) {
903
933
  machineId,
904
934
  version: machineVersion
905
935
  };
906
- const stampAgentMeta = (snapshot) => {
907
- if (snapshot && typeof snapshot === "object") snapshot.agentMeta = agentMeta;
908
- };
909
- const onTrace = (event) => {
910
- options.onTrace?.({
911
- schemaVersion: 1,
912
- runId,
913
- seq: ++traceSeq,
914
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
915
- machineId,
916
- machineVersion,
917
- ...event
918
- });
919
- };
936
+ const onTrace = createTraceDispatch({
937
+ onTrace: (payload) => {
938
+ options.onTrace?.({
939
+ schemaVersion: 1,
940
+ runId,
941
+ seq: ++traceSeq,
942
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
943
+ machineId,
944
+ machineVersion,
945
+ ...payload
946
+ });
947
+ },
948
+ onChunk: options.onChunk,
949
+ onResult: options.onResult,
950
+ onTransition: options.onTransition
951
+ });
920
952
  const consumeModelCall = () => {
921
953
  if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
922
954
  if (modelCallCount + 1 > maxModelCalls) {
@@ -927,33 +959,20 @@ function createAgentSession(machine, options, lifecycle) {
927
959
  };
928
960
  const tokenTotals = {};
929
961
  const cycleGate = { isResolved: () => false };
930
- const deliverUsageEvent = (usage, source) => {
931
- const actorRef = actorHolder.actorRef;
932
- if (!actorRef) return;
933
- const event = {
934
- type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
935
- ...source,
936
- usage
937
- };
938
- const snapshot = actorRef.getSnapshot();
939
- if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
940
- if (cycleGate.isResolved()) {
941
- onTrace({
942
- type: "usage.dropped",
943
- event,
944
- reason: "settled"
945
- });
946
- return;
947
- }
948
- if (!snapshot.can(event)) return;
949
- actorRef.send(event);
950
- };
951
962
  const recordUsage = (usage, source = {}) => {
952
963
  for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
953
964
  const value = usage[field];
954
965
  if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
955
966
  }
956
- deliverUsageEvent(usage, source);
967
+ deliverUsageEvent(usage, source, () => actorHolder.actorRef, (event) => {
968
+ if (!cycleGate.isResolved()) return false;
969
+ onTrace({
970
+ type: "usage.dropped",
971
+ event,
972
+ reason: "settled"
973
+ });
974
+ return true;
975
+ });
957
976
  };
958
977
  const runUsage = () => ({
959
978
  ...tokenTotals,
@@ -973,20 +992,14 @@ function createAgentSession(machine, options, lifecycle) {
973
992
  };
974
993
  const provided = machine.provide({ actors: options.actors });
975
994
  const effectiveSources = provided.sources.actors;
976
- assertBindable(provided, effectiveSources, {
977
- hasGenerateText: !!options.executors?.generateText,
978
- hasDecide: !!options.executors?.decide,
979
- hasStreamText: !!options.executors?.streamText
980
- });
995
+ assertBindable(provided, effectiveSources, options.executors ?? {});
981
996
  if (options.getRequests && !options.executors?.generateText && !options.executors?.decide) throw new Error("runAgent: 'getRequests' requires a 'generateText' and/or 'decide' executor — the returned requests run through them.");
982
997
  const actorHolder = { actorRef: void 0 };
983
998
  const runCtx = {
984
999
  generateText: options.executors?.generateText,
985
1000
  streamText: options.executors?.streamText,
986
1001
  decide: options.executors?.decide,
987
- onChunk: options.onChunk,
988
- onResult: options.onResult,
989
- onTrace,
1002
+ emitTrace: onTrace,
990
1003
  consumeModelCall,
991
1004
  recordUsage,
992
1005
  actorHolder,
@@ -1007,11 +1020,11 @@ function createAgentSession(machine, options, lifecycle) {
1007
1020
  continue;
1008
1021
  }
1009
1022
  if (require_decision.isDecisionLogic(logic)) {
1010
- wrappedSources[key] = createRunAgentDecisionLogic(logic, runCtx);
1023
+ wrappedSources[key] = bindDecisionLogic(logic, runCtx);
1011
1024
  continue;
1012
1025
  }
1013
1026
  if (require_decision.isTextLogic(logic)) {
1014
- if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = wrapTextLogicForRunAgent(logic, runCtx);
1027
+ if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
1015
1028
  continue;
1016
1029
  }
1017
1030
  if (isStateMachine(logic)) {
@@ -1053,9 +1066,11 @@ function createAgentSession(machine, options, lifecycle) {
1053
1066
  }
1054
1067
  const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
1055
1068
  const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
1056
- const stampMessages = (snapshot) => {
1069
+ const stampSettledSnapshot = (snapshot) => {
1070
+ if (!snapshot || typeof snapshot !== "object") return;
1071
+ snapshot.agentMeta = agentMeta;
1057
1072
  if (!options.getRequests && !options.messages && messages.length === 0) return;
1058
- if (snapshot && typeof snapshot === "object") snapshot.messages = [...messages];
1073
+ snapshot.messages = [...messages];
1059
1074
  };
1060
1075
  if (effectiveSnapshot !== void 0 && options.event !== void 0 && (options.onIllegalResumeEvent ?? "throw") === "throw") {
1061
1076
  const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
@@ -1063,21 +1078,11 @@ function createAgentSession(machine, options, lifecycle) {
1063
1078
  if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
1064
1079
  }
1065
1080
  const replayEvents = [...resumeEvents ?? []];
1066
- const replayEventIds = /* @__PURE__ */ new Set();
1067
- for (let index = 0; index < replayEvents.length; index++) {
1068
- const entry = replayEvents[index];
1069
- require_event_log_store.assertAgentLogEntry(entry);
1070
- if (entry.index !== index) throw new Error(`runAgent events must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
1071
- if (entry.machineId !== machineId || entry.machineVersion !== machineVersion) throw new require_setup_agent.AgentReplayMachineMismatchError(entry.id, entry.index, {
1072
- machineId,
1073
- machineVersion
1074
- }, {
1075
- machineId: entry.machineId,
1076
- machineVersion: entry.machineVersion
1077
- });
1078
- if (replayEventIds.has(entry.id)) throw new Error(`runAgent events contain duplicate event id '${entry.id}'.`);
1079
- replayEventIds.add(entry.id);
1080
- }
1081
+ require_setup_agent.validateReplayEntries(replayEvents, {
1082
+ machineId,
1083
+ machineVersion
1084
+ }, "runAgent events");
1085
+ const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
1081
1086
  const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
1082
1087
  const appendReplayEvent = (event) => {
1083
1088
  const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
@@ -1096,247 +1101,239 @@ function createAgentSession(machine, options, lifecycle) {
1096
1101
  replayEvents.push(entry);
1097
1102
  options.onEvent?.(entry);
1098
1103
  }
1099
- return (() => {
1100
- let settled = false;
1101
- let finalized = false;
1102
- cycleGate.isResolved = () => settled;
1103
- let lastResult;
1104
- const waiters = [];
1105
- let idleTimer;
1106
- let actor;
1107
- let deliveringResumeEvent = options.event !== void 0;
1108
- const settle = (outcome) => {
1109
- if (settled) return;
1110
- settled = true;
1111
- const result = {
1112
- ...outcome,
1113
- events: [...replayEvents],
1114
- usage: runUsage()
1115
- };
1116
- if (idleTimer !== void 0) clearTimeout(idleTimer);
1117
- stampAgentMeta(result.snapshot);
1118
- stampMessages(result.snapshot);
1119
- if ("persistedSnapshot" in result) {
1120
- stampAgentMeta(result.persistedSnapshot);
1121
- stampMessages(result.persistedSnapshot);
1122
- }
1123
- onTrace({
1124
- type: "run.end",
1125
- ...outcome
1126
- });
1127
- if (lifecycle.oneShot || result.status !== "idle") {
1128
- finalized = true;
1129
- if (options.signal) options.signal.removeEventListener("abort", onAbort);
1130
- actor.stop();
1131
- }
1132
- lastResult = result;
1133
- for (const resolve of waiters.splice(0)) resolve(result);
1104
+ let settled = false;
1105
+ let finalized = false;
1106
+ cycleGate.isResolved = () => settled;
1107
+ let lastResult;
1108
+ const waiters = [];
1109
+ let idleTimer;
1110
+ let actor;
1111
+ let deliveringResumeEvent = options.event !== void 0;
1112
+ const settle = (outcome) => {
1113
+ if (settled) return;
1114
+ settled = true;
1115
+ const result = {
1116
+ ...outcome,
1117
+ events: [...replayEvents],
1118
+ usage: runUsage()
1134
1119
  };
1135
- const onAbort = () => {
1136
- settle({
1137
- status: "error",
1138
- cause: "aborted",
1139
- error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
1140
- snapshot: actor.getSnapshot()
1141
- });
1142
- };
1143
- const settleIdle = (current) => {
1144
- warnNonSerializableContext(current);
1145
- const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
1146
- settle({
1147
- status: "idle",
1148
- snapshot: current,
1149
- persistedSnapshot: actor.getPersistedSnapshot(),
1150
- ...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
1151
- });
1152
- };
1153
- let interpreting = false;
1154
- let interpretSeq = 0;
1155
- const appendToLog = (...items) => {
1156
- messages.push(...items);
1157
- if (options.onMessage) {
1158
- const info = {
1159
- runId,
1160
- machineId,
1161
- machineVersion
1162
- };
1163
- for (const item of items) options.onMessage(item, info);
1164
- }
1165
- };
1166
- const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
1167
- const settleInterpretError = (error) => {
1168
- settle({
1169
- status: "error",
1170
- cause: runErrorCause(error),
1171
- error,
1172
- snapshot: actor.getSnapshot()
1173
- });
1174
- };
1175
- const passDeps = {
1176
- getSnapshot: () => actor.getSnapshot(),
1177
- send: (event) => actor.send(event),
1178
- isSettled: () => settled,
1179
- messages,
1180
- appendToLog,
1181
- generateText: runCtx.generateText,
1182
- decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
1183
- consumeModelCall,
1184
- recordUsage,
1185
- nextRequestId: () => `interpret_${++interpretSeq}`,
1186
- onTrace,
1187
- onResult: runCtx.onResult,
1188
- schemas: runCtx.schemas,
1189
- signal: options.signal
1190
- };
1191
- const maybeInterpret = (snapshot) => {
1192
- if (!options.getRequests || settled) return false;
1193
- if (interpreting) return true;
1194
- let requested;
1195
- try {
1196
- requested = options.getRequests(snapshot, { messages });
1197
- } catch (error) {
1198
- settleInterpretError(error);
1199
- return true;
1200
- }
1201
- const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
1202
- if (requests.length === 0) return false;
1203
- interpreting = true;
1204
- runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
1205
- if (settled || sentAny) return;
1206
- const current = actor.getSnapshot();
1207
- if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
1208
- }).catch((error) => settleInterpretError(error)).finally(() => {
1209
- interpreting = false;
1210
- if (!settled) scheduleIdleCheck();
1211
- });
1120
+ if (idleTimer !== void 0) clearTimeout(idleTimer);
1121
+ stampSettledSnapshot(result.snapshot);
1122
+ if ("persistedSnapshot" in result) stampSettledSnapshot(result.persistedSnapshot);
1123
+ onTrace({
1124
+ type: "run.end",
1125
+ ...outcome
1126
+ });
1127
+ if (lifecycle.oneShot || result.status !== "idle") {
1128
+ finalized = true;
1129
+ if (options.signal) options.signal.removeEventListener("abort", onAbort);
1130
+ actor.stop();
1131
+ }
1132
+ lastResult = result;
1133
+ for (const resolve of waiters.splice(0)) resolve(result);
1134
+ };
1135
+ const onAbort = () => {
1136
+ settle({
1137
+ status: "error",
1138
+ cause: "aborted",
1139
+ error: options.signal?.reason ?? /* @__PURE__ */ new Error("Aborted"),
1140
+ snapshot: actor.getSnapshot()
1141
+ });
1142
+ };
1143
+ const settleIdle = (current) => {
1144
+ warnNonSerializableContext(current);
1145
+ const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
1146
+ settle({
1147
+ status: "idle",
1148
+ snapshot: current,
1149
+ persistedSnapshot: actor.getPersistedSnapshot(),
1150
+ ...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
1151
+ });
1152
+ };
1153
+ let interpreting = false;
1154
+ let interpretSeq = 0;
1155
+ const appendToLog = (...items) => {
1156
+ messages.push(...items);
1157
+ if (options.onMessage) {
1158
+ const info = {
1159
+ runId,
1160
+ machineId,
1161
+ machineVersion
1162
+ };
1163
+ for (const item of items) options.onMessage(item, info);
1164
+ }
1165
+ };
1166
+ const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
1167
+ const settleInterpretError = (error) => {
1168
+ settle({
1169
+ status: "error",
1170
+ cause: runErrorCause(error),
1171
+ error,
1172
+ snapshot: actor.getSnapshot()
1173
+ });
1174
+ };
1175
+ const passDeps = {
1176
+ getSnapshot: () => actor.getSnapshot(),
1177
+ send: (event) => actor.send(event),
1178
+ isSettled: () => settled,
1179
+ messages,
1180
+ appendToLog,
1181
+ generateText: runCtx.generateText,
1182
+ decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
1183
+ consumeModelCall,
1184
+ recordUsage,
1185
+ nextRequestId: () => `interpret_${++interpretSeq}`,
1186
+ onTrace,
1187
+ schemas: runCtx.schemas,
1188
+ signal: options.signal
1189
+ };
1190
+ const maybeInterpret = (snapshot) => {
1191
+ if (!options.getRequests || settled) return false;
1192
+ if (interpreting) return true;
1193
+ let requested;
1194
+ try {
1195
+ requested = options.getRequests(snapshot, { messages });
1196
+ } catch (error) {
1197
+ settleInterpretError(error);
1212
1198
  return true;
1213
- };
1214
- const scheduleIdleCheck = () => {
1215
- if (idleTimer !== void 0) clearTimeout(idleTimer);
1216
- idleTimer = setTimeout(() => {
1217
- idleTimer = void 0;
1218
- if (settled) return;
1219
- const current = actor.getSnapshot();
1220
- if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1221
- if (!maybeInterpret(current)) {
1222
- if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1223
- warnedHeuristicIdle = true;
1224
- 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').");
1225
- }
1226
- settleIdle(current);
1199
+ }
1200
+ const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
1201
+ if (requests.length === 0) return false;
1202
+ interpreting = true;
1203
+ runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
1204
+ if (settled || sentAny) return;
1205
+ const current = actor.getSnapshot();
1206
+ if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
1207
+ }).catch((error) => settleInterpretError(error)).finally(() => {
1208
+ interpreting = false;
1209
+ if (!settled) scheduleIdleCheck();
1210
+ });
1211
+ return true;
1212
+ };
1213
+ const scheduleIdleCheck = () => {
1214
+ if (idleTimer !== void 0) clearTimeout(idleTimer);
1215
+ idleTimer = setTimeout(() => {
1216
+ idleTimer = void 0;
1217
+ if (settled) return;
1218
+ const current = actor.getSnapshot();
1219
+ if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1220
+ if (!maybeInterpret(current)) {
1221
+ if (!declaredSuspensionPredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1222
+ 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').");
1227
1224
  }
1225
+ settleIdle(current);
1228
1226
  }
1229
- }, 0);
1230
- };
1231
- actor = (0, xstate.createActor)(boundMachine, {
1232
- input: resolvedInput,
1233
- snapshot: effectiveSnapshot,
1234
- inspect: (event) => {
1235
- if (typeof options.inspect === "function") options.inspect(event);
1236
- else options.inspect?.next?.(event);
1237
- if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1238
- if (settled) {
1239
- if (finalized) return;
1240
- settled = false;
1241
- lastResult = void 0;
1242
- }
1243
- const snapshot = event.snapshot;
1244
- let eventId;
1245
- if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
1246
- else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
1247
- onTrace({
1248
- type: "machine.transition",
1249
- snapshot,
1250
- event: event.event,
1251
- ...eventId !== void 0 ? { eventId } : {}
1252
- });
1253
- options.onTransition?.(snapshot, event.event);
1254
- if (snapshot.status === "done") {
1255
- settle({
1256
- status: "done",
1257
- output: snapshot.output,
1258
- snapshot
1259
- });
1260
- return;
1261
- }
1262
- if (snapshot.status === "error") {
1263
- settle({
1264
- status: "error",
1265
- cause: runErrorCause(snapshot.error),
1266
- error: snapshot.error,
1267
- snapshot
1268
- });
1269
- return;
1270
- }
1271
- if (snapshot.status === "stopped") {
1272
- settle({
1273
- status: "error",
1274
- cause: "stopped",
1275
- error: /* @__PURE__ */ new Error("Actor stopped externally."),
1276
- snapshot
1277
- });
1278
- return;
1279
- }
1280
- if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1281
- queueMicrotask(() => {
1282
- if (settled) return;
1283
- const current = actor.getSnapshot();
1284
- if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1285
- if (!maybeInterpret(current)) settleIdle(current);
1286
- } else scheduleIdleCheck();
1287
- });
1288
- return;
1289
- }
1290
- scheduleIdleCheck();
1291
1227
  }
1292
- });
1293
- actorHolder.actorRef = actor;
1294
- actor.subscribe({ error: () => {} });
1295
- actor.on("*", (event) => {
1228
+ }, 0);
1229
+ };
1230
+ actor = (0, xstate.createActor)(boundMachine, {
1231
+ input: resolvedInput,
1232
+ snapshot: effectiveSnapshot,
1233
+ inspect: (event) => {
1234
+ if (typeof options.inspect === "function") options.inspect(event);
1235
+ else options.inspect?.next?.(event);
1236
+ if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1237
+ if (settled) {
1238
+ if (finalized) return;
1239
+ settled = false;
1240
+ lastResult = void 0;
1241
+ }
1242
+ const snapshot = event.snapshot;
1243
+ let eventId;
1244
+ if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
1245
+ else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
1296
1246
  onTrace({
1297
- type: "emit",
1298
- event
1247
+ type: "machine.transition",
1248
+ snapshot,
1249
+ event: event.event,
1250
+ ...eventId !== void 0 ? { eventId } : {}
1299
1251
  });
1300
- });
1301
- for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
1302
- const sessionApi = {
1303
- actor,
1304
- get events() {
1305
- return replayEvents;
1306
- },
1307
- usage: runUsage,
1308
- settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
1309
- waiters.push(resolve);
1310
- }),
1311
- stop: () => {
1312
- actor.stop();
1252
+ if (snapshot.status === "done") {
1253
+ settle({
1254
+ status: "done",
1255
+ output: snapshot.output,
1256
+ snapshot
1257
+ });
1258
+ return;
1313
1259
  }
1314
- };
1315
- if (options.signal) {
1316
- if (options.signal.aborted) {
1260
+ if (snapshot.status === "error") {
1317
1261
  settle({
1318
1262
  status: "error",
1319
- cause: "aborted",
1320
- error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
1321
- snapshot: actor.getSnapshot()
1263
+ cause: runErrorCause(snapshot.error),
1264
+ error: snapshot.error,
1265
+ snapshot
1266
+ });
1267
+ return;
1268
+ }
1269
+ if (snapshot.status === "stopped") {
1270
+ settle({
1271
+ status: "error",
1272
+ cause: "stopped",
1273
+ error: /* @__PURE__ */ new Error("Actor stopped externally."),
1274
+ snapshot
1275
+ });
1276
+ return;
1277
+ }
1278
+ if (!deliveringResumeEvent && isSuspended(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1279
+ queueMicrotask(() => {
1280
+ if (settled) return;
1281
+ const current = actor.getSnapshot();
1282
+ if (isSuspended(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1283
+ if (!maybeInterpret(current)) settleIdle(current);
1284
+ } else scheduleIdleCheck();
1322
1285
  });
1323
- return sessionApi;
1286
+ return;
1324
1287
  }
1325
- options.signal.addEventListener("abort", onAbort);
1288
+ scheduleIdleCheck();
1326
1289
  }
1290
+ });
1291
+ actorHolder.actorRef = actor;
1292
+ actor.subscribe({ error: () => {} });
1293
+ actor.on("*", (event) => {
1327
1294
  onTrace({
1328
- type: "run.start",
1329
- ...resolvedInput !== void 0 ? { input: resolvedInput } : {},
1330
- ...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
1331
- ...options.event !== void 0 ? { event: options.event } : {}
1295
+ type: "emit",
1296
+ event
1332
1297
  });
1333
- actor.start();
1334
- if (options.event) {
1335
- deliveringResumeEvent = false;
1336
- actor.send(options.event);
1298
+ });
1299
+ for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
1300
+ const sessionApi = {
1301
+ actor,
1302
+ get events() {
1303
+ return replayEvents;
1304
+ },
1305
+ usage: runUsage,
1306
+ settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
1307
+ waiters.push(resolve);
1308
+ }),
1309
+ stop: () => {
1310
+ actor.stop();
1311
+ }
1312
+ };
1313
+ if (options.signal) {
1314
+ if (options.signal.aborted) {
1315
+ settle({
1316
+ status: "error",
1317
+ cause: "aborted",
1318
+ error: options.signal.reason ?? /* @__PURE__ */ new Error("Aborted"),
1319
+ snapshot: actor.getSnapshot()
1320
+ });
1321
+ return sessionApi;
1337
1322
  }
1338
- return sessionApi;
1339
- })();
1323
+ options.signal.addEventListener("abort", onAbort);
1324
+ }
1325
+ onTrace({
1326
+ type: "run.start",
1327
+ ...resolvedInput !== void 0 ? { input: resolvedInput } : {},
1328
+ ...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
1329
+ ...options.event !== void 0 ? { event: options.event } : {}
1330
+ });
1331
+ actor.start();
1332
+ if (options.event) {
1333
+ deliveringResumeEvent = false;
1334
+ actor.send(options.event);
1335
+ }
1336
+ return sessionApi;
1340
1337
  }
1341
1338
  /**
1342
1339
  * Runs an agent machine to a **final state**, for run-to-done flows where an
@@ -1492,14 +1489,17 @@ function createAgentRun(machine, options) {
1492
1489
  });
1493
1490
  else buffer.push(event);
1494
1491
  };
1495
- const close = () => {
1496
- if (closed) return;
1497
- closed = true;
1492
+ const drain = () => {
1498
1493
  while (resolvers.length > 0) resolvers.shift()({
1499
1494
  value: void 0,
1500
1495
  done: true
1501
1496
  });
1502
1497
  };
1498
+ const close = () => {
1499
+ if (closed) return;
1500
+ closed = true;
1501
+ drain();
1502
+ };
1503
1503
  const userOnTrace = options.onTrace;
1504
1504
  const result = runAgent(machine, {
1505
1505
  ...options,
@@ -1528,10 +1528,7 @@ function createAgentRun(machine, options) {
1528
1528
  return() {
1529
1529
  stopped = true;
1530
1530
  buffer.length = 0;
1531
- while (resolvers.length > 0) resolvers.shift()({
1532
- value: void 0,
1533
- done: true
1534
- });
1531
+ drain();
1535
1532
  return Promise.resolve({
1536
1533
  value: void 0,
1537
1534
  done: true
@@ -1579,7 +1576,7 @@ function createAgentRun(machine, options) {
1579
1576
  * Throws at bind time if a source needs an executor kind that `executors` does
1580
1577
  * not provide.
1581
1578
  *
1582
- * v1 does NOT descend into invoked child state machines: a string-keyed child
1579
+ * `provideExecutors` does not descend into invoked child state machines: a string-keyed child
1583
1580
  * machine source is left untouched, so a child with its own agent invokes needs
1584
1581
  * its own `provideExecutors(...)` (or `runAgent`, which does rebind children).
1585
1582
  */
@@ -1588,32 +1585,36 @@ function provideExecutors(machine, executors, options = {}) {
1588
1585
  onChunk: options.onChunk,
1589
1586
  onTrace: options.onTrace
1590
1587
  };
1591
- const provided = options.actors ? machine.provide({ actors: options.actors }) : machine;
1588
+ const withActors = (target, actors) => target.provide({ actors });
1589
+ const provided = options.actors ? withActors(machine, options.actors) : machine;
1592
1590
  const effectiveSources = provided.sources.actors;
1593
1591
  const wrappedSources = {};
1594
1592
  const invokedSrcs = getConfiguredInvokeSrcs(provided);
1595
1593
  for (const [key, logic] of Object.entries(effectiveSources)) {
1596
1594
  if (key === "agent.userInput") continue;
1597
- if (require_decision.isDecisionLogic(logic)) {
1598
- if (require_decision.executorBoundLogics.has(logic)) continue;
1599
- if (!executors.decide) {
1600
- if (invokedSrcs.has(key)) throw missingExecutorError(key, "decision", "decide");
1601
- continue;
1602
- }
1603
- wrappedSources[key] = bindDecisionForProvide(provided, logic, executors, bindOptions);
1604
- 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
+ };
1605
1608
  }
1606
- if (require_decision.isTextLogic(logic)) {
1607
- if (require_decision.executorBoundLogics.has(logic)) continue;
1608
- if (!(logic.mode === "stream" ? executors.streamText : executors.generateText)) {
1609
- if (invokedSrcs.has(key)) throw logic.mode === "stream" ? missingExecutorError(key, "streaming text", "streamText") : missingExecutorError(key, "text", "generateText");
1610
- continue;
1611
- }
1612
- wrappedSources[key] = bindTextForProvide(provided, logic, executors, bindOptions);
1609
+ if (!binding) continue;
1610
+ if (require_decision.executorBoundLogics.has(logic)) continue;
1611
+ if (!executors[binding.executorKey]) {
1612
+ if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
1613
1613
  continue;
1614
1614
  }
1615
+ wrappedSources[key] = binding.bind();
1615
1616
  }
1616
- return provided.provide({ actors: wrappedSources });
1617
+ return withActors(provided, wrappedSources);
1617
1618
  }
1618
1619
  function missingExecutorError(src, kind, executor) {
1619
1620
  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.`);
@@ -1633,10 +1634,8 @@ function buildStateIndex(rootConfig) {
1633
1634
  const hasChildren = !!config.states && Object.keys(config.states).length > 0;
1634
1635
  index.set(path, {
1635
1636
  path,
1636
- name,
1637
1637
  config,
1638
1638
  parentPath,
1639
- type: config.type,
1640
1639
  isFinal: config.type === "final",
1641
1640
  isParallel: config.type === "parallel",
1642
1641
  isCompound: hasChildren && config.type !== "parallel",
@@ -1754,36 +1753,20 @@ function ancestorChain(node, index) {
1754
1753
  function hasNonEmptyOn(config) {
1755
1754
  return !!config.on && Object.keys(config.on).length > 0;
1756
1755
  }
1757
- function decisionKindOf(src, actors) {
1758
- if (typeof src === "string") {
1759
- if (src === DECIDE_SRC) return "decision";
1760
- return require_decision.isDecisionLogic(actors[src]) ? "decision" : void 0;
1761
- }
1762
- return require_decision.isDecisionLogic(src) ? "decision" : void 0;
1756
+ function isDecisionInvoke(src, actors) {
1757
+ if (typeof src === "string") return src === DECIDE_SRC || require_decision.isDecisionLogic(actors[src]);
1758
+ return require_decision.isDecisionLogic(src);
1763
1759
  }
1764
1760
  function isAgentLogicNeedingBinding(src) {
1765
1761
  return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
1766
1762
  }
1767
- function schemaExposesJson(schema) {
1768
- if (!schema) return false;
1769
- try {
1770
- return require_decision.getJsonSchemaSync(schema) !== void 0;
1771
- } catch {
1772
- return false;
1773
- }
1774
- }
1775
- function isDeclaredOutputSchema(schema) {
1776
- if (!schema) return false;
1777
- let json;
1763
+ function readJsonSchema(schema) {
1764
+ if (!schema) return;
1778
1765
  try {
1779
- json = require_decision.getJsonSchemaSync(schema);
1766
+ return require_decision.getJsonSchemaSync(schema);
1780
1767
  } catch {
1781
- return false;
1768
+ return;
1782
1769
  }
1783
- if (!json) return false;
1784
- const properties = json.properties;
1785
- const required = json.required;
1786
- return json.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0;
1787
1770
  }
1788
1771
  function checkUnreachableStates(ctx) {
1789
1772
  const out = [];
@@ -1798,8 +1781,7 @@ function checkUnreachableStates(ctx) {
1798
1781
  function checkDecideWithoutEvents(ctx) {
1799
1782
  const out = [];
1800
1783
  for (const node of ctx.index.values()) for (const invoke of node.invokes) {
1801
- const kind = decisionKindOf(invoke.src, ctx.actors);
1802
- if (!kind) continue;
1784
+ if (!isDecisionInvoke(invoke.src, ctx.actors)) continue;
1803
1785
  const selfHandles = hasNonEmptyOn(node.config);
1804
1786
  const ancestorHandles = ancestorChain(node, ctx.index).some((ancestor) => hasNonEmptyOn(ancestor.config));
1805
1787
  const invokeObserves = invoke.onDone !== void 0;
@@ -1809,7 +1791,7 @@ function checkDecideWithoutEvents(ctx) {
1809
1791
  code: "decide-without-events",
1810
1792
  severity: "error",
1811
1793
  path: node.path,
1812
- message: `State '${node.path}' invokes ${kind} source '${srcName}', but neither it nor any ancestor handles any event (no 'on:'), so the ${kind}'s chosen event can never be delivered. Add an 'on:' handler for the candidate events.`
1794
+ message: `State '${node.path}' invokes decision source '${srcName}', but neither it nor any ancestor handles any event (no 'on:'), so the decision's chosen event can never be delivered. Add an 'on:' handler for the candidate events.`
1813
1795
  });
1814
1796
  }
1815
1797
  return out;
@@ -1817,7 +1799,7 @@ function checkDecideWithoutEvents(ctx) {
1817
1799
  function checkUnserializableContext(ctx) {
1818
1800
  const contextSchema = ctx.schemas?.context;
1819
1801
  if (!contextSchema) return [];
1820
- if (schemaExposesJson(contextSchema)) return [];
1802
+ if (readJsonSchema(contextSchema)) return [];
1821
1803
  return [{
1822
1804
  code: "unserializable-context",
1823
1805
  severity: "warning",
@@ -1841,7 +1823,10 @@ function checkDirectObjectSrc(ctx) {
1841
1823
  return out;
1842
1824
  }
1843
1825
  function checkFinalWithoutOutput(ctx) {
1844
- if (!isDeclaredOutputSchema(ctx.schemas?.output)) return [];
1826
+ const outputJson = readJsonSchema(ctx.schemas?.output);
1827
+ const properties = outputJson?.properties;
1828
+ const required = outputJson?.required;
1829
+ if (!(outputJson?.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0)) return [];
1845
1830
  if (ctx.config.output !== void 0) return [];
1846
1831
  const out = [];
1847
1832
  for (const node of ctx.index.values()) {
@@ -1935,7 +1920,6 @@ function lintAgentMachine(machine, options = {}) {
1935
1920
  const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
1936
1921
  const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
1937
1922
  const ctx = {
1938
- machine,
1939
1923
  config,
1940
1924
  index,
1941
1925
  reachable,
@@ -2018,7 +2002,7 @@ function takeFromQueue(channel, src) {
2018
2002
  async function simulateAgent(machine, options) {
2019
2003
  const maxSteps = options.maxSteps ?? 100;
2020
2004
  const script = {
2021
- text: { ...options.script.text },
2005
+ text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
2022
2006
  decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
2023
2007
  invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
2024
2008
  };
@@ -2090,6 +2074,7 @@ function scriptDryError(kind, src, id, request) {
2090
2074
  const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
2091
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}`);
2092
2076
  }
2077
+ const MAX_ADVANCE_STEPS = 1e3;
2093
2078
  async function explore(machine, options, stopWhen) {
2094
2079
  const maxDepth = options.maxDepth ?? 8;
2095
2080
  const maxPaths = options.maxPaths ?? 200;
@@ -2114,7 +2099,7 @@ async function explore(machine, options, stopWhen) {
2114
2099
  if (stopWhen?.(initial.snapshot)) witness = [];
2115
2100
  const advance = (step) => {
2116
2101
  let current = step;
2117
- for (let i = 0; i < 1e3; i++) {
2102
+ for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
2118
2103
  if (current.done) return { step: current };
2119
2104
  const request = current.requests[0];
2120
2105
  if (request && request.kind === "text") {
@@ -2266,19 +2251,22 @@ async function canReach(machine, statePath, options = {}) {
2266
2251
  } : { canReach: false };
2267
2252
  }
2268
2253
  //#endregion
2269
- //#region src/trajectory.ts
2270
- function isRecord$2(value) {
2254
+ //#region src/internal/is-record.ts
2255
+ /** True for a plain object-like value (arrays excluded). @internal */
2256
+ function isRecord(value) {
2271
2257
  return typeof value === "object" && value !== null && !Array.isArray(value);
2272
2258
  }
2259
+ //#endregion
2260
+ //#region src/trajectory.ts
2273
2261
  /**
2274
2262
  * Classifies one item. Event-shaped things (log entries, events) become events;
2275
2263
  * everything else is a state value. A snapshot is unwrapped to its `value`.
2276
2264
  * @internal
2277
2265
  */
2278
2266
  function normalize(item) {
2279
- if (isRecord$2(item)) {
2267
+ if (isRecord(item)) {
2280
2268
  const nested = item["event"];
2281
- if (isRecord$2(nested) && typeof nested["type"] === "string") return {
2269
+ if (isRecord(nested) && typeof nested["type"] === "string") return {
2282
2270
  kind: "event",
2283
2271
  event: nested
2284
2272
  };
@@ -2299,7 +2287,7 @@ function normalize(item) {
2299
2287
  /** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
2300
2288
  function statePaths(value) {
2301
2289
  if (typeof value === "string") return [value];
2302
- if (!isRecord$2(value)) return [];
2290
+ if (!isRecord(value)) return [];
2303
2291
  return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
2304
2292
  }
2305
2293
  /**
@@ -2312,12 +2300,13 @@ function stateMatchesPath(path, value) {
2312
2300
  function deepEqual(a, b) {
2313
2301
  if (Object.is(a, b)) return true;
2314
2302
  if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
2315
- if (!isRecord$2(a) || !isRecord$2(b)) return false;
2303
+ if (!isRecord(a) || !isRecord(b)) return false;
2316
2304
  const keys = Object.keys(a);
2317
2305
  return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
2318
2306
  }
2319
2307
  /**
2320
- * Does one actual item satisfy one expected item?
2308
+ * Normalizes ONE expected item into a predicate over actual items, so the
2309
+ * expectation is classified once instead of once per candidate:
2321
2310
  *
2322
2311
  * - A string expectation matches an event's `type`, or a state value it names.
2323
2312
  * - An event expectation matches an event with the same `type` whose other
@@ -2325,13 +2314,22 @@ function deepEqual(a, b) {
2325
2314
  * - A state-value expectation matches structurally.
2326
2315
  * @internal
2327
2316
  */
2328
- function matchesItem(expected, actual) {
2329
- const actualItem = normalize(actual);
2330
- if (typeof expected === "string") return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
2317
+ function matcherFor(expected) {
2318
+ if (typeof expected === "string") return (actual) => {
2319
+ const actualItem = normalize(actual);
2320
+ return actualItem.kind === "event" ? actualItem.event.type === expected : stateMatchesPath(expected, actualItem.value);
2321
+ };
2331
2322
  const expectedItem = normalize(expected);
2332
- if (expectedItem.kind !== actualItem.kind) return false;
2333
- if (expectedItem.kind === "event" && actualItem.kind === "event") return Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
2334
- return deepEqual(expectedItem.value, actualItem.value);
2323
+ switch (expectedItem.kind) {
2324
+ case "event": return (actual) => {
2325
+ const actualItem = normalize(actual);
2326
+ return actualItem.kind === "event" && Object.keys(expectedItem.event).every((key) => deepEqual(expectedItem.event[key], actualItem.event[key]));
2327
+ };
2328
+ case "state": return (actual) => {
2329
+ const actualItem = normalize(actual);
2330
+ return actualItem.kind === "state" && deepEqual(expectedItem.value, actualItem.value);
2331
+ };
2332
+ }
2335
2333
  }
2336
2334
  /**
2337
2335
  * Matches a run's trajectory against an expected one.
@@ -2368,59 +2366,41 @@ function matchesItem(expected, actual) {
2368
2366
  */
2369
2367
  function matchesTrajectory(actual, expected, options = {}) {
2370
2368
  const expectedCount = expected.length;
2371
- if (options.exact) {
2372
- let matchedCount = 0;
2373
- let firstMiss;
2374
- for (let index = 0; index < expectedCount; index++) {
2375
- if (index < actual.length && matchesItem(expected[index], actual[index])) {
2376
- matchedCount++;
2377
- continue;
2378
- }
2379
- firstMiss = {
2380
- index,
2381
- expected: expected[index],
2382
- searchedFrom: index
2383
- };
2384
- break;
2385
- }
2386
- const matched = !firstMiss && actual.length === expectedCount;
2387
- const denominator = Math.max(expectedCount, actual.length);
2388
- return {
2389
- matched,
2390
- matchedCount,
2391
- expectedCount,
2392
- score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
2393
- ...firstMiss ? { firstMiss } : {}
2394
- };
2395
- }
2369
+ const exact = options.exact === true;
2396
2370
  let cursor = 0;
2397
2371
  let matchedCount = 0;
2372
+ let firstMiss;
2398
2373
  for (let index = 0; index < expectedCount; index++) {
2399
2374
  const want = expected[index];
2375
+ const matches = matcherFor(want);
2376
+ const searchedFrom = exact ? index : cursor;
2400
2377
  let found = -1;
2401
- for (let at = cursor; at < actual.length; at++) if (matchesItem(want, actual[at])) {
2402
- found = at;
2403
- break;
2378
+ for (let at = searchedFrom; at < actual.length; at++) {
2379
+ if (matches(actual[at])) {
2380
+ found = at;
2381
+ break;
2382
+ }
2383
+ if (exact) break;
2404
2384
  }
2405
- if (found === -1) return {
2406
- matched: false,
2407
- matchedCount,
2408
- expectedCount,
2409
- score: matchedCount / expectedCount,
2410
- firstMiss: {
2385
+ if (found === -1) {
2386
+ firstMiss = {
2411
2387
  index,
2412
2388
  expected: want,
2413
- searchedFrom: cursor
2414
- }
2415
- };
2389
+ searchedFrom
2390
+ };
2391
+ break;
2392
+ }
2416
2393
  matchedCount++;
2417
2394
  cursor = found + 1;
2418
2395
  }
2396
+ const matched = !firstMiss && (!exact || actual.length === expectedCount);
2397
+ const denominator = exact ? Math.max(expectedCount, actual.length) : expectedCount;
2419
2398
  return {
2420
- matched: true,
2399
+ matched,
2421
2400
  matchedCount,
2422
2401
  expectedCount,
2423
- score: 1
2402
+ score: matched ? 1 : denominator === 0 ? 1 : matchedCount / denominator,
2403
+ ...firstMiss ? { firstMiss } : {}
2424
2404
  };
2425
2405
  }
2426
2406
  //#endregion
@@ -2433,17 +2413,9 @@ function matchesTrajectory(actual, expected, options = {}) {
2433
2413
  * `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
2434
2414
  * network. It is the fastest way to see a machine run, and the least ceremonial
2435
2415
  * way to test one: same machine, same executor contract, scripted answers.
2416
+ *
2417
+ * @module
2436
2418
  */
2437
- /** Thrown when a scripted queue runs dry on a pending request. */
2438
- var ScriptedExecutorsError = class extends require_errors.AgentError {
2439
- constructor(message) {
2440
- super("scripted-executors-exhausted", message);
2441
- this.name = "ScriptedExecutorsError";
2442
- }
2443
- };
2444
- function isRecord$1(value) {
2445
- return typeof value === "object" && value !== null && !Array.isArray(value);
2446
- }
2447
2419
  /** The only own keys an executor-result envelope carries. @internal */
2448
2420
  const TEXT_ENVELOPE_KEYS = new Set([
2449
2421
  "output",
@@ -2468,7 +2440,22 @@ function isTextEnvelope(value) {
2468
2440
  */
2469
2441
  async function resolveScriptedTextEntry(entry, request, info) {
2470
2442
  const value = typeof entry === "function" ? await entry(request, info) : entry;
2471
- return isRecord$1(value) && isTextEnvelope(value) ? value : { output: value };
2443
+ return isRecord(value) && isTextEnvelope(value) ? value : { output: value };
2444
+ }
2445
+ /**
2446
+ * Names a pending text request in an error message. Shared with `runSeam`.
2447
+ * @internal
2448
+ */
2449
+ function describeText(request) {
2450
+ return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
2451
+ }
2452
+ /**
2453
+ * Stream semantics with no model: the whole text lands as one chunk. Shared
2454
+ * with `runSeam`, whose scripted answers stream the same way. @internal
2455
+ */
2456
+ function emitScriptedChunk(result, info) {
2457
+ const output = isRecord(result) ? result["output"] : void 0;
2458
+ if (typeof output === "string") info?.onChunk?.(output);
2472
2459
  }
2473
2460
  /**
2474
2461
  * Keyless executors that replay a script instead of calling a model. Every
@@ -2503,45 +2490,26 @@ function createScriptedExecutors(script = {}) {
2503
2490
  const decisions = [...script.decisions ?? []];
2504
2491
  const text = [...script.text ?? []];
2505
2492
  const nextText = async (request, info) => {
2506
- if (text.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
2493
+ 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.`);
2507
2494
  return resolveScriptedTextEntry(text.shift(), request, info);
2508
2495
  };
2509
2496
  return {
2510
2497
  generateText: nextText,
2511
2498
  streamText: async (request, info) => {
2512
2499
  const result = await nextText(request, info);
2513
- if (typeof result.output === "string") info?.onChunk?.(result.output);
2500
+ emitScriptedChunk(result, info);
2514
2501
  return result;
2515
2502
  },
2516
2503
  decide: async (request) => {
2517
- if (decisions.length === 0) throw new ScriptedExecutorsError(`createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
2504
+ if (decisions.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
2518
2505
  const entry = decisions.shift();
2519
2506
  const value = typeof entry === "function" ? await entry(request) : entry;
2520
- return typeof value.type === "string" ? { event: value } : value;
2507
+ return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
2521
2508
  }
2522
2509
  };
2523
2510
  }
2524
- function describeText(request) {
2525
- return request.name ? `'${request.name}'` : `(model '${request.model}')`;
2526
- }
2527
2511
  //#endregion
2528
2512
  //#region src/seam.ts
2529
- /** Thrown when the call plan runs dry on a request that is not the live seam. */
2530
- var SeamScriptError = class extends require_errors.AgentError {
2531
- constructor(message) {
2532
- super("seam-script-exhausted", message);
2533
- this.name = "SeamScriptError";
2534
- }
2535
- };
2536
- function seamKeyOf(seam) {
2537
- return seam.request ?? seam.model;
2538
- }
2539
- function describeRequest(request) {
2540
- return request.name ? `'${request.name}' (model '${request.model}')` : `(model '${request.model}')`;
2541
- }
2542
- function isRecord(value) {
2543
- return typeof value === "object" && value !== null;
2544
- }
2545
2513
  /**
2546
2514
  * The seam's own answer, for scoring. Our `{ output }` envelope is read
2547
2515
  * directly; a raw AI SDK `generateText` result contributes its `text`. A raw
@@ -2596,40 +2564,50 @@ async function runSeam(machine, options) {
2596
2564
  let seamStateAt = 0;
2597
2565
  let seamEventAt = 0;
2598
2566
  let liveEvents = 0;
2599
- const scriptedAnswer = async (request, info, optional) => {
2600
- const key = request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
2601
- const queue = queues.get(key);
2602
- if (!queue?.length) {
2603
- if (optional) return;
2604
- throw new SeamScriptError(`runSeam: no scripted answer left for request ${describeRequest(request)}. Add an entry to \`scripts.${key}\` — its last entry repeats, so one extra answer covers a longer branch.`);
2605
- }
2567
+ const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
2568
+ /**
2569
+ * 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.
2572
+ */
2573
+ const takeScriptedSlot = async (request, info) => {
2574
+ const queue = queues.get(queueKeyOf(request));
2575
+ if (!queue?.length) return;
2606
2576
  return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
2607
2577
  };
2578
+ const scriptedAnswer = async (request, info) => {
2579
+ 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.`);
2581
+ return scripted;
2582
+ };
2608
2583
  const route = async (request, info) => {
2609
2584
  const callIndex = calls++;
2610
2585
  const isSeam = (seam.request !== void 0 ? request.name === seam.request : request.model === seam.model) && seamMatches++ === (seam.occurrence ?? 0);
2611
- const scripted = await scriptedAnswer(request, info, isSeam && candidate !== void 0);
2586
+ if (isSeam && candidate) {
2587
+ await takeScriptedSlot(request, info);
2588
+ seamReached = true;
2589
+ callsBeforeSeam = callIndex;
2590
+ seamStateAt = statePath.length;
2591
+ seamEventAt = liveEvents;
2592
+ const result = await candidate(request, info);
2593
+ seamOutput = await seamOutputOf(result, request);
2594
+ return result;
2595
+ }
2596
+ const scripted = await scriptedAnswer(request, info);
2612
2597
  if (!isSeam) return scripted;
2613
2598
  seamReached = true;
2614
2599
  callsBeforeSeam = callIndex;
2615
2600
  seamStateAt = statePath.length;
2616
2601
  seamEventAt = liveEvents;
2617
- if (!candidate) {
2618
- if (!scripted) throw new SeamScriptError(`runSeam: the seam '${seamKeyOf(seam)}' has no candidate and no scripted answer. Pass \`candidate\`, or script the seam's key.`);
2619
- seamOutput = scripted.output;
2620
- return scripted;
2621
- }
2622
- const result = await candidate(request, info);
2623
- seamOutput = await seamOutputOf(result, request);
2624
- return result;
2602
+ seamOutput = scripted.output;
2603
+ return scripted;
2625
2604
  };
2626
2605
  const executors = {
2627
2606
  ...options.executors,
2628
2607
  generateText: route,
2629
2608
  streamText: async (request, info) => {
2630
2609
  const result = await route(request, info);
2631
- const output = isRecord(result) ? result["output"] : void 0;
2632
- if (typeof output === "string") info?.onChunk?.(output);
2610
+ emitScriptedChunk(result, info);
2633
2611
  return result;
2634
2612
  }
2635
2613
  };
@@ -2733,7 +2711,7 @@ exports.getAcceptedEvents = require_decision.getAcceptedEvents;
2733
2711
  exports.getAgentEffects = require_setup_agent.getAgentEffects;
2734
2712
  exports.getAgentMessages = require_decision.getAgentMessages;
2735
2713
  exports.getAgentOutputMode = require_decision.getAgentOutputMode;
2736
- exports.getCallUsage = require_setup_agent.getCallUsage;
2714
+ exports.getCallUsage = require_decision.getCallUsage;
2737
2715
  exports.getJsonSchema = require_decision.getJsonSchema;
2738
2716
  exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
2739
2717
  exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;