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

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