@statelyai/agent 2.0.0-alpha.19 → 2.0.0-alpha.21

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.
@@ -168,7 +168,7 @@ function toolMessage(content) {
168
168
  * ```
169
169
  */
170
170
  function getStateMeta(snapshot) {
171
- const nodes = snapshot._nodes;
171
+ const nodes = snapshot.nodes;
172
172
  const depthById = new Map(nodes?.map((node) => [node.id, node.path.length]));
173
173
  const depth = (id) => depthById.get(id) ?? id.split(".").length;
174
174
  const entries = Object.entries(snapshot.getMeta()).filter((entry) => entry[1] != null).sort(([a], [b]) => depth(a) - depth(b) || (a < b ? -1 : a > b ? 1 : 0));
@@ -424,7 +424,7 @@ function createBuiltinTextActor(src, mode, outputSchema) {
424
424
  messages: ({ input }) => input.messages,
425
425
  tools: ({ input }) => input.tools,
426
426
  toolChoice: ({ input }) => input.toolChoice,
427
- reasoning: ({ input }) => input.reasoning,
427
+ includeReasoning: ({ input }) => input.includeReasoning,
428
428
  temperature: ({ input }) => input.temperature,
429
429
  maxOutputTokens: ({ input }) => input.maxOutputTokens,
430
430
  topP: ({ input }) => input.topP,
@@ -496,7 +496,7 @@ function createTextLogic(config, execute) {
496
496
  tools: resolveTextLogicValue(config.tools, args),
497
497
  toolChoice: resolveTextLogicValue(config.toolChoice, args),
498
498
  outputSchema: schemas.output,
499
- reasoning: resolveTextLogicValue(config.reasoning, args),
499
+ includeReasoning: resolveTextLogicValue(config.includeReasoning, args),
500
500
  temperature: resolveTextLogicValue(config.temperature, args),
501
501
  maxOutputTokens: resolveTextLogicValue(config.maxOutputTokens, args),
502
502
  topP: resolveTextLogicValue(config.topP, args),
@@ -643,7 +643,7 @@ function buildEnvelopeSchema(inner, options = {}) {
643
643
  */
644
644
  function parseStructuredEnvelope(request, value) {
645
645
  if (!request.outputSchema) throw new Error("parseStructuredEnvelope: the request declares no outputSchema.");
646
- return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.reasoning }), value);
646
+ return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.includeReasoning }), value);
647
647
  }
648
648
  /**
649
649
  * Merges request-declared and call-site `tools`, dispatches to the
@@ -1048,12 +1048,24 @@ Object.defineProperty(exports, "DECIDE_ACTOR", {
1048
1048
  return DECIDE_ACTOR;
1049
1049
  }
1050
1050
  });
1051
+ Object.defineProperty(exports, "GENERATE_TEXT_ACTOR", {
1052
+ enumerable: true,
1053
+ get: function() {
1054
+ return GENERATE_TEXT_ACTOR;
1055
+ }
1056
+ });
1051
1057
  Object.defineProperty(exports, "INTERPRET_SOURCE", {
1052
1058
  enumerable: true,
1053
1059
  get: function() {
1054
1060
  return INTERPRET_SOURCE;
1055
1061
  }
1056
1062
  });
1063
+ Object.defineProperty(exports, "STREAM_TEXT_ACTOR", {
1064
+ enumerable: true,
1065
+ get: function() {
1066
+ return STREAM_TEXT_ACTOR;
1067
+ }
1068
+ });
1057
1069
  Object.defineProperty(exports, "USER_INPUT_ACTOR", {
1058
1070
  enumerable: true,
1059
1071
  get: function() {
@@ -167,7 +167,7 @@ function toolMessage(content) {
167
167
  * ```
168
168
  */
169
169
  function getStateMeta(snapshot) {
170
- const nodes = snapshot._nodes;
170
+ const nodes = snapshot.nodes;
171
171
  const depthById = new Map(nodes?.map((node) => [node.id, node.path.length]));
172
172
  const depth = (id) => depthById.get(id) ?? id.split(".").length;
173
173
  const entries = Object.entries(snapshot.getMeta()).filter((entry) => entry[1] != null).sort(([a], [b]) => depth(a) - depth(b) || (a < b ? -1 : a > b ? 1 : 0));
@@ -423,7 +423,7 @@ function createBuiltinTextActor(src, mode, outputSchema) {
423
423
  messages: ({ input }) => input.messages,
424
424
  tools: ({ input }) => input.tools,
425
425
  toolChoice: ({ input }) => input.toolChoice,
426
- reasoning: ({ input }) => input.reasoning,
426
+ includeReasoning: ({ input }) => input.includeReasoning,
427
427
  temperature: ({ input }) => input.temperature,
428
428
  maxOutputTokens: ({ input }) => input.maxOutputTokens,
429
429
  topP: ({ input }) => input.topP,
@@ -495,7 +495,7 @@ function createTextLogic(config, execute) {
495
495
  tools: resolveTextLogicValue(config.tools, args),
496
496
  toolChoice: resolveTextLogicValue(config.toolChoice, args),
497
497
  outputSchema: schemas.output,
498
- reasoning: resolveTextLogicValue(config.reasoning, args),
498
+ includeReasoning: resolveTextLogicValue(config.includeReasoning, args),
499
499
  temperature: resolveTextLogicValue(config.temperature, args),
500
500
  maxOutputTokens: resolveTextLogicValue(config.maxOutputTokens, args),
501
501
  topP: resolveTextLogicValue(config.topP, args),
@@ -642,7 +642,7 @@ function buildEnvelopeSchema(inner, options = {}) {
642
642
  */
643
643
  function parseStructuredEnvelope(request, value) {
644
644
  if (!request.outputSchema) throw new Error("parseStructuredEnvelope: the request declares no outputSchema.");
645
- return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.reasoning }), value);
645
+ return validateSchemaSync(buildEnvelopeSchema(request.outputSchema, { reasoning: request.includeReasoning }), value);
646
646
  }
647
647
  /**
648
648
  * Merges request-declared and call-site `tools`, dispatches to the
@@ -1029,4 +1029,4 @@ async function resolveDecision(request, executors, options = {}) {
1029
1029
  throw new AgentDecisionExhaustedError(attempts);
1030
1030
  }
1031
1031
  //#endregion
1032
- export { isUnboundPlaceholder as A, getMachineStructuralHash as B, parseStructuredEnvelope as C, getMachineIdlePredicate as D, executorBoundLogics as E, djb2Hex as F, toolMessage as G, isStandardSchema as H, findNonSerializableContextPaths as I, userMessage as K, getAgentMessages as L, machineStaticTransitionTargets as M, missingActor as N, getMachineStaticTransitionTargets as O, assistantMessage as P, getJsonSchema as R, parseOutput as S, agentExecutionOptions as T, resolveMachineVersion as U, getStateMeta as V, systemMessage as W, getAgentOutputMode as _, resolveDecision as a, normalizeGeneratorResult as b, AGENT_USAGE_TOKEN_FIELDS as c, USER_INPUT_ACTOR as d, bindRequestExecutor as f, executeAgentTextRequest as g, createTextLogic as h, renderDecisionAttempts as i, machineIdlePredicates as j, getRegisteredAgentExecutionOptions as k, DECIDE_ACTOR as l, builtinTextActors as m, createDecideActor as n, getAcceptedEvents as o, buildEnvelopeSchema as p, validateSchemaSync as q, isDecisionLogic as r, parseAgentEvent as s, AgentDecisionExhaustedError as t, INTERPRET_SOURCE as u, getCallUsage as v, userInputActor as w, parseModelRef as x, isTextLogic as y, getJsonSchemaSync as z };
1032
+ export { getMachineStaticTransitionTargets as A, getJsonSchema as B, parseModelRef as C, agentExecutionOptions as D, userInputActor as E, missingActor as F, resolveMachineVersion as G, getMachineStructuralHash as H, assistantMessage as I, userMessage as J, systemMessage as K, djb2Hex as L, isUnboundPlaceholder as M, machineIdlePredicates as N, executorBoundLogics as O, machineStaticTransitionTargets as P, findNonSerializableContextPaths as R, normalizeGeneratorResult as S, parseStructuredEnvelope as T, getStateMeta as U, getJsonSchemaSync as V, isStandardSchema as W, validateSchemaSync as Y, createTextLogic as _, resolveDecision as a, getCallUsage as b, AGENT_USAGE_TOKEN_FIELDS as c, INTERPRET_SOURCE as d, STREAM_TEXT_ACTOR as f, builtinTextActors as g, buildEnvelopeSchema as h, renderDecisionAttempts as i, getRegisteredAgentExecutionOptions as j, getMachineIdlePredicate as k, DECIDE_ACTOR as l, bindRequestExecutor as m, createDecideActor as n, getAcceptedEvents as o, USER_INPUT_ACTOR as p, toolMessage as q, isDecisionLogic as r, parseAgentEvent as s, AgentDecisionExhaustedError as t, GENERATE_TEXT_ACTOR as u, executeAgentTextRequest as v, parseOutput as w, isTextLogic as x, getAgentOutputMode as y, getAgentMessages as z };
package/dist/index.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("./errors-DUBBzRLP.cjs");
3
- const require_setup_agent = require("./setup-agent-BFA4VKpN.cjs");
4
- const require_decision = require("./decision-t26zsnSR.cjs");
3
+ const require_setup_agent = require("./setup-agent-91FSuZbB.cjs");
4
+ const require_decision = require("./decision-BnTCsuJv.cjs");
5
5
  const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
6
6
  require("./validate.cjs");
7
7
  let xstate = require("xstate");
8
+ let xstate_durable = require("xstate/durable");
8
9
  //#region src/internal/state-request-pass.ts
9
10
  async function runTextPhase(stateRequest, baseMessages, deps) {
10
11
  const { model, system } = stateRequest;
@@ -286,7 +287,7 @@ function serializeTraceEvent(event, options = {}) {
286
287
  return out;
287
288
  }
288
289
  function snapshotNodes(snapshot) {
289
- return (snapshot._nodes ?? []).map((raw) => {
290
+ return (snapshot.nodes ?? []).map((raw) => {
290
291
  const node = raw;
291
292
  return {
292
293
  id: node.id ?? "",
@@ -569,9 +570,13 @@ function invokingActorOf(self, runCtx) {
569
570
  * identical event shapes by construction. @internal
570
571
  */
571
572
  function bindTextLogic(logic, runCtx) {
572
- return logic.withExecutor(async ({ request, self: selfArg, signal }) => {
573
+ return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
573
574
  const self = selfArg;
574
575
  const { id, src } = selfIdAndSrc(self);
576
+ const request = rawRequest.name === void 0 && src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? {
577
+ ...rawRequest,
578
+ name: src
579
+ } : rawRequest;
575
580
  const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
576
581
  if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
577
582
  const requestWithTools = {
@@ -1161,6 +1166,7 @@ function createAgentSession(machine, options, lifecycle) {
1161
1166
  const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
1162
1167
  if (machineOwnVersion === void 0) delete aligned.version;
1163
1168
  else aligned.version = machineOwnVersion;
1169
+ delete aligned.machine;
1164
1170
  effectiveSnapshot = aligned;
1165
1171
  }
1166
1172
  const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
@@ -2084,8 +2090,8 @@ function lintAgentMachine(machine, options = {}) {
2084
2090
  return diagnostics;
2085
2091
  }
2086
2092
  /**
2087
- * Thrown by `lintAgentMachine(machine, { throw: true })` when lint finds
2088
- * failing diagnostics.
2093
+ * Thrown by `lintAgentMachine(machine, { throw: true })` (and its
2094
+ * {@link assertAgentMachine} alias) when lint finds failing diagnostics.
2089
2095
  * `diagnostics` holds the findings; the message lists them one per finding,
2090
2096
  * so a test runner's failure output reads like the CLI's lint report.
2091
2097
  */
@@ -2098,6 +2104,26 @@ var AgentLintError = class extends require_errors.AgentError {
2098
2104
  this.diagnostics = diagnostics;
2099
2105
  }
2100
2106
  };
2107
+ /**
2108
+ * Asserts a machine passes {@link lintAgentMachine}: returns silently when
2109
+ * clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
2110
+ * otherwise — sugar for `lintAgentMachine(machine, { ...options, throw: true })`.
2111
+ * Fails on error-severity findings; set `warnings: true` to fail on warnings
2112
+ * too. The one-liner for tests and generation loops:
2113
+ *
2114
+ * @example
2115
+ * ```ts
2116
+ * test('agent machine is structurally sound', () => {
2117
+ * assertAgentMachine(machine);
2118
+ * });
2119
+ * ```
2120
+ */
2121
+ function assertAgentMachine(machine, options = {}) {
2122
+ lintAgentMachine(machine, {
2123
+ ...options,
2124
+ throw: true
2125
+ });
2126
+ }
2101
2127
  function pendingInvokes(step) {
2102
2128
  const out = [];
2103
2129
  for (const action of step.actions) {
@@ -2124,6 +2150,21 @@ function takeFromQueue(channel, src) {
2124
2150
  * the real transition logic. Returns the terminal `status`, final `snapshot`,
2125
2151
  * and a `trail` of every step taken.
2126
2152
  *
2153
+ * Decisions run through the live run's own validation/retry core,
2154
+ * {@link resolveDecision}, with the script standing in for the model: each
2155
+ * attempt consumes the next queued {@link ChosenEvent} for that src, so an
2156
+ * unknown, payload-invalid, or guard-rejected event is NOT silently swallowed
2157
+ * — the next queued decision is tried, exactly as a live run re-asks the
2158
+ * model. The decision logic's `maxRetries` caps attempts as it would live;
2159
+ * when retries continue past the end of the queue, the last queued decision
2160
+ * repeats (a scripted model that insists). The repeat applies only within one
2161
+ * decision request's retries — each new decision request must have its own
2162
+ * queued entry, or the dry-script error throws as usual. Exhausting all
2163
+ * attempts delivers
2164
+ * the resulting {@link AgentDecisionExhaustedError} to the machine as the
2165
+ * decision invoke's error (so an `onError` transition observes it, as it
2166
+ * would live); with no `onError` to catch it, it is thrown.
2167
+ *
2127
2168
  * Throws a descriptive error when the script runs dry mid-request, naming the
2128
2169
  * pending request's kind, src, and id so the missing scripted response is
2129
2170
  * obvious.
@@ -2132,7 +2173,10 @@ function takeFromQueue(channel, src) {
2132
2173
  * ```ts
2133
2174
  * const { status, snapshot } = simulateAgent(machine, {
2134
2175
  * input: { topic: 'state machines' },
2135
- * script: { decisions: { 'agent.decide': [{ type: 'END' }] } },
2176
+ * script: {
2177
+ * decisions: { 'agent.decide': [{ type: 'ESCALATE' }] },
2178
+ * events: [{ type: 'APPROVE' }], // crosses the human gate
2179
+ * },
2136
2180
  * });
2137
2181
  * ```
2138
2182
  */
@@ -2141,11 +2185,12 @@ async function simulateAgent(machine, options) {
2141
2185
  const script = {
2142
2186
  text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
2143
2187
  decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
2144
- invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
2188
+ invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr]),
2189
+ events: [...options.script.events ?? []]
2145
2190
  };
2146
2191
  if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
2147
2192
  let step = require_setup_agent.initialAgentStep(machine, options.input);
2148
- const trail = [];
2193
+ const trail = [{ state: step.snapshot.value }];
2149
2194
  for (let i = 0; i < maxSteps; i++) {
2150
2195
  if (step.done) return {
2151
2196
  status: "done",
@@ -2155,14 +2200,10 @@ async function simulateAgent(machine, options) {
2155
2200
  const request = step.requests[0];
2156
2201
  if (request) {
2157
2202
  if (request.kind === "decision") {
2158
- const decisionSrc = new Map(pendingInvokes(step).map((invoke) => [invoke.id, invoke.src])).get(request.id) ?? request.id;
2159
- const taken = takeFromQueue(script.decisions, decisionSrc);
2160
- if (!taken.found) throw scriptDryError("decision", decisionSrc, request.id, request);
2161
- step = require_setup_agent.transitionAgentStep(machine, step, taken.value);
2162
- trail.push({
2163
- state: step.snapshot.value,
2164
- appliedEvent: taken.value
2165
- });
2203
+ const invokeMeta = findInvokeMetadata(step, request.id);
2204
+ const src = invokeMeta?.src ?? request.id;
2205
+ const decisionSrc = src in (script.decisions ?? {}) ? src : request.id in (script.decisions ?? {}) ? request.id : src;
2206
+ step = await applyScriptedDecision(machine, step, request, decisionSrc, invokeMeta?.logic, script, trail);
2166
2207
  continue;
2167
2208
  }
2168
2209
  const taken = takeFromQueue(script.text, request.src);
@@ -2193,6 +2234,18 @@ async function simulateAgent(machine, options) {
2193
2234
  });
2194
2235
  continue;
2195
2236
  }
2237
+ const external = script.events;
2238
+ if (external.length > 0) {
2239
+ const event = external.shift();
2240
+ if (!step.snapshot.can(event)) throw new Error(`simulateAgent: scripted external event '${event.type}' cannot be taken in state ${JSON.stringify(step.snapshot.value)} (no handler, or its guard rejected it). A live run's send would be silently dropped here; fix the script's \`events\` queue.`);
2241
+ step = require_setup_agent.transitionAgentStep(machine, step, event);
2242
+ trail.push({
2243
+ state: step.snapshot.value,
2244
+ appliedEvent: event,
2245
+ external: true
2246
+ });
2247
+ continue;
2248
+ }
2196
2249
  return {
2197
2250
  status: "idle",
2198
2251
  snapshot: step.snapshot,
@@ -2205,6 +2258,66 @@ async function simulateAgent(machine, options) {
2205
2258
  trail
2206
2259
  };
2207
2260
  }
2261
+ async function applyScriptedDecision(machine, step, request, decisionSrc, invokeLogic, script, trail) {
2262
+ const dequeued = [];
2263
+ const decide = async () => {
2264
+ const taken = takeFromQueue(script.decisions, decisionSrc);
2265
+ if (taken.found) {
2266
+ dequeued.push(taken.value);
2267
+ return { event: taken.value };
2268
+ }
2269
+ if (dequeued.length === 0) throw scriptDryError("decision", decisionSrc, request.id, request);
2270
+ return { event: dequeued[dequeued.length - 1] };
2271
+ };
2272
+ const maxRetries = (require_decision.isDecisionLogic(invokeLogic) ? invokeLogic : resolveRegisteredDecisionLogic(machine, decisionSrc))?.maxRetries;
2273
+ let chosen;
2274
+ try {
2275
+ chosen = await require_decision.resolveDecision(request, { decide }, {
2276
+ maxRetries,
2277
+ canTake: (event) => step.snapshot.can(event)
2278
+ });
2279
+ } catch (error) {
2280
+ if (!(error instanceof require_decision.AgentDecisionExhaustedError)) throw error;
2281
+ const next = require_setup_agent.transitionAgentStep(machine, step, {
2282
+ type: "xstate.error.actor",
2283
+ actorId: request.id,
2284
+ ...sessionIdOf(step.snapshot, request.id),
2285
+ error
2286
+ });
2287
+ if (next.snapshot.status === "error") throw error;
2288
+ trail.push({
2289
+ state: next.snapshot.value,
2290
+ rejectedEvents: dequeued
2291
+ });
2292
+ return next;
2293
+ }
2294
+ const next = require_setup_agent.transitionAgentStep(machine, step, chosen);
2295
+ const rejected = dequeued.slice(0, -1);
2296
+ trail.push({
2297
+ state: next.snapshot.value,
2298
+ appliedEvent: chosen,
2299
+ ...rejected.length > 0 ? { rejectedEvents: rejected } : {}
2300
+ });
2301
+ return next;
2302
+ }
2303
+ function findInvokeMetadata(step, id) {
2304
+ for (const action of step.actions) {
2305
+ const metadata = require_setup_agent.getInvokeEffectMetadata(action);
2306
+ if (metadata?.id !== id) continue;
2307
+ return {
2308
+ ...typeof metadata.src === "string" ? { src: metadata.src } : {},
2309
+ logic: metadata.logic ?? (typeof metadata.src === "object" ? metadata.src : void 0)
2310
+ };
2311
+ }
2312
+ }
2313
+ function resolveRegisteredDecisionLogic(machine, src) {
2314
+ const candidate = require_decision.getRegisteredAgentExecutionOptions(machine).actors?.[src] ?? machine.sources?.actors?.[src];
2315
+ return require_decision.isDecisionLogic(candidate) ? candidate : void 0;
2316
+ }
2317
+ function sessionIdOf(snapshot, id) {
2318
+ const child = snapshot.children[id];
2319
+ return typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {};
2320
+ }
2208
2321
  function mapValues(obj, fn) {
2209
2322
  return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
2210
2323
  }
@@ -2241,6 +2354,10 @@ async function explore(machine, options, stopWhen) {
2241
2354
  const advance = (step) => {
2242
2355
  let current = step;
2243
2356
  for (let i = 0; i < MAX_ADVANCE_STEPS; i++) {
2357
+ if (stopWhen?.(current.snapshot)) return {
2358
+ step: current,
2359
+ hit: true
2360
+ };
2244
2361
  if (current.done) return { step: current };
2245
2362
  const request = current.requests[0];
2246
2363
  if (request && request.kind === "text") {
@@ -2273,8 +2390,8 @@ async function explore(machine, options, stopWhen) {
2273
2390
  hitPathCap = true;
2274
2391
  return;
2275
2392
  }
2276
- const { step: settled, blockedSrc } = advance(step);
2277
- if (stopWhen?.(settled.snapshot)) {
2393
+ const { step: settled, blockedSrc, hit } = advance(step);
2394
+ if (hit) {
2278
2395
  witness = path;
2279
2396
  return;
2280
2397
  }
@@ -2369,8 +2486,11 @@ async function explorePaths(machine, options = {}) {
2369
2486
  return (await explore(machine, options)).report;
2370
2487
  }
2371
2488
  /**
2372
- * Answers "can the machine reach `statePath`?" by exploring its branches (a
2373
- * thin wrapper over {@link explorePaths}). Returns
2489
+ * Answers "can the machine reach this?" by exploring its branches (a thin
2490
+ * wrapper over {@link explorePaths}). The target is either a state path string
2491
+ * (`snapshot.matches(...)` semantics) or a snapshot predicate — the predicate
2492
+ * form checks any property (a context invariant, a tag, a state+context
2493
+ * combination) without reifying a sentinel state for it. Returns
2374
2494
  * `{ reachable: true, witness }` with the event sequence that reaches it, or
2375
2495
  * `{ reachable: false }`.
2376
2496
  *
@@ -2379,11 +2499,21 @@ async function explorePaths(machine, options = {}) {
2379
2499
  * const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
2380
2500
  * // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
2381
2501
  * ```
2502
+ *
2503
+ * @example Predicate target — a violation property, no sentinel state needed
2504
+ * ```ts
2505
+ * const violation = await canReach(
2506
+ * refundMachine,
2507
+ * (snapshot) => snapshot.matches('issued') && !snapshot.context.approved,
2508
+ * { input: { amount: 5000 } },
2509
+ * );
2510
+ * // violation.reachable → false is the safety proof
2511
+ * ```
2382
2512
  */
2383
- async function canReach(machine, statePath, options = {}) {
2384
- const { witness } = await explore(machine, options, (snapshot) => {
2513
+ async function canReach(machine, target, options = {}) {
2514
+ const { witness } = await explore(machine, options, typeof target === "function" ? target : (snapshot) => {
2385
2515
  try {
2386
- return snapshot.matches(statePath);
2516
+ return snapshot.matches(target);
2387
2517
  } catch {
2388
2518
  return false;
2389
2519
  }
@@ -2715,6 +2845,8 @@ async function runSeam(machine, options) {
2715
2845
  let calls = 0;
2716
2846
  let seamMatches = 0;
2717
2847
  let seamOutput;
2848
+ let seamUsage;
2849
+ const callLog = [];
2718
2850
  let seamReached = false;
2719
2851
  let callsBeforeSeam = -1;
2720
2852
  let seamStateAt = 0;
@@ -2739,6 +2871,15 @@ async function runSeam(machine, options) {
2739
2871
  const route = async (request, info) => {
2740
2872
  const callIndex = calls++;
2741
2873
  const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
2874
+ const ledger = (source) => {
2875
+ callLog.push({
2876
+ ...request.name !== void 0 ? { name: request.name } : {},
2877
+ model: request.model,
2878
+ key: queueKeyOf(request),
2879
+ source,
2880
+ seam: isSeam
2881
+ });
2882
+ };
2742
2883
  if (isSeam && candidate) {
2743
2884
  await takeScriptedSlot(request, info);
2744
2885
  seamReached = true;
@@ -2747,15 +2888,19 @@ async function runSeam(machine, options) {
2747
2888
  seamEventAt = liveEvents;
2748
2889
  const result = await candidate(request, info);
2749
2890
  seamOutput = await seamOutputOf(result, request);
2891
+ seamUsage = require_decision.getCallUsage(result);
2892
+ ledger("candidate");
2750
2893
  return result;
2751
2894
  }
2752
2895
  const scripted = await scriptedAnswer(request, info);
2896
+ ledger("script");
2753
2897
  if (!isSeam) return scripted;
2754
2898
  seamReached = true;
2755
2899
  callsBeforeSeam = callIndex;
2756
2900
  seamStateAt = statePath.length;
2757
2901
  seamEventAt = liveEvents;
2758
2902
  seamOutput = scripted.output;
2903
+ seamUsage = scripted.usage;
2759
2904
  return scripted;
2760
2905
  };
2761
2906
  const executors = {
@@ -2817,7 +2962,9 @@ async function runSeam(machine, options) {
2817
2962
  return {
2818
2963
  result,
2819
2964
  seamOutput,
2965
+ ...seamUsage !== void 0 ? { seamUsage } : {},
2820
2966
  callsBeforeSeam,
2967
+ calls: callLog,
2821
2968
  before: {
2822
2969
  statePath: statePath.slice(0, splitStateAt),
2823
2970
  events: events.slice(0, splitAt)
@@ -2829,6 +2976,211 @@ async function runSeam(machine, options) {
2829
2976
  };
2830
2977
  }
2831
2978
  //#endregion
2979
+ //#region src/durable.ts
2980
+ /**
2981
+ * The durable host runner: {@link runDurableAgent} drives an executor-bound
2982
+ * agent machine on xstate's `createDurable` execution (`xstate/durable`),
2983
+ * with the agent event log as the journal.
2984
+ *
2985
+ * Where {@link replay} + `getAgentEffects` hand a host an effect list to run
2986
+ * itself, `runDurableAgent` owns the whole loop on the durable runtime:
2987
+ * invoked actors execute live through xstate's own runtime, every EXTERNAL
2988
+ * event (invoke completions included) is appended to the log, and a resume
2989
+ * folds the log back through pure transitions — an invoke whose completion is
2990
+ * already journaled is never re-started, so recorded model calls are never
2991
+ * re-executed. Crash recovery re-runs only the work that was still in flight.
2992
+ *
2993
+ * @module
2994
+ */
2995
+ const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
2996
+ const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
2997
+ function completionActorId(event) {
2998
+ if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
2999
+ const actorId = event.actorId;
3000
+ return typeof actorId === "string" ? actorId : void 0;
3001
+ }
3002
+ /** Thrown by the adapter's `waitForEvent` when nothing can produce an event. */
3003
+ const IDLE = Symbol("agent.durable.idle");
3004
+ function createMailbox() {
3005
+ const queue = [];
3006
+ const waiters = [];
3007
+ return {
3008
+ push(event) {
3009
+ const waiter = waiters.shift();
3010
+ if (waiter) waiter(event);
3011
+ else queue.push(event);
3012
+ },
3013
+ take() {
3014
+ const next = queue.shift();
3015
+ if (next !== void 0) return Promise.resolve(next);
3016
+ return new Promise((resolve) => waiters.push(resolve));
3017
+ },
3018
+ size: () => queue.length
3019
+ };
3020
+ }
3021
+ /**
3022
+ * Runs an agent machine as a durable execution: journal in, journal out.
3023
+ *
3024
+ * A fresh call starts from `input` and appends a reserved init entry; a
3025
+ * resume call folds `entries` through pure transitions first — invokes whose
3026
+ * completions are journaled are suppressed (their recorded results replay
3027
+ * instead of re-executing), while work that was in flight at the crash
3028
+ * re-executes live. After the journal, an optional `options.event` is
3029
+ * delivered. The call settles:
3030
+ *
3031
+ * - `done` when the machine reaches a final state, with `output`;
3032
+ * - `idle` when the frontier needs an external event the host has not
3033
+ * supplied (no live work pending, or `isIdle` says the pending work is a
3034
+ * human wait). Persist `entries` and call again with them later.
3035
+ *
3036
+ * ```ts
3037
+ * const first = await runDurableAgent(machine, { input, executors });
3038
+ * // ... persist first.entries; later, in a new process:
3039
+ * const next = await runDurableAgent(machine, {
3040
+ * entries: first.entries,
3041
+ * event: { type: "APPROVE" },
3042
+ * executors,
3043
+ * });
3044
+ * ```
3045
+ *
3046
+ * @experimental Built on xstate's experimental `xstate/durable` entrypoint.
3047
+ */
3048
+ async function runDurableAgent(machine, options = {}) {
3049
+ const bound = options.executors ? provideExecutors(machine, options.executors, {
3050
+ actors: options.actors,
3051
+ onChunk: options.onChunk,
3052
+ onTrace: options.onTrace
3053
+ }) : options.actors ? machine.provide({ actors: options.actors }) : machine;
3054
+ const machineId = machine.config.id ?? machine.id ?? "(machine)";
3055
+ const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
3056
+ const priorEntries = options.entries ?? [];
3057
+ if (priorEntries.length > 0) require_setup_agent.validateReplayEntries(priorEntries, {
3058
+ machineId,
3059
+ machineVersion
3060
+ }, "Durable journal entries");
3061
+ const hasInit = priorEntries[0]?.event.type === require_setup_agent.AGENT_INIT_EVENT_TYPE;
3062
+ const input = hasInit ? priorEntries[0].event.input : options.input;
3063
+ const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
3064
+ const journaledCompletions = /* @__PURE__ */ new Map();
3065
+ for (const event of journal) {
3066
+ const actorId = completionActorId(event);
3067
+ if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
3068
+ }
3069
+ const storedExecutionId = hasInit ? priorEntries[0].metadata?.executionId : void 0;
3070
+ const executionId = typeof storedExecutionId === "string" ? storedExecutionId : hasInit ? priorEntries[0].id : crypto.randomUUID();
3071
+ const mailbox = createMailbox();
3072
+ const rootAddress = machineId;
3073
+ const suppressedChildren = /* @__PURE__ */ new WeakSet();
3074
+ const startsSeen = /* @__PURE__ */ new Map();
3075
+ const liveInFlight = /* @__PURE__ */ new Set();
3076
+ const findChildRef = (effect) => {
3077
+ const raw = effect;
3078
+ const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
3079
+ for (const candidate of candidates) {
3080
+ const ref = candidate;
3081
+ if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
3082
+ }
3083
+ };
3084
+ let replaying = journal.length > 0;
3085
+ let liveEventConsumed = false;
3086
+ let latestSnapshot;
3087
+ const execution = (0, xstate_durable.createDurable)(bound, {
3088
+ executionId,
3089
+ sendEvent(source, target, event) {
3090
+ if (target.address === rootAddress) {
3091
+ mailbox.push(event);
3092
+ return;
3093
+ }
3094
+ (0, xstate.deliverEvent)(source, target, event);
3095
+ },
3096
+ runtime(_metadata, effect) {
3097
+ const type = effect.type;
3098
+ if (type === "@xstate.spawn" || type === "@xstate.start") {
3099
+ const child = findChildRef(effect);
3100
+ if (!child) return {};
3101
+ if (type === "@xstate.spawn") {
3102
+ const seen = (startsSeen.get(child.id) ?? 0) + 1;
3103
+ startsSeen.set(child.id, seen);
3104
+ if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
3105
+ else liveInFlight.add(child.id);
3106
+ }
3107
+ if (suppressedChildren.has(child)) return {
3108
+ spawnActor() {},
3109
+ startActor() {}
3110
+ };
3111
+ }
3112
+ return {};
3113
+ },
3114
+ executeAction(action) {
3115
+ if (replaying) return;
3116
+ action.exec?.();
3117
+ },
3118
+ async waitForEvent() {
3119
+ if (mailbox.size() > 0) return await mailbox.take();
3120
+ if (liveInFlight.size > 0 && !(options.isIdle?.(latestSnapshot) ?? false)) return await mailbox.take();
3121
+ if (!liveEventConsumed && options.event !== void 0) {
3122
+ liveEventConsumed = true;
3123
+ return options.event;
3124
+ }
3125
+ throw IDLE;
3126
+ }
3127
+ });
3128
+ const entries = [...priorEntries];
3129
+ const entryOptions = {
3130
+ machineVersion,
3131
+ verification: options.verification ?? false
3132
+ };
3133
+ const appendEntry = (event) => {
3134
+ const entry = require_setup_agent.createReplayEntry(machine, entries, event, entryOptions);
3135
+ entries.push(entry);
3136
+ options.onEntry?.(entry);
3137
+ };
3138
+ if (!hasInit) {
3139
+ const entry = require_setup_agent.initEntry(machine, input, {
3140
+ ...entryOptions,
3141
+ metadata: { executionId }
3142
+ });
3143
+ entries.push(entry);
3144
+ options.onEntry?.(entry);
3145
+ }
3146
+ let journalIndex = 0;
3147
+ let [snapshot, effects] = execution.initialTransition(input);
3148
+ latestSnapshot = snapshot;
3149
+ for (;;) {
3150
+ await execution.executeEffects(effects);
3151
+ const machineSnapshot = snapshot;
3152
+ if (machineSnapshot.status === "done") return {
3153
+ status: "done",
3154
+ output: machineSnapshot.output,
3155
+ snapshot,
3156
+ entries
3157
+ };
3158
+ if (machineSnapshot.status === "error") throw machineSnapshot.error;
3159
+ let event;
3160
+ let fromJournal = false;
3161
+ if (journalIndex < journal.length) {
3162
+ event = journal[journalIndex];
3163
+ journalIndex++;
3164
+ fromJournal = true;
3165
+ replaying = journalIndex < journal.length;
3166
+ } else try {
3167
+ event = await execution.waitForEvent();
3168
+ } catch (error) {
3169
+ if (error === IDLE) return {
3170
+ status: "idle",
3171
+ snapshot,
3172
+ entries
3173
+ };
3174
+ throw error;
3175
+ }
3176
+ const completedId = completionActorId(event);
3177
+ if (completedId !== void 0) liveInFlight.delete(completedId);
3178
+ if (!fromJournal) appendEntry(event);
3179
+ [snapshot, effects] = execution.transition(snapshot, event);
3180
+ latestSnapshot = snapshot;
3181
+ }
3182
+ }
3183
+ //#endregion
2832
3184
  exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
2833
3185
  exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
2834
3186
  exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
@@ -2846,6 +3198,7 @@ exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
2846
3198
  exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
2847
3199
  exports.appendMessages = require_setup_agent.appendMessages;
2848
3200
  exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
3201
+ exports.assertAgentMachine = assertAgentMachine;
2849
3202
  exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
2850
3203
  exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
2851
3204
  exports.assistantMessage = require_decision.assistantMessage;
@@ -2890,6 +3243,7 @@ exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
2890
3243
  exports.replay = require_setup_agent.replay;
2891
3244
  exports.resolveDecision = require_decision.resolveDecision;
2892
3245
  exports.runAgent = runAgent;
3246
+ exports.runDurableAgent = runDurableAgent;
2893
3247
  exports.runSeam = runSeam;
2894
3248
  exports.serializeTraceEvent = serializeTraceEvent;
2895
3249
  Object.defineProperty(exports, "setupAgent", {