@statelyai/agent 2.0.0-alpha.22 → 2.0.0-alpha.24

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 (48) hide show
  1. package/dist/ai-sdk.cjs +53 -25
  2. package/dist/ai-sdk.d.cts +20 -12
  3. package/dist/ai-sdk.d.mts +20 -12
  4. package/dist/ai-sdk.mjs +53 -25
  5. package/dist/{decision-DxSTqzgZ.mjs → decision-BBZ2jMO1.mjs} +444 -79
  6. package/dist/{decision-1o45_ZrS.cjs → decision-Cv6SfNWl.cjs} +525 -94
  7. package/dist/index.cjs +2224 -1032
  8. package/dist/index.d.cts +413 -275
  9. package/dist/index.d.mts +413 -275
  10. package/dist/index.mjs +2163 -983
  11. package/dist/machines.cjs +30 -2
  12. package/dist/machines.d.cts +1 -1
  13. package/dist/machines.d.mts +1 -1
  14. package/dist/machines.mjs +30 -2
  15. package/dist/openai.cjs +447 -0
  16. package/dist/openai.d.cts +177 -0
  17. package/dist/openai.d.mts +177 -0
  18. package/dist/openai.mjs +437 -0
  19. package/dist/otel.cjs +3 -6
  20. package/dist/otel.d.cts +1 -1
  21. package/dist/otel.d.mts +1 -1
  22. package/dist/otel.mjs +3 -6
  23. package/dist/run-agent-Ck7Sa3Tb.d.mts +968 -0
  24. package/dist/run-agent-DWqA8KiI.d.cts +968 -0
  25. package/dist/setup-agent-CeO2fARv.mjs +639 -0
  26. package/dist/setup-agent-Dl2VELLi.cjs +663 -0
  27. package/dist/{text-logic-BaxPrcLk.d.cts → text-logic-C7bSnH_B.d.cts} +195 -44
  28. package/dist/{text-logic-DhfFWzu9.d.mts → text-logic-DJBK3LbG.d.mts} +195 -44
  29. package/dist/{types-DSdj2tGs.d.cts → types-CjHk5OdH.d.cts} +4 -19
  30. package/dist/{types-CvWRGFxP.d.mts → types-DWA6X9R-.d.mts} +4 -19
  31. package/package.json +17 -21
  32. package/readme.md +32 -17
  33. package/dist/errors-BQRk9eiZ.d.cts +0 -19
  34. package/dist/errors-C9rxnWbX.d.mts +0 -19
  35. package/dist/errors-CeSXQx0v.mjs +0 -23
  36. package/dist/errors-DUBBzRLP.cjs +0 -28
  37. package/dist/event-log-store-Bz7HDBkE.d.cts +0 -145
  38. package/dist/event-log-store-DmIDosD6.mjs +0 -427
  39. package/dist/event-log-store-a_TKy1gk.cjs +0 -468
  40. package/dist/event-log-store-hrA1vqtN.d.mts +0 -145
  41. package/dist/run-agent-0fpEZ1wJ.d.mts +0 -1098
  42. package/dist/run-agent-Zbkx7XP8.d.cts +0 -1098
  43. package/dist/setup-agent-DkCTy5Eu.cjs +0 -1707
  44. package/dist/setup-agent-nSFYrNTP.mjs +0 -1587
  45. package/dist/sqlite.cjs +0 -129
  46. package/dist/sqlite.d.cts +0 -57
  47. package/dist/sqlite.d.mts +0 -57
  48. package/dist/sqlite.mjs +0 -127
package/dist/index.cjs CHANGED
@@ -1,196 +1,1370 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_errors = require("./errors-DUBBzRLP.cjs");
3
- const require_setup_agent = require("./setup-agent-DkCTy5Eu.cjs");
4
- const require_decision = require("./decision-1o45_ZrS.cjs");
5
- const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
2
+ const require_decision = require("./decision-Cv6SfNWl.cjs");
3
+ const require_setup_agent = require("./setup-agent-Dl2VELLi.cjs");
6
4
  require("./validate.cjs");
7
5
  let xstate = require("xstate");
8
- let xstate_durable = require("xstate/durable");
9
- //#region src/internal/state-request-pass.ts
10
- async function runTextPhase(stateRequest, baseMessages, deps) {
11
- const { model, system } = stateRequest;
12
- const id = stateRequest.id ?? deps.nextRequestId();
13
- const promptMessage = require_decision.userMessage(stateRequest.prompt);
14
- if (stateRequest.kind === "decision") return {
15
- stateRequest,
16
- id,
17
- output: void 0,
18
- appended: [promptMessage]
6
+ //#region src/interaction.ts
7
+ function issue(message, path) {
8
+ return {
9
+ message,
10
+ path
11
+ };
12
+ }
13
+ function validateEventMeta(value, path) {
14
+ if (typeof value === "string") return [];
15
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [issue("Expected a label string or an interaction event descriptor", path)];
16
+ const descriptor = value;
17
+ const issues = [];
18
+ if (descriptor["label"] !== void 0 && typeof descriptor["label"] !== "string") issues.push(issue("Expected a string", [...path, "label"]));
19
+ if (descriptor["style"] !== void 0 && typeof descriptor["style"] !== "string") issues.push(issue("Expected a string", [...path, "style"]));
20
+ const event = descriptor["event"];
21
+ if (event !== void 0 && (!event || typeof event !== "object" || Array.isArray(event))) issues.push(issue("Expected an object of fixed event fields", [...path, "event"]));
22
+ return issues;
23
+ }
24
+ /**
25
+ * A {@link StandardSchemaV1} for the state metadata {@link getInteraction}
26
+ * reads: an object whose optional `interaction` declares a `label` (a string
27
+ * with `{context.path}` interpolation, or a function of the context), an
28
+ * `events` map of choices, and a `textEvent` for free-text answers. Pass it
29
+ * straight to `createAgentSchemas({ meta: interactionMetaSchema })` instead of
30
+ * restating the shape per machine.
31
+ */
32
+ const interactionMetaSchema = { "~standard": {
33
+ version: 1,
34
+ vendor: "statelyai-agent",
35
+ validate(value) {
36
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { issues: [{ message: "Expected a state meta object" }] };
37
+ const interaction = value["interaction"];
38
+ if (interaction === void 0) return { value };
39
+ if (!interaction || typeof interaction !== "object" || Array.isArray(interaction)) return { issues: [issue("Expected an interaction descriptor object", ["interaction"])] };
40
+ const descriptor = interaction;
41
+ const issues = [];
42
+ const label = descriptor["label"];
43
+ if (label !== void 0 && typeof label !== "string" && typeof label !== "function") issues.push(issue("Expected a string or a (context) => string", ["interaction", "label"]));
44
+ const events = descriptor["events"];
45
+ if (events !== void 0) if (!events || typeof events !== "object" || Array.isArray(events)) issues.push(issue("Expected an object keyed by event type", ["interaction", "events"]));
46
+ else for (const [type, config] of Object.entries(events)) issues.push(...validateEventMeta(config, [
47
+ "interaction",
48
+ "events",
49
+ type
50
+ ]));
51
+ if (descriptor["textEvent"] !== void 0 && typeof descriptor["textEvent"] !== "string") issues.push(issue("Expected a string", ["interaction", "textEvent"]));
52
+ return issues.length > 0 ? { issues } : { value };
53
+ }
54
+ } };
55
+ function readPath(value, path) {
56
+ return path.split(".").reduce((current, key) => {
57
+ if (!current || typeof current !== "object") return void 0;
58
+ return current[key];
59
+ }, value);
60
+ }
61
+ function interpolate(label, context, preserveWhitespace) {
62
+ const interpolated = label.replace(/\{([^{}]+)\}/g, (_, path) => {
63
+ const value = readPath(context, path.trim());
64
+ return value === void 0 || value === null ? "" : String(value);
65
+ });
66
+ return preserveWhitespace ? interpolated : interpolated.replace(/\s+/g, " ").trim();
67
+ }
68
+ /** Reads the active state's interaction metadata and filters it through the
69
+ * events XState currently accepts. Returns `undefined` when none is declared. */
70
+ function getInteraction(snapshot, options = {}) {
71
+ const interaction = require_decision.getStateMeta(snapshot).interaction;
72
+ if (!interaction) return void 0;
73
+ const preserveWhitespace = options.preserveWhitespace ?? false;
74
+ const schemas = require_decision.getAgentExecutionOptions(snapshot.machine)?.schemas;
75
+ const accepted = new Set(require_decision.getAcceptedEvents(snapshot).map((event) => event.type));
76
+ const label = typeof interaction.label === "function" ? interaction.label({ context: snapshot.context }) : interaction.label ?? "";
77
+ const events = Object.entries(interaction.events ?? {}).filter(([type]) => accepted.has(type)).map(([type, config]) => {
78
+ return [type, typeof config === "string" ? { label: config } : config];
79
+ }).filter(([type, descriptor]) => {
80
+ try {
81
+ const complete = require_decision.parseAgentEvent(snapshot, {
82
+ ...descriptor.event,
83
+ type
84
+ }, { schemas });
85
+ return snapshot.can(complete);
86
+ } catch {
87
+ return true;
88
+ }
89
+ }).map(([type, descriptor]) => {
90
+ return {
91
+ type,
92
+ label: interpolate(descriptor.label ?? type, snapshot.context, preserveWhitespace),
93
+ ...descriptor.style ? { style: descriptor.style } : {},
94
+ ...descriptor.event ? { event: descriptor.event } : {}
95
+ };
96
+ });
97
+ return {
98
+ label: interpolate(label, snapshot.context, preserveWhitespace),
99
+ events,
100
+ ...interaction.textEvent && accepted.has(interaction.textEvent) ? { textEvent: interaction.textEvent } : {}
101
+ };
102
+ }
103
+ /** Converts a rendered interaction choice back into a schema-validated machine
104
+ * event. Fixed fields declared in metadata are merged before user fields. */
105
+ function eventFromInteraction(snapshot, choice) {
106
+ const interaction = getInteraction(snapshot);
107
+ if (!interaction) throw new require_decision.AgentInvalidEventPayloadError("(interaction)", "this state renders no interaction, so there is no choice to convert.");
108
+ let event;
109
+ if ("text" in choice) {
110
+ if (!interaction.textEvent) throw new require_decision.AgentInvalidEventPayloadError("(text)", `this interaction takes no free text. Choices: ${interaction.events.map(({ type }) => type).join(", ") || "(none)"}.`);
111
+ event = {
112
+ type: interaction.textEvent,
113
+ text: choice.text
114
+ };
115
+ } else {
116
+ const descriptor = interaction.events.find(({ type }) => type === choice.type);
117
+ if (!descriptor) throw new require_decision.AgentInvalidEventPayloadError(choice.type, `this interaction does not offer that choice. Choices: ${interaction.events.map(({ type }) => type).join(", ") || "(none)"}.`);
118
+ event = {
119
+ ...choice,
120
+ ...descriptor.event,
121
+ type: choice.type
122
+ };
123
+ }
124
+ return require_decision.parseAgentEvent(snapshot, event);
125
+ }
126
+ //#endregion
127
+ //#region src/steps.ts
128
+ /**
129
+ * The step envelope: an INTERNAL, per-model-call-checkpoint view of an agent
130
+ * machine (`AgentStep` = snapshot + actions + discovered requests + done). It
131
+ * once backed a public step API; the public step path is now the thin
132
+ * effect/replay loop in `./effects.ts` (re-exported from `@statelyai/agent`).
133
+ * These helpers survive as internals that `runAgent`, `verify.ts`, and the
134
+ * effect path build on — the only symbol still on the public surface is
135
+ * {@link executeAgentRequest} (the `text`-effect resolver).
136
+ * @module
137
+ */
138
+ /** @internal Normalizes current and legacy XState invoke effect shapes. */
139
+ function getInvokeEffectMetadata(action) {
140
+ if (action.type === "@xstate.spawn") return action;
141
+ if (action.type === "xstate.spawnChild") {
142
+ const params = action.params;
143
+ return params ? {
144
+ ...params,
145
+ logic: action.logic
146
+ } : void 0;
147
+ }
148
+ if (action.type === "@xstate.start" && typeof action.src === "string") return action;
149
+ }
150
+ /**
151
+ * The invokes a snapshot is still waiting on, lowered to the same spawn-effect
152
+ * shape {@link getAgentRequests} reads. Derived from the snapshot's live
153
+ * children rather than the last transition's actions, so a state that invokes
154
+ * several actors at once (an `invoke: [...]` array, or parallel regions each
155
+ * invoking) keeps reporting the siblings after the first one settles —
156
+ * including when the settled invoke's `onDone` is targetless and starts
157
+ * nothing new.
158
+ *
159
+ * Entries are ordered by invoke id, so a walker that resolves one invoke per
160
+ * step is deterministic.
161
+ *
162
+ * @internal
163
+ */
164
+ function getPendingInvokes(snapshot) {
165
+ const children = snapshot.children ?? {};
166
+ return Object.keys(children).sort().flatMap((id) => {
167
+ const child = children[id];
168
+ const childSnapshot = child?.getSnapshot?.();
169
+ if (!child || childSnapshot?.status !== "active") return [];
170
+ return [{
171
+ type: "@xstate.spawn",
172
+ id,
173
+ src: child.src,
174
+ input: childSnapshot.input,
175
+ logic: child.logic
176
+ }];
177
+ });
178
+ }
179
+ /**
180
+ * Scans a set of executable actions (as returned by xstate's `transition`/
181
+ * `initialTransition`) for spawned `TextLogic`/`DecisionLogic` invokes and
182
+ * lowers each into an {@link AgentStepRequest}. Pass `options.machine` to
183
+ * pre-fill `schemas`/`actors` from the machine's registered `setupAgent`
184
+ * options instead of passing them by hand.
185
+ * `options.snapshot` is required to resolve a decision's candidate events
186
+ * (intersecting declared `allowedEvents` with what's currently legal) — omit
187
+ * it and decision requests report an empty `events` list.
188
+ *
189
+ * @internal
190
+ */
191
+ function getAgentRequests(actions, { machine, ...options } = {}) {
192
+ if (machine) options = {
193
+ ...require_decision.getRegisteredAgentExecutionOptions(machine),
194
+ ...options
195
+ };
196
+ return actions.flatMap((action) => {
197
+ const params = getInvokeEffectMetadata(action);
198
+ if (!params || typeof params.src !== "string") return [];
199
+ if (typeof params.id !== "string" || params.id.length === 0) throw new Error(`Agent invoke '${params.src}' must define a durable string id.`);
200
+ const registeredLogic = require_decision.isTextLogic(params.logic) || require_decision.isDecisionLogic(params.logic) ? params.logic : options.actors?.[params.src];
201
+ if (require_decision.isDecisionLogic(registeredLogic)) {
202
+ const decisionRequest = registeredLogic.request(params.input);
203
+ const allowedEventTypes = registeredLogic.allowedEventTypes?.(params.input);
204
+ const events = options.snapshot ? require_decision.getAcceptedEvents(options.snapshot, {
205
+ events: options.events,
206
+ schemas: options.schemas,
207
+ eventTypes: allowedEventTypes,
208
+ eventToolName: options.eventToolName
209
+ }) : [];
210
+ return [{
211
+ ...decisionRequest,
212
+ id: params.id,
213
+ events
214
+ }];
215
+ }
216
+ const textLogic = require_decision.isTextLogic(registeredLogic) ? registeredLogic : void 0;
217
+ const input = textLogic ? textLogic.request(params.input) : void 0;
218
+ if (!input) return [];
219
+ return [{
220
+ kind: "text",
221
+ id: params.id,
222
+ src: params.src,
223
+ ...textLogic ? { mode: textLogic.mode } : {},
224
+ input,
225
+ tools: input.tools ?? {},
226
+ events: []
227
+ }];
228
+ });
229
+ }
230
+ /**
231
+ * Builds the canonical `xstate.done.actor` event xstate's `transition()`
232
+ * expects to resolve a spawned invoke — including the child session identity
233
+ * that rejects a stale completion from an earlier incarnation.
234
+ *
235
+ * @internal
236
+ */
237
+ function doneEvent(snapshot, request, output) {
238
+ const id = typeof request === "string" ? request : request.id;
239
+ const child = snapshot.children[id];
240
+ return {
241
+ type: "xstate.done.actor",
242
+ actorId: id,
243
+ ...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
244
+ output
19
245
  };
20
- if (!deps.generateText) throw new Error("runAgent: a getRequests request needs a 'generateText' executor (or use kind: 'decision').");
21
- const request = {
22
- model,
23
- ...system !== void 0 ? { system } : {},
24
- messages: [...baseMessages, promptMessage],
25
- tools: {}
246
+ }
247
+ /**
248
+ * The `xstate.error.actor` counterpart of {@link doneEvent} — the event a
249
+ * failed invoke delivers, which `onError` transitions observe. Carries the
250
+ * same child session identity, so xstate does not discard it as stale.
251
+ *
252
+ * @internal
253
+ */
254
+ function errorEvent(snapshot, request, error) {
255
+ const id = typeof request === "string" ? request : request.id;
256
+ const child = snapshot.children[id];
257
+ return {
258
+ type: "xstate.error.actor",
259
+ actorId: id,
260
+ ...typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {},
261
+ error
26
262
  };
27
- const agentRequest = {
263
+ }
264
+ /**
265
+ * Applies a request's `output` as a done event (or, with `outcome: 'error'`,
266
+ * its `output` as the invoke's error) via `transition(...)`, returning the raw
267
+ * `[snapshot, actions]` tuple. Lower-level than {@link resolveAgentStep} /
268
+ * {@link rejectAgentStep} — those helpers wrap this and also run
269
+ * {@link getAgentRequests} to produce the next {@link AgentStep}.
270
+ *
271
+ * @internal
272
+ */
273
+ function transitionResult(logic, snapshot, request, output, outcome = "output") {
274
+ const event = outcome === "error" ? errorEvent(snapshot, request, output) : doneEvent(snapshot, request, output);
275
+ const result = (0, xstate.transition)(logic, snapshot, event);
276
+ applyFinalStateOutput(logic, result[0], event);
277
+ return result;
278
+ }
279
+ /**
280
+ * Starts a machine and returns its first {@link AgentStep} — the step-path
281
+ * equivalent of `initialTransition` plus request discovery. Begins the
282
+ * durable/per-model-call-checkpoint loop: resolve each `step.requests` entry
283
+ * (via {@link executeAgentRequest} for `kind: 'text'`, or
284
+ * {@link resolveDecision} for `kind: 'decision'`), then advance with
285
+ * {@link resolveAgentStep} or {@link transitionAgentStep}.
286
+ */
287
+ function initialAgentStep(machine, input, options) {
288
+ const [snapshot, actions] = (0, xstate.initialTransition)(machine, input);
289
+ return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
290
+ }
291
+ /**
292
+ * Applies an externally-sent event (e.g. a decision's chosen event, or a
293
+ * human's reply) and returns the next {@link AgentStep}. Accepts **either**
294
+ * a raw snapshot **or** a prior `AgentStep` as the second argument —
295
+ * `.snapshot` is unwrapped automatically, so callers can thread the whole
296
+ * step object through without manually plucking the snapshot out.
297
+ */
298
+ function transitionAgentStep(machine, snapshotOrStep, event, options) {
299
+ const [nextSnapshot, actions] = (0, xstate.transition)(machine, isAgentStep(snapshotOrStep) ? snapshotOrStep.snapshot : snapshotOrStep, event);
300
+ return createAgentStep(machine, nextSnapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
301
+ }
302
+ /**
303
+ * Applies a resolved text request's output (a `kind: 'text'`
304
+ * {@link AgentRequest} — not a decision) as a done event and returns the
305
+ * next {@link AgentStep}. For decisions, resolve with `resolveDecision`
306
+ * (which returns a {@link ChosenEvent}) and apply it with
307
+ * {@link transitionAgentStep} instead — a decision has no output value of
308
+ * its own to feed here.
309
+ */
310
+ function resolveAgentStep(machine, step, request, output, options) {
311
+ const [snapshot, actions] = transitionResult(machine, step.snapshot, request, output);
312
+ return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
313
+ }
314
+ /**
315
+ * Rejects a pending invoke — the failure counterpart of
316
+ * {@link resolveAgentStep}. Delivers `error` as the invoke's
317
+ * `xstate.error.actor` event, so the machine takes that invoke's `onError`
318
+ * transition and returns the next {@link AgentStep}. With no `onError` in
319
+ * scope the returned step's snapshot has `status: 'error'`, exactly as a live
320
+ * run's failed invoke does.
321
+ *
322
+ * A durable host replays a failed call with this: the log says the call
323
+ * failed, so the replay rejects the invoke instead of resolving it. Scripted
324
+ * playthroughs use it too — see `simulateAgent`'s `script.errors`.
325
+ *
326
+ * `error` can be any value. An `Error` instance is the usual one, but a plain
327
+ * object (`{ code: 'truncated' }`) reaches the `onError` transition as
328
+ * `event.error` untouched, so a machine that branches on a failure code is
329
+ * testable without constructing a provider's error class.
330
+ */
331
+ function rejectAgentStep(machine, step, request, error, options) {
332
+ const [snapshot, actions] = transitionResult(machine, step.snapshot, request, error, "error");
333
+ return createAgentStep(machine, snapshot, actions, require_decision.getRegisteredAgentExecutionOptions(machine, options));
334
+ }
335
+ async function executeAgentRequest(requestOrEffect, executors) {
336
+ if (requestOrEffect.kind === "decision") throw new Error("executeAgentRequest(...) is text-only. Resolve a 'decision' request with resolveDecision(request, executors, ...) instead.");
337
+ const request = "requestId" in requestOrEffect ? {
28
338
  kind: "text",
29
- id,
30
- src: require_decision.INTERPRET_SOURCE,
31
- mode: "generate",
32
- input: request,
33
- tools: {},
339
+ id: requestOrEffect.requestId,
340
+ src: "",
341
+ mode: requestOrEffect.mode,
342
+ input: requestOrEffect.request,
343
+ tools: requestOrEffect.request.tools ?? {},
34
344
  events: []
345
+ } : requestOrEffect;
346
+ assertTextExecutor(request, executors);
347
+ const { output, raw } = await require_decision.executeAgentTextRequest(request.mode ?? "generate", request.id, request.input, executors, request.tools);
348
+ return {
349
+ output: request.input.outputSchema ? require_decision.validateSchemaSync(request.input.outputSchema, output) : output,
350
+ raw
35
351
  };
36
- deps.consumeModelCall();
37
- deps.onTrace?.({
38
- type: "request.start",
39
- request: agentRequest
40
- });
41
- let output;
352
+ }
353
+ function assertTextExecutor(request, executors) {
354
+ const mode = request.mode ?? "generate";
355
+ const kind = mode === "stream" ? "streamText" : "generateText";
356
+ if (!(mode === "stream" ? executors.streamText : executors.generateText)) throw new Error(`this step's text request '${request.src}' needs a '${kind}' executor but none was provided.`);
357
+ }
358
+ function createAgentStep(machine, snapshot, actions, options) {
359
+ applyFinalStateOutput(machine, snapshot);
360
+ return {
361
+ snapshot,
362
+ actions,
363
+ requests: getAgentRequests(getPendingInvokes(snapshot), {
364
+ ...options,
365
+ snapshot
366
+ }),
367
+ done: snapshot.status === "done"
368
+ };
369
+ }
370
+ function resolveStateValueConfig(config, value) {
371
+ if (typeof value === "string") return config.states?.[value];
372
+ if (!value || typeof value !== "object") return;
373
+ for (const [key, childValue] of Object.entries(value)) {
374
+ const childConfig = config.states?.[key];
375
+ if (!childConfig) continue;
376
+ if (childConfig.type === "final") return childConfig;
377
+ const nested = resolveStateValueConfig(childConfig, childValue);
378
+ if (nested) return nested;
379
+ }
380
+ }
381
+ function applyFinalStateOutput(logic, snapshot, event) {
382
+ const machineSnapshot = snapshot;
383
+ if (machineSnapshot.status !== "done" || machineSnapshot.output !== void 0 || !("config" in logic)) return;
384
+ const config = logic.config;
385
+ if (!config) return;
386
+ const output = resolveStateValueConfig(config, machineSnapshot.value)?.output;
387
+ if (output === void 0) return;
388
+ machineSnapshot.output = typeof output === "function" ? output({
389
+ context: machineSnapshot.context,
390
+ event
391
+ }) : output;
392
+ }
393
+ function isAgentStep(value) {
394
+ return !!value && typeof value === "object" && "snapshot" in value && "actions" in value && "requests" in value;
395
+ }
396
+ //#endregion
397
+ //#region src/event-log.ts
398
+ /**
399
+ * The event log: an append-only journal of the EXTERNAL inputs a machine
400
+ * consumed, and the pure fold ({@link replay}) that turns that journal back
401
+ * into a snapshot without executing anything.
402
+ *
403
+ * A journaled entry is always an external input — an invoke completion
404
+ * (`xstate.done.actor`/`xstate.error.actor` carrying its output inline), a
405
+ * user-sent event, a timer firing, a reserved `@agent.usage` record. Never a
406
+ * raised/internal event: replay re-derives those from the machine's own logic,
407
+ * so journaling them would double-apply them.
408
+ *
409
+ * A log is self-contained: its reserved first entry ({@link initEntry}) carries
410
+ * either the machine `input` (a fresh start) or a persisted snapshot (a
411
+ * continuation across a machine version, or from a log-less legacy snapshot),
412
+ * so {@link replay} needs no side channel.
413
+ * @module
414
+ */
415
+ /** The durable entry envelope version. */
416
+ const AGENT_EVENT_SCHEMA_VERSION = 1;
417
+ /**
418
+ * The reserved event type of the first entry of every log. It carries the
419
+ * machine `input` (fresh start) or a persisted `snapshot` (continuation), so a
420
+ * log replays with no side channel. Lives in the reserved `@agent.*` namespace
421
+ * so it can never collide with a machine's own vocabulary; it is consumed by
422
+ * {@link replay} and never fed to `transition`.
423
+ */
424
+ const AGENT_INIT_EVENT_TYPE = "@agent.init";
425
+ /** A malformed or internally inconsistent log. */
426
+ var AgentEventLogError = class extends require_decision.AgentError {
427
+ constructor(message) {
428
+ super("invalid-event-log", message);
429
+ this.name = "AgentEventLogError";
430
+ }
431
+ };
432
+ /** A precise failure when an entry would not survive a JSON round-trip. */
433
+ var NonSerializableAgentEventError = class extends require_decision.AgentError {
434
+ path;
435
+ valueType;
436
+ constructor(path, valueType) {
437
+ super("non-serializable-event", `Agent event field '${path}' is not JSON-serializable (${valueType}).`);
438
+ this.name = "NonSerializableAgentEventError";
439
+ this.path = path;
440
+ this.valueType = valueType;
441
+ }
442
+ };
443
+ /** Replay reached a state that does not match the entry's recorded hash. */
444
+ var AgentReplayDivergenceError = class extends require_decision.AgentError {
445
+ constructor(eventId, index, kind, expected, actual) {
446
+ super("replay-diverged", kind === "missing-verification" ? `Replay entry '${eventId}' at index ${index} has no recorded verification hash.` : `Replay diverged after '${eventId}' at index ${index}: expected state hash '${expected}', got '${actual}'.`);
447
+ this.eventId = eventId;
448
+ this.index = index;
449
+ this.kind = kind;
450
+ this.expected = expected;
451
+ this.actual = actual;
452
+ this.name = "AgentReplayDivergenceError";
453
+ }
454
+ };
455
+ /**
456
+ * An entry was recorded against a different machine (id) or a different
457
+ * machine version than the one replaying it. An init-with-snapshot entry is
458
+ * exempt: that entry IS the bridge from the old version to the new one.
459
+ */
460
+ var AgentMachineVersionMismatchError = class extends require_decision.AgentError {
461
+ constructor(eventId, index, expected, actual) {
462
+ super("machine-version-mismatch", `Event log entry '${eventId}' at index ${index} targets machine '${actual.machineId}'@'${actual.machineVersion}', expected '${expected.machineId}'@'${expected.machineVersion}'.`);
463
+ this.eventId = eventId;
464
+ this.index = index;
465
+ this.expected = expected;
466
+ this.actual = actual;
467
+ this.name = "AgentMachineVersionMismatchError";
468
+ }
469
+ };
470
+ function assertNoHiddenKeys(value, path, isOwnKey) {
471
+ const symbols = Object.getOwnPropertySymbols(value);
472
+ if (symbols.length > 0) throw new NonSerializableAgentEventError(`${path}.[${String(symbols[0])}]`, "symbol key");
473
+ const hiddenKey = Object.getOwnPropertyNames(value).find((key) => !isOwnKey?.(key) && !Object.getOwnPropertyDescriptor(value, key)?.enumerable);
474
+ if (hiddenKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${hiddenKey}`, "non-enumerable property");
475
+ }
476
+ const arrayIndexPattern = /^(?:0|[1-9]\d*)$/;
477
+ /**
478
+ * Rejects values JSON would drop or coerce. Unlike `JSON.stringify`, this does
479
+ * not silently erase `undefined`/functions or turn non-finite numbers into
480
+ * `null`; durable entries contain only plain JSON values.
481
+ */
482
+ function assertJsonSerializable(value, path = "entry", ancestors = /* @__PURE__ */ new WeakSet()) {
483
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
484
+ if (typeof value === "number") {
485
+ if (!Number.isFinite(value) || Object.is(value, -0)) throw new NonSerializableAgentEventError(path, Object.is(value, -0) ? "-0" : String(value));
486
+ return;
487
+ }
488
+ if (typeof value !== "object") throw new NonSerializableAgentEventError(path, typeof value);
489
+ if (ancestors.has(value)) throw new NonSerializableAgentEventError(path, "circular reference");
490
+ ancestors.add(value);
42
491
  try {
43
- const raw = await deps.generateText(request, { signal: deps.signal });
44
- output = await require_decision.normalizeGeneratorResult(raw, id, { request });
45
- const rawReasoning = raw?.reasoning;
46
- const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
47
- const usage = require_decision.getCallUsage(raw);
48
- if (usage) deps.recordUsage?.(usage);
49
- deps.onResult?.(agentRequest, {
50
- output,
51
- raw
52
- });
53
- deps.onTrace?.({
54
- type: "request.end",
55
- request: agentRequest,
56
- output,
57
- raw,
58
- ...reasoning !== void 0 ? { reasoning } : {},
59
- ...usage !== void 0 ? { usage } : {}
60
- });
61
- } catch (error) {
62
- deps.onTrace?.({
63
- type: "request.error",
64
- request: agentRequest,
65
- error
492
+ if (Array.isArray(value)) {
493
+ for (let index = 0; index < value.length; index++) {
494
+ if (!Object.hasOwn(value, index)) throw new NonSerializableAgentEventError(`${path}[${index}]`, "array hole");
495
+ assertJsonSerializable(value[index], `${path}[${index}]`, ancestors);
496
+ }
497
+ const extraKey = Object.keys(value).find((key) => !arrayIndexPattern.test(key) || Number(key) >= value.length);
498
+ if (extraKey !== void 0) throw new NonSerializableAgentEventError(`${path}.${extraKey}`, "array property");
499
+ assertNoHiddenKeys(value, path, (key) => key === "length" || arrayIndexPattern.test(key));
500
+ return;
501
+ }
502
+ const prototype = Object.getPrototypeOf(value);
503
+ if (prototype !== Object.prototype && prototype !== null) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
504
+ assertNoHiddenKeys(value, path);
505
+ for (const [key, child] of Object.entries(value)) assertJsonSerializable(child, `${path}.${key}`, ancestors);
506
+ } finally {
507
+ ancestors.delete(value);
508
+ }
509
+ }
510
+ function requireNonEmptyString(value, label, qualifier = "") {
511
+ if (typeof value !== "string" || !value) throw new AgentEventLogError(`Agent event ${label} must be a non-empty string${qualifier}.`);
512
+ }
513
+ /** Validates the complete durable envelope before append/export/replay. */
514
+ function assertAgentLogEntry(entry) {
515
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new AgentEventLogError("Agent event entry must be an object.");
516
+ assertJsonSerializable(entry);
517
+ const candidate = entry;
518
+ if (candidate.schemaVersion !== 1) throw new AgentEventLogError(`Unsupported agent event schema version '${String(candidate.schemaVersion)}'; expected '1'.`);
519
+ if (!Number.isInteger(candidate.index) || candidate.index < 0) throw new AgentEventLogError(`Agent event entry.index must be a non-negative integer; got ${String(candidate.index)}.`);
520
+ for (const field of [
521
+ "id",
522
+ "machineId",
523
+ "machineVersion"
524
+ ]) requireNonEmptyString(candidate[field], `entry.${field}`);
525
+ if (typeof candidate.recordedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(candidate.recordedAt) || Number.isNaN(Date.parse(candidate.recordedAt))) throw new AgentEventLogError(`Agent event entry.recordedAt is not RFC 3339: '${String(candidate.recordedAt)}'.`);
526
+ if (candidate.event === null || typeof candidate.event !== "object" || Array.isArray(candidate.event) || typeof candidate.event.type !== "string" || !candidate.event.type) throw new AgentEventLogError("Agent event entry.event requires a non-empty string type.");
527
+ if (candidate.causationId !== void 0) requireNonEmptyString(candidate.causationId, "entry.causationId", " when present");
528
+ if (candidate.metadata !== void 0 && (candidate.metadata === null || typeof candidate.metadata !== "object" || Array.isArray(candidate.metadata))) throw new AgentEventLogError("Agent event entry.metadata must be an object when present.");
529
+ if (candidate.verification !== void 0 && (candidate.verification === null || typeof candidate.verification !== "object" || typeof candidate.verification.stateHash !== "string" || !candidate.verification.stateHash)) throw new AgentEventLogError("Agent event entry.verification requires a non-empty stateHash string.");
530
+ }
531
+ /**
532
+ * Replaces `Error` instances with plain `{ name, message, cause? }` objects so
533
+ * an `xstate.error.actor` payload survives the log's JSON round-trip. Cycles
534
+ * are preserved by identity; non-plain objects are left alone (and are then
535
+ * rejected by {@link assertJsonSerializable}).
536
+ */
537
+ function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
538
+ if (value instanceof Error) {
539
+ const normalized = {
540
+ name: value.name,
541
+ message: value.message
542
+ };
543
+ seen.set(value, normalized);
544
+ if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
545
+ return normalized;
546
+ }
547
+ if (value === null || typeof value !== "object") return value;
548
+ const existing = seen.get(value);
549
+ if (existing !== void 0) return existing;
550
+ if (Array.isArray(value)) {
551
+ const result = [];
552
+ seen.set(value, result);
553
+ for (const item of value) result.push(normalizeEventErrors(item, seen));
554
+ return result;
555
+ }
556
+ const prototype = Object.getPrototypeOf(value);
557
+ if (prototype !== Object.prototype && prototype !== null) return value;
558
+ const result = {};
559
+ seen.set(value, result);
560
+ for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
561
+ return result;
562
+ }
563
+ const SNAPSHOT_VOLATILE_KEYS = new Set([
564
+ "sessionId",
565
+ "_nextTimerId",
566
+ "_nextActorIds"
567
+ ]);
568
+ const CHILD_VOLATILE_KEYS = new Set(["sessionId"]);
569
+ function positionOf(parent, key) {
570
+ if (parent === "snapshot") return key === "children" ? "children" : "data";
571
+ if (parent === "children") return "child";
572
+ if (parent === "child") return key === "snapshot" ? "snapshot" : "data";
573
+ return "data";
574
+ }
575
+ function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet(), position = "snapshot") {
576
+ if (value === void 0) return "[undefined]";
577
+ if (typeof value === "function") return "[function]";
578
+ if (typeof value === "bigint") return `[bigint:${String(value)}]`;
579
+ if (typeof value === "symbol") return `[symbol:${String(value)}]`;
580
+ if (value === null || typeof value !== "object") return value;
581
+ if (value instanceof Date) return `[Date:${value.toISOString()}]`;
582
+ if (value instanceof Error) {
583
+ if (seen.has(value)) return "[circular]";
584
+ seen.add(value);
585
+ try {
586
+ return {
587
+ errorName: value.name,
588
+ message: value.message,
589
+ ...value.cause !== void 0 ? { cause: canonicalizeForHash(value.cause, seen, "data") } : {}
590
+ };
591
+ } finally {
592
+ seen.delete(value);
593
+ }
594
+ }
595
+ if (seen.has(value)) return "[circular]";
596
+ seen.add(value);
597
+ try {
598
+ if (value instanceof Set) return [...value].map((item) => canonicalizeForHash(item, seen, "data")).sort((a, b) => stableJson(a).localeCompare(stableJson(b)));
599
+ if (value instanceof Map) return [...value.entries()].map(([key, item]) => [canonicalizeForHash(key, seen, "data"), canonicalizeForHash(item, seen, "data")]).sort(([a], [b]) => stableJson(a).localeCompare(stableJson(b)));
600
+ if (Array.isArray(value)) return value.map((item) => canonicalizeForHash(item, seen, "data"));
601
+ const volatileKeys = position === "snapshot" ? SNAPSHOT_VOLATILE_KEYS : position === "child" ? CHILD_VOLATILE_KEYS : void 0;
602
+ const result = {};
603
+ for (const key of Object.keys(value).sort()) {
604
+ if (volatileKeys?.has(key)) continue;
605
+ const child = value[key];
606
+ if (child === void 0) continue;
607
+ result[key] = canonicalizeForHash(child, seen, positionOf(position, key));
608
+ }
609
+ return result;
610
+ } finally {
611
+ seen.delete(value);
612
+ }
613
+ }
614
+ /**
615
+ * Key-sorted JSON for comparing two events by content: key insertion order
616
+ * does not count, so an entry read back from storage equals the one that was
617
+ * appended.
618
+ * @internal
619
+ */
620
+ function canonicalEventJson(event) {
621
+ return stableJson(canonicalizeForHash(event, /* @__PURE__ */ new Set(), "data"));
622
+ }
623
+ function stableJson(value) {
624
+ return JSON.stringify(sortJson(value));
625
+ }
626
+ function sortJson(value) {
627
+ if (Array.isArray(value)) return value.map(sortJson);
628
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
629
+ return value;
630
+ }
631
+ /**
632
+ * The canonical hash of a PERSISTED snapshot (`machine.getPersistedSnapshot()`,
633
+ * or {@link replay}'s `persistedSnapshot`) — the value recorded in an entry's
634
+ * `verification.stateHash`.
635
+ *
636
+ * Stripped before hashing, because they are re-minted on every fold and would
637
+ * otherwise make two identical runs hash differently: `sessionId`,
638
+ * `_nextTimerId` and `_nextActorIds` at the snapshot root, `sessionId` on each
639
+ * `children[*]` entry (recursively, through nested child snapshots), and
640
+ * `undefined`-valued properties (so a snapshot hashes the same before and
641
+ * after a JSON round-trip). Stripping is confined to those structural
642
+ * positions: a `sessionId` inside `context`, `output`, `error` or an event
643
+ * payload is application data and is hashed. Object keys are sorted, so key
644
+ * order is irrelevant.
645
+ *
646
+ * A change detector, not a cryptographic digest.
647
+ */
648
+ function getSnapshotStateHash(snapshot) {
649
+ return require_decision.djb2Hex(stableJson(canonicalizeForHash(snapshot)));
650
+ }
651
+ function machineIdOf(machine) {
652
+ return machine.config.id ?? machine.id ?? "(machine)";
653
+ }
654
+ function toPersisted(machine, snapshot) {
655
+ return typeof snapshot.matches === "function" ? machine.getPersistedSnapshot(snapshot) : snapshot;
656
+ }
657
+ /**
658
+ * Creates a JSON-safe, self-describing entry for `event` appended after
659
+ * `entries` (which must be the complete prefix, so `index` and the recorded
660
+ * state hash line up).
661
+ */
662
+ function createReplayEntry(machine, entries, event, options = {}) {
663
+ const index = entries.length;
664
+ const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
665
+ const entry = {
666
+ schemaVersion: 1,
667
+ id: options.id ?? `evt_${String(index).padStart(8, "0")}`,
668
+ index,
669
+ recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
670
+ machineId: machineIdOf(machine),
671
+ machineVersion,
672
+ event: normalizeEventErrors(event),
673
+ ...options.causationId !== void 0 ? { causationId: options.causationId } : {},
674
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
675
+ };
676
+ assertAgentLogEntry(entry);
677
+ if (options.verification !== false) entry.verification = { stateHash: getSnapshotStateHash(options.snapshot ? toPersisted(machine, options.snapshot) : replay(machine, [...entries, entry], {
678
+ machineVersion,
679
+ verify: false
680
+ }).persistedSnapshot) };
681
+ return entry;
682
+ }
683
+ function pruneUndefined(value) {
684
+ if (Array.isArray(value)) return value.map(pruneUndefined);
685
+ if (value === null || typeof value !== "object") return value;
686
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
687
+ const result = {};
688
+ for (const [key, child] of Object.entries(value)) {
689
+ if (child === void 0) continue;
690
+ result[key] = pruneUndefined(child);
691
+ }
692
+ return result;
693
+ }
694
+ /**
695
+ * The reserved first entry of a log. Pass EXACTLY one of:
696
+ *
697
+ * - `{ input }` — a fresh start; replay folds from `initialTransition`.
698
+ * - `{ snapshot }` — a continuation from a persisted snapshot: a version
699
+ * bridge, or a legacy snapshot that predates the log. Replay restores it
700
+ * purely (no actors started) and folds the rest of the log onto it.
701
+ *
702
+ * `options.metadata.executionId` names the lineage; this library always sets
703
+ * one, and readers must tolerate its absence in logs written elsewhere.
704
+ */
705
+ function initEntry(machine, init = {}, options = {}) {
706
+ const hasSnapshot = init.snapshot !== void 0;
707
+ if (hasSnapshot && init.input !== void 0) throw new AgentEventLogError("initEntry accepts exactly one of `input` (fresh start) or `snapshot` (continuation).");
708
+ return createReplayEntry(machine, [], {
709
+ type: AGENT_INIT_EVENT_TYPE,
710
+ ...hasSnapshot ? { snapshot: pruneUndefined(init.snapshot) } : init.input !== void 0 ? { input: init.input } : {}
711
+ }, options);
712
+ }
713
+ /** Whether `entry` is the reserved init entry. */
714
+ function isInitEntry(entry) {
715
+ return entry?.event?.type === AGENT_INIT_EVENT_TYPE;
716
+ }
717
+ /** Whether `entry` is the version-bridging init-with-snapshot entry. */
718
+ function isSnapshotInitEntry(entry) {
719
+ return isInitEntry(entry) && entry.event.snapshot !== void 0;
720
+ }
721
+ /**
722
+ * The log's execution id: the lineage identity pinned in the init entry's
723
+ * `metadata.executionId`.
724
+ *
725
+ * Minted once per run and inherited verbatim by every resume of that log, so
726
+ * it names one lineage rather than one process. Entry ids cannot serve this
727
+ * role — the built-in ids are index-derived, so every log's init entry is
728
+ * `evt_00000000`. A fork copies the init entry, so it shares its parent's
729
+ * execution id by construction.
730
+ *
731
+ * Returns `undefined` for a log with no init entry and for one written without
732
+ * the field — callers must treat the id as optional.
733
+ */
734
+ function getLogExecutionId(entries) {
735
+ const executionId = (entries.find((entry) => isInitEntry(entry))?.metadata)?.executionId;
736
+ return typeof executionId === "string" ? executionId : void 0;
737
+ }
738
+ /**
739
+ * Validates a log before replay or append: every envelope well-formed, indices
740
+ * contiguous from 0, ids unique, the first entry the reserved init entry, and
741
+ * one consistent machine identity throughout.
742
+ *
743
+ * With `machine`, entries are also checked against that machine's id and
744
+ * resolved version — a mismatch throws {@link AgentMachineVersionMismatchError}.
745
+ * An init-with-snapshot entry is exempt from the version check: that entry is
746
+ * itself the bridge from whatever version produced the snapshot.
747
+ */
748
+ function validateReplayEntries(entries, options = {}) {
749
+ if (entries.length === 0) return;
750
+ const reference = entries.find((entry) => !isSnapshotInitEntry(entry)) ?? entries[0];
751
+ const expected = options.machine ? {
752
+ machineId: machineIdOf(options.machine),
753
+ machineVersion: options.machineVersion ?? require_decision.resolveMachineVersion(options.machine)
754
+ } : {
755
+ machineId: reference.machineId,
756
+ machineVersion: options.machineVersion ?? reference.machineVersion
757
+ };
758
+ const ids = /* @__PURE__ */ new Set();
759
+ for (let index = 0; index < entries.length; index++) {
760
+ const entry = entries[index];
761
+ assertAgentLogEntry(entry);
762
+ if (entry.index !== index) throw new AgentEventLogError(`Event log must be contiguous from index 0; found entry.index ${entry.index} at position ${index}.`);
763
+ if (ids.has(entry.id)) throw new AgentEventLogError(`Event log contains duplicate entry id '${entry.id}'.`);
764
+ ids.add(entry.id);
765
+ if (index === 0 && !isInitEntry(entry)) throw new AgentEventLogError(`Event log must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry; found '${entry.event.type}'.`);
766
+ if (index > 0 && isInitEntry(entry)) throw new AgentEventLogError(`Event log has a second '${AGENT_INIT_EVENT_TYPE}' entry at index ${index}.`);
767
+ const versionMatches = entry.machineVersion === expected.machineVersion;
768
+ if (entry.machineId !== expected.machineId || !versionMatches && !isSnapshotInitEntry(entry)) throw new AgentMachineVersionMismatchError(entry.id, entry.index, expected, {
769
+ machineId: entry.machineId,
770
+ machineVersion: entry.machineVersion
66
771
  });
67
- throw error;
772
+ }
773
+ }
774
+ /**
775
+ * The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
776
+ * and must leave the init entry in place, so the fork is still self-contained.
777
+ */
778
+ function forkEventLog(entries, upToIndex) {
779
+ if (!Number.isInteger(upToIndex) || upToIndex < 1 || upToIndex > entries.length) throw new AgentEventLogError(`forkEventLog: upToIndex must be an integer in [1, ${entries.length}]; got ${String(upToIndex)}.`);
780
+ const forked = entries.slice(0, upToIndex);
781
+ if (!isInitEntry(forked[0])) throw new AgentEventLogError(`forkEventLog: the forked prefix must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry.`);
782
+ return forked;
783
+ }
784
+ const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
785
+ const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
786
+ /** Normalizes a mixed `EventObject | AgentLogEntry` history into bare events. */
787
+ function toEvents(history) {
788
+ if (!history) return [];
789
+ return history.map((item) => {
790
+ const candidate = item;
791
+ return candidate?.schemaVersion === 1 && typeof candidate.index === "number" && candidate.event !== null && typeof candidate.event === "object" ? candidate.event : item;
792
+ });
793
+ }
794
+ /**
795
+ * The 1-based occurrence `n` for the NEXT call at invoke site `siteId`:
796
+ * `1 + completions`, where a completion is a journaled `xstate.done.actor` OR
797
+ * `xstate.error.actor` for that `actorId` (an error is a semantic completion).
798
+ * The one counting rule behind every `${siteId}#${n}` call key, so a live run
799
+ * and a replay derive the same `n` by construction.
800
+ */
801
+ function agentCallOccurrence(history, siteId) {
802
+ let count = 0;
803
+ for (const event of toEvents(history)) if ((event.type === DONE_ACTOR_EVENT_TYPE || event.type === ERROR_ACTOR_EVENT_TYPE) && event.actorId === siteId) count++;
804
+ return count + 1;
805
+ }
806
+ /**
807
+ * Folds the reserved `@agent.usage` entries of a log into one cumulative usage
808
+ * total. The log is the source of truth, so the totals are a pure projection of
809
+ * it: replay a log, fold it, and you get the numbers the run reported with no
810
+ * host-side accumulator in between.
811
+ *
812
+ * Accepts entries or bare events. Token fields are partial sums: a field stays
813
+ * `undefined` until some entry reports it, and non-finite values are ignored.
814
+ */
815
+ function getUsageFromEvents(entries) {
816
+ const totals = {};
817
+ for (const event of toEvents(entries)) {
818
+ if (event.type !== "@agent.usage") continue;
819
+ const usage = event.usage;
820
+ if (!usage || typeof usage !== "object") continue;
821
+ for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
822
+ const value = usage[field];
823
+ if (typeof value === "number" && Number.isFinite(value)) totals[field] = (totals[field] ?? 0) + value;
824
+ }
825
+ }
826
+ return totals;
827
+ }
828
+ /**
829
+ * Rewrites a journaled `xstate.done.actor`/`xstate.error.actor` event's
830
+ * `sessionId` to the session the CURRENT fold minted for that `actorId`.
831
+ *
832
+ * Session ids are re-minted on every fold, and XState drops a completion event
833
+ * whose `sessionId` does not match the live child — silently, as a no-op
834
+ * transition. Rebinding is what makes a journaled completion replayable at all.
835
+ * `sessions` caches the mapping per fold, so a later event referring to the
836
+ * recorded session of an already-stopped child still resolves.
837
+ */
838
+ function rebindActorSession(event, snapshot, sessions) {
839
+ const actorEvent = event;
840
+ if (typeof actorEvent.actorId !== "string" || typeof actorEvent.sessionId !== "string") return event;
841
+ const key = `${actorEvent.actorId}\0${actorEvent.sessionId}`;
842
+ let sessionId = sessions.get(key);
843
+ if (!sessionId) {
844
+ const child = snapshot.children[actorEvent.actorId];
845
+ if (typeof child?.sessionId !== "string") return event;
846
+ sessionId = child.sessionId;
847
+ sessions.set(key, sessionId);
68
848
  }
69
849
  return {
70
- stateRequest,
71
- id,
72
- output,
73
- appended: [promptMessage, require_decision.assistantMessage(typeof output === "string" ? output : JSON.stringify(output))]
74
- };
75
- }
76
- async function runAdvancePhase(plan, deps) {
77
- const { stateRequest, id, output } = plan;
78
- const { model, system } = stateRequest;
79
- deps.appendToLog(...plan.appended);
80
- if (stateRequest.kind !== "decision" && stateRequest.onDone !== void 0) {
81
- const snapshot = deps.getSnapshot();
82
- const resolved = typeof stateRequest.onDone === "function" ? stateRequest.onDone({
83
- output,
84
- snapshot,
85
- messages: deps.messages
86
- }) : stateRequest.onDone;
87
- if (!resolved) return false;
88
- const acceptedTypes = require_decision.getAcceptedEvents(snapshot, { schemas: deps.schemas }).map((descriptor) => descriptor.type);
89
- if (!acceptedTypes.includes(resolved.type)) throw new Error(`runAgent: getRequests request '${id}' resolved onDone to event '${resolved.type}', which the current state does not accept. Accepted: ${acceptedTypes.join(", ") || "(none)"}.`);
90
- if (!snapshot.can(resolved)) return false;
91
- deps.send(resolved);
92
- return true;
850
+ ...event,
851
+ sessionId
852
+ };
853
+ }
854
+ function verifyEntry(entry, persisted, mode) {
855
+ if (mode === false) return;
856
+ if (!entry.verification) {
857
+ if (mode === "strict") throw new AgentReplayDivergenceError(entry.id, entry.index, "missing-verification");
858
+ return;
93
859
  }
94
- const events = require_decision.getAcceptedEvents(deps.getSnapshot(), {
95
- schemas: deps.schemas,
96
- ...stateRequest.allowedEvents ? { eventTypes: stateRequest.allowedEvents } : {}
97
- });
98
- if (events.length === 0) return false;
99
- if (!deps.decide) throw new Error(`runAgent: a getRequests request without 'onDone' needs a 'decide' executor to choose between events ${events.map((descriptor) => `'${descriptor.type}'`).join(", ")}. Provide request.onDone for deterministic advancement, or a 'decide' executor.`);
100
- const chosen = await require_decision.resolveDecision({
101
- kind: "decision",
102
- id,
103
- model,
104
- ...system !== void 0 ? { system } : {},
105
- messages: [...deps.messages],
106
- events,
107
- attempts: []
108
- }, { decide: deps.decide }, {
109
- signal: deps.signal,
110
- canTake: (event) => deps.getSnapshot().can(event)
860
+ const actual = getSnapshotStateHash(persisted);
861
+ if (actual !== entry.verification.stateHash) throw new AgentReplayDivergenceError(entry.id, entry.index, "state", entry.verification.stateHash, actual);
862
+ }
863
+ /**
864
+ * Folds a log through XState's pure `initialTransition`/`transition` WITHOUT
865
+ * executing anything: no actor is started, no action runs, no model is called.
866
+ * A journaled `xstate.done.actor` entry carries the completed actor's output
867
+ * inline, so the invoke it belongs to is never re-run. Crash recovery, fork
868
+ * resume, and time travel in one call.
869
+ *
870
+ * The first entry is the reserved {@link initEntry}: `input` folds from
871
+ * `initialTransition`, `snapshot` restores the persisted snapshot purely
872
+ * (`machine.restoreSnapshot`) and folds the rest of the log onto it.
873
+ */
874
+ function replay(machine, entries, options = {}) {
875
+ validateReplayEntries(entries, {
876
+ machine,
877
+ machineVersion: options.machineVersion ?? require_decision.resolveMachineVersion(machine)
111
878
  });
112
- if (deps.isSettled()) return false;
113
- deps.appendToLog(require_decision.assistantMessage(`[chose: ${chosen.type}]`));
114
- deps.send(chosen);
115
- return true;
116
- }
117
- /**
118
- * Executes one getRequests pass: all text calls concurrently against the
119
- * pass-start log, then per-request advancement sequentially in request
120
- * order. Returns whether ANY request sent an event — the host settles idle
121
- * when none did (otherwise an unchanged snapshot would re-produce the same
122
- * pass forever). Stops early (skipping remaining appends/sends) once
123
- * {@link StateRequestPassDeps.isSettled} reports the run is over. Throws on
124
- * executor/`onDone` errors — the host maps them to its error settle.
125
- */
126
- async function runStateRequestPass(requests, deps) {
127
- const baseMessages = [...deps.messages];
128
- const plans = await Promise.all(requests.map((stateRequest) => runTextPhase(stateRequest, baseMessages, deps)));
129
- let sentAny = false;
130
- for (const plan of plans) {
131
- if (deps.isSettled()) break;
132
- sentAny = await runAdvancePhase(plan, deps) || sentAny;
133
- }
134
- return { sentAny };
879
+ if (entries.length === 0) throw new AgentEventLogError(`Cannot replay an empty event log; it must start with a '${AGENT_INIT_EVENT_TYPE}' entry.`);
880
+ const initEvent = entries[0].event;
881
+ let snapshot = initEvent.snapshot !== void 0 ? machine.restoreSnapshot(initEvent.snapshot) : (0, xstate.initialTransition)(machine, initEvent.input)[0];
882
+ let persisted = machine.getPersistedSnapshot(snapshot);
883
+ verifyEntry(entries[0], persisted, options.verify);
884
+ const sessions = /* @__PURE__ */ new Map();
885
+ for (let index = 1; index < entries.length; index++) {
886
+ const entry = entries[index];
887
+ const event = rebindActorSession(entry.event, snapshot, sessions);
888
+ [snapshot] = (0, xstate.transition)(machine, snapshot, event);
889
+ persisted = machine.getPersistedSnapshot(snapshot);
890
+ verifyEntry(entry, persisted, options.verify);
891
+ }
892
+ return {
893
+ snapshot,
894
+ persistedSnapshot: persisted
895
+ };
135
896
  }
136
897
  //#endregion
137
- //#region src/run-agent.ts
898
+ //#region src/event-log-store.ts
138
899
  /**
139
- * Thrown by {@link runAgent} when resuming with a `snapshot` + `event` whose
140
- * `type` the restored state cannot accept (a type-level check via
141
- * {@link getAcceptedEvents}). A programmer/integration error, in the same
142
- * class as runAgent's bind-time throws — it throws rather than settling an
143
- * `error` result. A type-legal event a guard rejects is NOT this error (the
144
- * machine simply takes no transition). Always enforced; there is no opt-out.
145
- */
146
- var AgentIllegalResumeEventError = class extends require_errors.AgentError {
147
- eventType;
148
- acceptedTypes;
149
- constructor(eventType, acceptedTypes) {
150
- super("illegal-resume-event", `runAgent: cannot resume with event '${eventType}' — the restored state does not accept it. Accepted event types: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
151
- this.name = "AgentIllegalResumeEventError";
152
- this.eventType = eventType;
153
- this.acceptedTypes = acceptedTypes;
900
+ * Durable storage for {@link AgentLogEntry} logs: the one interface a host
901
+ * implements, plus the in-memory reference implementation the conformance
902
+ * suite is written against.
903
+ * @module
904
+ */
905
+ /**
906
+ * Rejection from {@link AgentEventLogStore.append} when the thread's stored
907
+ * length is not the `expectedIndex` the writer held — a concurrent writer
908
+ * appended first. `actualIndex` is the next index the store would accept.
909
+ */
910
+ var AgentEventLogConflictError = class extends require_decision.AgentError {
911
+ threadId;
912
+ expectedIndex;
913
+ actualIndex;
914
+ constructor(threadId, expectedIndex, actualIndex) {
915
+ super("event-log-conflict", `AgentEventLogStore.append: index conflict on thread "${threadId}": expected index ${expectedIndex} but found ${actualIndex} — a concurrent writer won.`);
916
+ this.name = "AgentEventLogConflictError";
917
+ this.threadId = threadId;
918
+ this.expectedIndex = expectedIndex;
919
+ this.actualIndex = actualIndex;
154
920
  }
155
921
  };
156
922
  /**
157
- * Thrown by {@link runAgent} when resuming from a `snapshot` whose stamped
158
- * `agentMeta.version` differs from the current machine's version, under the
159
- * default `onVersionMismatch: 'throw'` and with no `migrateSnapshot` hook. The
160
- * structural fingerprint of the machine changed since the snapshot was
161
- * persisted (a state/transition/invoke was added, removed, or retargeted), so
162
- * the snapshot may no longer resume cleanly. `from` is the snapshot's version,
163
- * `to` the current machine's.
164
- */
165
- var AgentSnapshotVersionMismatchError = class extends require_errors.AgentError {
166
- from;
167
- to;
168
- machineId;
169
- constructor(from, to, machineId) {
170
- super("snapshot-version-mismatch", `runAgent: cannot resume snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${to}' — the machine's structure changed since the snapshot was persisted. Provide options.migrateSnapshot to adapt it, or set options.onVersionMismatch to 'warn'/'ignore' to proceed anyway.`);
171
- this.name = "AgentSnapshotVersionMismatchError";
172
- this.from = from;
173
- this.to = to;
174
- this.machineId = machineId;
923
+ * In-memory reference store, and the baseline the conformance suite runs
924
+ * against. Per-thread entries are held in a contiguous, index-ordered list;
925
+ * append's check-and-push runs synchronously (no `await` between reading the
926
+ * length and writing), so two appends racing on the same `expectedIndex`
927
+ * resolve to exactly one winner. Every stored and returned entry is
928
+ * `structuredClone`d, so a caller can neither mutate stored state through a
929
+ * value it appended nor through a value it read.
930
+ */
931
+ function createInMemoryEventLogStore() {
932
+ const threads = /* @__PURE__ */ new Map();
933
+ const clone = (value) => structuredClone(value);
934
+ return {
935
+ async append({ threadId, expectedIndex, entries }) {
936
+ for (const entry of entries) assertAgentLogEntry(entry);
937
+ for (let i = 0; i < entries.length; i++) if (entries[i].index !== expectedIndex + i) throw new Error(`AgentEventLogStore.append: entry.index (${entries[i].index}) must be contiguous from expectedIndex (${expectedIndex}); expected ${expectedIndex + i} at position ${i} on thread "${threadId}".`);
938
+ const list = threads.get(threadId);
939
+ const length = list ? list.length : 0;
940
+ if (length !== expectedIndex) throw new AgentEventLogConflictError(threadId, expectedIndex, length);
941
+ const ids = new Set((list ?? []).map((entry) => entry.id));
942
+ for (const entry of entries) {
943
+ if (ids.has(entry.id)) throw new Error(`AgentEventLogStore.append: duplicate event id "${entry.id}" in thread "${threadId}".`);
944
+ ids.add(entry.id);
945
+ }
946
+ const cloned = entries.map((entry) => clone(entry));
947
+ if (list) for (const entry of cloned) list.push(entry);
948
+ else threads.set(threadId, cloned);
949
+ },
950
+ async read(threadId, options) {
951
+ const list = threads.get(threadId) ?? [];
952
+ const from = options?.from ?? 0;
953
+ return list.slice(from).map((entry) => clone(entry));
954
+ },
955
+ async length(threadId) {
956
+ return threads.get(threadId)?.length ?? 0;
957
+ },
958
+ async fork({ threadId, newThreadId, upToIndex }) {
959
+ if ((threads.get(newThreadId)?.length ?? 0) > 0) throw new Error(`AgentEventLogStore.fork: newThreadId "${newThreadId}" already has entries.`);
960
+ const source = threads.get(threadId);
961
+ if (!source) throw new Error(`AgentEventLogStore.fork: unknown source thread "${threadId}".`);
962
+ const upTo = upToIndex ?? source.length;
963
+ if (!Number.isInteger(upTo) || upTo < 1 || upTo > source.length) throw new Error(`AgentEventLogStore.fork: upToIndex must be an integer in [1, ${source.length}] on thread "${threadId}"; got ${String(upTo)}.`);
964
+ threads.set(newThreadId, source.slice(0, upTo).map((entry) => clone(entry)));
965
+ }
966
+ };
967
+ }
968
+ //#endregion
969
+ //#region src/event-log-store-conformance.ts
970
+ /**
971
+ * The runner-agnostic conformance suite for {@link AgentEventLogStore}
972
+ * implementations. Split out of `event-log-store.ts` so the store contract and
973
+ * the reference implementation stay readable.
974
+ *
975
+ * Every case throws a descriptive `Error` on the first violation, so any test
976
+ * runner (or a plain script) can drive it against its own store. Pass a
977
+ * `{ describe, it }` harness to register one test per case instead.
978
+ * @module
979
+ */
980
+ function fail(message) {
981
+ throw new Error(`event-log-store conformance: ${message}`);
982
+ }
983
+ function entry(index, type, metadata) {
984
+ return {
985
+ schemaVersion: 1,
986
+ id: `evt_${index}`,
987
+ index,
988
+ recordedAt: "2026-01-01T00:00:00.000Z",
989
+ machineId: "conformance",
990
+ machineVersion: "v1",
991
+ event: {
992
+ type,
993
+ seq: index
994
+ },
995
+ ...metadata !== void 0 ? { metadata } : {}
996
+ };
997
+ }
998
+ function entriesFrom(start, count) {
999
+ return Array.from({ length: count }, (_unused, i) => entry(start + i, `e${start + i}`));
1000
+ }
1001
+ function assertJsonEqual(actual, expected, message) {
1002
+ const a = JSON.stringify(actual);
1003
+ const b = JSON.stringify(expected);
1004
+ if (a !== b) fail(`${message} (expected ${b}, got ${a})`);
1005
+ }
1006
+ const cases = [
1007
+ {
1008
+ name: "an unknown thread reads empty and has length 0",
1009
+ async run(create) {
1010
+ const store = await create();
1011
+ assertJsonEqual(await store.read("missing"), [], "read of an unknown thread must be empty");
1012
+ if (await store.length("missing") !== 0) fail("length of an unknown thread must be 0");
1013
+ }
1014
+ },
1015
+ {
1016
+ name: "a single append reads back verbatim",
1017
+ async run(create) {
1018
+ const store = await create();
1019
+ const e = entry(0, "start", { label: "first" });
1020
+ await store.append({
1021
+ threadId: "t",
1022
+ expectedIndex: 0,
1023
+ entries: [e]
1024
+ });
1025
+ assertJsonEqual(await store.read("t"), [e], "read must return the appended entry");
1026
+ if (await store.length("t") !== 1) fail("length after a single append must be 1");
1027
+ }
1028
+ },
1029
+ {
1030
+ name: "a second append extends the log in order",
1031
+ async run(create) {
1032
+ const store = await create();
1033
+ await store.append({
1034
+ threadId: "t",
1035
+ expectedIndex: 0,
1036
+ entries: entriesFrom(0, 3)
1037
+ });
1038
+ await store.append({
1039
+ threadId: "t",
1040
+ expectedIndex: 3,
1041
+ entries: entriesFrom(3, 2)
1042
+ });
1043
+ if (await store.length("t") !== 5) fail("length after appending 3 then 2 entries must be 5");
1044
+ assertJsonEqual((await store.read("t")).map((e) => e.index), [
1045
+ 0,
1046
+ 1,
1047
+ 2,
1048
+ 3,
1049
+ 4
1050
+ ], "reads must return entries in contiguous log order");
1051
+ }
1052
+ },
1053
+ {
1054
+ name: "a non-contiguous entry.index is misuse, not a race",
1055
+ async run(create) {
1056
+ const store = await create();
1057
+ let caught;
1058
+ try {
1059
+ await store.append({
1060
+ threadId: "t",
1061
+ expectedIndex: 0,
1062
+ entries: [entry(1, "gap")]
1063
+ });
1064
+ } catch (error) {
1065
+ caught = error;
1066
+ }
1067
+ if (!(caught instanceof Error) || caught instanceof AgentEventLogConflictError) fail("a non-contiguous entry.index must throw a plain Error, not a conflict");
1068
+ }
1069
+ },
1070
+ {
1071
+ name: "a stale expectedIndex conflicts with correct fields",
1072
+ async run(create) {
1073
+ const store = await create();
1074
+ await store.append({
1075
+ threadId: "t",
1076
+ expectedIndex: 0,
1077
+ entries: [entry(0, "a")]
1078
+ });
1079
+ let caught;
1080
+ try {
1081
+ await store.append({
1082
+ threadId: "t",
1083
+ expectedIndex: 0,
1084
+ entries: [entry(0, "b")]
1085
+ });
1086
+ } catch (error) {
1087
+ caught = error;
1088
+ }
1089
+ if (!(caught instanceof AgentEventLogConflictError)) fail("a stale expectedIndex must throw AgentEventLogConflictError");
1090
+ if (caught.threadId !== "t" || caught.expectedIndex !== 0 || caught.actualIndex !== 1) fail("conflict error must carry threadId, expectedIndex, and the actual index");
1091
+ }
1092
+ },
1093
+ {
1094
+ name: "entry ids are unique within a thread",
1095
+ async run(create) {
1096
+ const store = await create();
1097
+ await store.append({
1098
+ threadId: "t",
1099
+ expectedIndex: 0,
1100
+ entries: [entry(0, "a")]
1101
+ });
1102
+ let caught;
1103
+ try {
1104
+ await store.append({
1105
+ threadId: "t",
1106
+ expectedIndex: 1,
1107
+ entries: [{
1108
+ ...entry(1, "b"),
1109
+ id: "evt_0"
1110
+ }]
1111
+ });
1112
+ } catch (error) {
1113
+ caught = error;
1114
+ }
1115
+ if (!(caught instanceof Error) || caught instanceof AgentEventLogConflictError) fail("a duplicate event id within a thread must throw a plain Error");
1116
+ }
1117
+ },
1118
+ {
1119
+ name: "concurrent appends at the same index have exactly one winner",
1120
+ async run(create) {
1121
+ const store = await create();
1122
+ await store.append({
1123
+ threadId: "t",
1124
+ expectedIndex: 0,
1125
+ entries: [entry(0, "a")]
1126
+ });
1127
+ const results = await Promise.allSettled([store.append({
1128
+ threadId: "t",
1129
+ expectedIndex: 1,
1130
+ entries: [entry(1, "w-a")]
1131
+ }), store.append({
1132
+ threadId: "t",
1133
+ expectedIndex: 1,
1134
+ entries: [entry(1, "w-b")]
1135
+ })]);
1136
+ const winners = results.filter((r) => r.status === "fulfilled");
1137
+ const conflicts = results.filter((r) => r.status === "rejected" && r.reason instanceof AgentEventLogConflictError);
1138
+ if (winners.length !== 1) fail(`exactly one racing append must win, got ${winners.length}`);
1139
+ if (conflicts.length !== 1) fail(`exactly one racing append must reject with a conflict, got ${conflicts.length}`);
1140
+ if (await store.length("t") !== 2) fail("after the race the log length must be 2");
1141
+ }
1142
+ },
1143
+ {
1144
+ name: "read({ from }) is incrementally correct",
1145
+ async run(create) {
1146
+ const store = await create();
1147
+ await store.append({
1148
+ threadId: "t",
1149
+ expectedIndex: 0,
1150
+ entries: entriesFrom(0, 5)
1151
+ });
1152
+ assertJsonEqual((await store.read("t", { from: 2 })).map((e) => e.index), [
1153
+ 2,
1154
+ 3,
1155
+ 4
1156
+ ], "read({ from }) must skip entries below `from`");
1157
+ assertJsonEqual(await store.read("t", { from: 5 }), [], "read({ from }) at the log length must be empty");
1158
+ }
1159
+ },
1160
+ {
1161
+ name: "threads are isolated and grow independently",
1162
+ async run(create) {
1163
+ const store = await create();
1164
+ await store.append({
1165
+ threadId: "a",
1166
+ expectedIndex: 0,
1167
+ entries: [entry(0, "x")]
1168
+ });
1169
+ if (await store.length("b") !== 0) fail("an append to one thread must not create another");
1170
+ await store.append({
1171
+ threadId: "b",
1172
+ expectedIndex: 0,
1173
+ entries: entriesFrom(0, 2)
1174
+ });
1175
+ if (await store.length("a") !== 1 || await store.length("b") !== 2) fail("threads must grow independently");
1176
+ }
1177
+ },
1178
+ {
1179
+ name: "metadata round-trips verbatim",
1180
+ async run(create) {
1181
+ const store = await create();
1182
+ const metadata = {
1183
+ source: "user",
1184
+ nested: { attempt: 2 },
1185
+ tags: ["x", "y"]
1186
+ };
1187
+ await store.append({
1188
+ threadId: "t",
1189
+ expectedIndex: 0,
1190
+ entries: [entry(0, "e", metadata)]
1191
+ });
1192
+ assertJsonEqual((await store.read("t"))[0].metadata, metadata, "metadata must round-trip verbatim");
1193
+ }
1194
+ },
1195
+ {
1196
+ name: "stored entries are isolated from caller mutation",
1197
+ async run(create) {
1198
+ const store = await create();
1199
+ const e = entry(0, "e", { tags: ["a"] });
1200
+ await store.append({
1201
+ threadId: "t",
1202
+ expectedIndex: 0,
1203
+ entries: [e]
1204
+ });
1205
+ e.metadata.tags.push("mutated-after-append");
1206
+ e.event.seq = 999;
1207
+ const read = await store.read("t");
1208
+ read[0].metadata.tags.push("mutated-after-read");
1209
+ read[0].event.seq = 888;
1210
+ const reread = await store.read("t");
1211
+ assertJsonEqual(reread[0].metadata, { tags: ["a"] }, "stored entry must be isolated from post-append and post-read mutation");
1212
+ assertJsonEqual(reread[0].event, {
1213
+ type: "e",
1214
+ seq: 0
1215
+ }, "stored event must be isolated from mutation of an appended or read copy");
1216
+ }
1217
+ },
1218
+ {
1219
+ name: "fork copies a prefix and then diverges independently",
1220
+ async run(create) {
1221
+ const store = await create();
1222
+ await store.append({
1223
+ threadId: "src",
1224
+ expectedIndex: 0,
1225
+ entries: entriesFrom(0, 3)
1226
+ });
1227
+ await store.fork({
1228
+ threadId: "src",
1229
+ newThreadId: "fork-full"
1230
+ });
1231
+ if (await store.length("fork-full") !== 3) fail("fork with the default upToIndex must copy the full log");
1232
+ await store.fork({
1233
+ threadId: "src",
1234
+ newThreadId: "fork-1",
1235
+ upToIndex: 1
1236
+ });
1237
+ assertJsonEqual((await store.read("fork-1")).map((e) => e.index), [0], "fork with upToIndex 1 must copy only entry 0");
1238
+ await store.fork({
1239
+ threadId: "src",
1240
+ newThreadId: "fork-2",
1241
+ upToIndex: 2
1242
+ });
1243
+ assertJsonEqual((await store.read("fork-2")).map((e) => e.id), ["evt_0", "evt_1"], "fork with upToIndex 2 must copy entries 0 and 1 only");
1244
+ await store.append({
1245
+ threadId: "fork-1",
1246
+ expectedIndex: 1,
1247
+ entries: [entry(1, "branch")]
1248
+ });
1249
+ assertJsonEqual((await store.read("fork-1")).map((e) => e.event.type), ["e0", "branch"], "a forked thread must append independently from its copied length");
1250
+ if (await store.length("src") !== 3) fail("appending to a fork must not touch the source thread");
1251
+ }
1252
+ },
1253
+ {
1254
+ name: "fork rejects a non-empty target, an unknown source, and an out-of-range cutoff (0 included)",
1255
+ async run(create) {
1256
+ const store = await create();
1257
+ await store.append({
1258
+ threadId: "src",
1259
+ expectedIndex: 0,
1260
+ entries: entriesFrom(0, 3)
1261
+ });
1262
+ await store.fork({
1263
+ threadId: "src",
1264
+ newThreadId: "fork-full"
1265
+ });
1266
+ let caughtExisting;
1267
+ try {
1268
+ await store.fork({
1269
+ threadId: "src",
1270
+ newThreadId: "fork-full"
1271
+ });
1272
+ } catch (error) {
1273
+ caughtExisting = error;
1274
+ }
1275
+ if (!(caughtExisting instanceof Error) || caughtExisting instanceof AgentEventLogConflictError) fail("forking onto a non-empty thread must reject with a plain Error");
1276
+ let caughtUnknown;
1277
+ try {
1278
+ await store.fork({
1279
+ threadId: "nope",
1280
+ newThreadId: "fork-nope"
1281
+ });
1282
+ } catch (error) {
1283
+ caughtUnknown = error;
1284
+ }
1285
+ if (!(caughtUnknown instanceof Error) || caughtUnknown instanceof AgentEventLogConflictError) fail("forking an unknown source thread must reject with a plain Error");
1286
+ let caughtRange;
1287
+ try {
1288
+ await store.fork({
1289
+ threadId: "src",
1290
+ newThreadId: "fork-range",
1291
+ upToIndex: 99
1292
+ });
1293
+ } catch (error) {
1294
+ caughtRange = error;
1295
+ }
1296
+ if (!(caughtRange instanceof Error) || caughtRange instanceof AgentEventLogConflictError) fail("forking past the source length must reject with a plain Error");
1297
+ let caughtZero;
1298
+ try {
1299
+ await store.fork({
1300
+ threadId: "src",
1301
+ newThreadId: "fork-zero",
1302
+ upToIndex: 0
1303
+ });
1304
+ } catch (error) {
1305
+ caughtZero = error;
1306
+ }
1307
+ if (!(caughtZero instanceof Error) || caughtZero instanceof AgentEventLogConflictError) fail("forking with upToIndex 0 must reject with a plain Error");
1308
+ if (await store.length("fork-zero") !== 0) fail("a rejected fork must not create the target thread");
1309
+ }
1310
+ }
1311
+ ];
1312
+ /**
1313
+ * Validates a store against the reference's semantics: empty read + zero length
1314
+ * for unknown threads; single and multi-entry append; contiguity misuse guard;
1315
+ * stale-`expectedIndex` conflict with correct fields; an interleaved concurrent
1316
+ * append race (exactly one winner); entry-id uniqueness; `read({ from })`
1317
+ * incremental correctness; thread isolation; metadata round-trip; deep-copy
1318
+ * isolation on append and read; and the full fork contract.
1319
+ *
1320
+ * Without a `harness`, every case runs in sequence and the first violation
1321
+ * throws. With one, each case is registered as its own test:
1322
+ *
1323
+ * ```ts
1324
+ * import { describe, it } from 'vitest';
1325
+ * assertEventLogStoreConformance(createMyStore, { describe, it });
1326
+ * ```
1327
+ */
1328
+ async function assertEventLogStoreConformance(create, harness) {
1329
+ if (harness) {
1330
+ harness.describe("AgentEventLogStore conformance", () => {
1331
+ for (const testCase of cases) harness.it(testCase.name, () => testCase.run(create));
1332
+ });
1333
+ return;
175
1334
  }
176
- };
1335
+ for (const testCase of cases) await testCase.run(create);
1336
+ }
1337
+ //#endregion
1338
+ //#region src/run-agent.ts
177
1339
  /**
178
- * Thrown by {@link generateResult} when the run settles `idle` instead of
179
- * `done`: the machine paused for external input. Carries the idle `snapshot`
180
- * and `acceptedTypes` (the event types that could resume it, via
181
- * {@link getAcceptedEvents}). Use {@link runAgent} directly when idle is an
182
- * expected outcome you handle.
183
- */
184
- var AgentIdleError = class extends require_errors.AgentError {
185
- snapshot;
186
- acceptedTypes;
187
- constructor(snapshot, acceptedTypes) {
188
- super("agent-idle", `generateResult: the machine paused (idle) instead of completing. Resume it by calling runAgent with one of these events: ${acceptedTypes.length > 0 ? acceptedTypes.join(", ") : "(none)"}.`);
189
- this.name = "AgentIdleError";
190
- this.snapshot = snapshot;
191
- this.acceptedTypes = acceptedTypes;
1340
+ * Thrown by {@link runAgent} when a resume is given BOTH an `events` log and a
1341
+ * `snapshot` that claims a position in that log (`agentMeta.logIndex`), and the
1342
+ * two disagree about the state at that position. The log is the source of
1343
+ * truth, so this is a host bug (a snapshot cached from a different lineage, or
1344
+ * a snapshot mutated out of band), not a recoverable resume: pass the log
1345
+ * alone, or the matching snapshot.
1346
+ */
1347
+ var AgentSnapshotDivergedError = class extends require_decision.AgentError {
1348
+ constructor(expected, actual, logIndex) {
1349
+ super("snapshot-diverged", `runAgent: the resume snapshot disagrees with the event log at index ${logIndex}: the log replays to state hash '${expected}', the snapshot hashes '${actual}'.`);
1350
+ this.expected = expected;
1351
+ this.actual = actual;
1352
+ this.logIndex = logIndex;
1353
+ this.name = "AgentSnapshotDivergedError";
192
1354
  }
193
1355
  };
1356
+ function hashResumeSnapshot(snapshot) {
1357
+ if (snapshot !== null && typeof snapshot === "object" && "agentMeta" in snapshot) {
1358
+ const rest = {};
1359
+ for (const [key, value] of Object.entries(snapshot)) if (key !== "agentMeta") rest[key] = value;
1360
+ return getSnapshotStateHash(rest);
1361
+ }
1362
+ return getSnapshotStateHash(snapshot);
1363
+ }
1364
+ function readAgentMeta(snapshot) {
1365
+ const meta = snapshot?.agentMeta;
1366
+ return meta !== null && typeof meta === "object" ? meta : void 0;
1367
+ }
194
1368
  /**
195
1369
  * The version of the {@link AgentTraceEvent} envelope every trace event carries
196
1370
  * as `schemaVersion`. Bumped only on a breaking change to the envelope or any
@@ -209,8 +1383,8 @@ const TRACE_ENVELOPE_KEYS = [
209
1383
  "type",
210
1384
  "status",
211
1385
  "cause",
212
- "eventId",
213
1386
  "reasoning",
1387
+ "finishReason",
214
1388
  "chunk"
215
1389
  ];
216
1390
  /**
@@ -286,69 +1460,6 @@ function serializeTraceEvent(event, options = {}) {
286
1460
  }
287
1461
  return out;
288
1462
  }
289
- function snapshotNodes(snapshot) {
290
- return (snapshot.nodes ?? []).map((raw) => {
291
- const node = raw;
292
- return {
293
- id: node.id ?? "",
294
- key: node.key ?? "",
295
- ...node.description !== void 0 ? { description: node.description } : {},
296
- tags: [...node.tags ?? []],
297
- ...node.meta !== void 0 ? { meta: node.meta } : {},
298
- ownEvents: [...node.ownEvents ?? []],
299
- leaf: Object.keys(node.states ?? {}).length === 0
300
- };
301
- });
302
- }
303
- /**
304
- * The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
305
- * descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
306
- * your `getRequests` hook needs to build requests some other way, so host code
307
- * never touches xstate's private `snapshot._nodes`.
308
- */
309
- function getSnapshotNodes(snapshot) {
310
- return snapshotNodes(snapshot);
311
- }
312
- /**
313
- * Builds the {@link AgentStateRequest}s for a snapshot straight from its active
314
- * state nodes — the prompts-in-descriptions recipe as a function, so a
315
- * {@link RunAgentOptions.getRequests} hook is one line and never reaches into
316
- * xstate's private `snapshot._nodes`:
317
- *
318
- * ```ts
319
- * runAgent(machine, {
320
- * executors,
321
- * getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
322
- * });
323
- * ```
324
- *
325
- * Each described active node becomes one request: `prompt` from the node's
326
- * `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
327
- * from `meta.role` when present, `allowedEvents` scoped to the node's own
328
- * events, and an explicit `onDone` when the node has exactly one own event
329
- * (single-outcome states advance deterministically; anything else falls
330
- * through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
331
- * run settles idle for a human. Override any of it with `filter`/`map`.
332
- */
333
- function getSnapshotRequests(snapshot, options) {
334
- const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
335
- const requests = [];
336
- for (const node of snapshotNodes(snapshot)) {
337
- if (!filter(node)) continue;
338
- const system = node.meta?.role;
339
- const request = {
340
- model: options.model,
341
- prompt: node.description ?? "",
342
- kind: node.tags.includes("decision") ? "decision" : "text",
343
- ...system !== void 0 ? { system } : {},
344
- ...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
345
- ...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
346
- };
347
- const mapped = options.map ? options.map(request, node) : request;
348
- if (mapped) requests.push(mapped);
349
- }
350
- return requests;
351
- }
352
1463
  let nextRunAgentTraceId = 1;
353
1464
  /**
354
1465
  * Thrown into the invoke that would have made the call once
@@ -365,7 +1476,7 @@ let nextRunAgentTraceId = 1;
365
1476
  * ]
366
1477
  * ```
367
1478
  */
368
- var AgentMaxModelCallsExceededError = class extends require_errors.AgentError {
1479
+ var AgentMaxModelCallsExceededError = class extends require_decision.AgentError {
369
1480
  /** The budget that was exceeded (`options.maxModelCalls`). */
370
1481
  maxModelCalls;
371
1482
  constructor(maxModelCalls) {
@@ -573,10 +1684,10 @@ function bindTextLogic(logic, runCtx) {
573
1684
  return logic.withExecutor(async ({ request: rawRequest, self: selfArg, signal }) => {
574
1685
  const self = selfArg;
575
1686
  const { id, src } = selfIdAndSrc(self);
576
- const request = rawRequest.name === void 0 && src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? {
1687
+ const request = {
577
1688
  ...rawRequest,
578
- name: src
579
- } : rawRequest;
1689
+ name: rawRequest.name ?? (src !== "" && src !== "agent.generateText" && src !== "agent.streamText" ? src : id || src)
1690
+ };
580
1691
  const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
581
1692
  if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
582
1693
  const requestWithTools = {
@@ -598,6 +1709,8 @@ function bindTextLogic(logic, runCtx) {
598
1709
  request: agentRequest
599
1710
  }, self);
600
1711
  try {
1712
+ const callKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
1713
+ await runCtx.awaitJournal?.();
601
1714
  const raw = await executor(requestWithTools, {
602
1715
  onChunk: (chunk) => {
603
1716
  runCtx.emitTrace?.({
@@ -608,7 +1721,8 @@ function bindTextLogic(logic, runCtx) {
608
1721
  },
609
1722
  signal,
610
1723
  ...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
611
- ...id !== "" ? { requestId: id } : {}
1724
+ ...id !== "" ? { requestId: id } : {},
1725
+ ...callKey !== void 0 ? { callKey } : {}
612
1726
  });
613
1727
  const output = await require_decision.normalizeGeneratorResult(raw, id, {
614
1728
  request,
@@ -620,9 +1734,21 @@ function bindTextLogic(logic, runCtx) {
620
1734
  }, self);
621
1735
  }
622
1736
  });
1737
+ const responseMessages = raw?.messages;
1738
+ if (Array.isArray(responseMessages) && responseMessages.length > 0) {
1739
+ const parent = invokingActorOf(self, runCtx);
1740
+ const event = {
1741
+ type: require_decision.AGENT_MESSAGES_EVENT_TYPE,
1742
+ request: request.name,
1743
+ actorId: id,
1744
+ messages: responseMessages
1745
+ };
1746
+ if ((parent?.getSnapshot())?.can(event)) parent?.send(event);
1747
+ }
623
1748
  const rawReasoning = raw?.reasoning;
624
1749
  const reasoning = typeof rawReasoning === "string" ? rawReasoning : void 0;
625
1750
  const usage = require_decision.getCallUsage(raw);
1751
+ const finishReason = require_decision.getCallFinishReason(raw);
626
1752
  if (usage) runCtx.recordUsage?.(usage, {
627
1753
  kind: "text",
628
1754
  ...id !== "" ? { id } : {},
@@ -636,7 +1762,8 @@ function bindTextLogic(logic, runCtx) {
636
1762
  output,
637
1763
  raw,
638
1764
  ...reasoning !== void 0 ? { reasoning } : {},
639
- ...usage !== void 0 ? { usage } : {}
1765
+ ...usage !== void 0 ? { usage } : {},
1766
+ ...finishReason !== void 0 ? { finishReason } : {}
640
1767
  }, self);
641
1768
  return { output };
642
1769
  } catch (error) {
@@ -658,15 +1785,20 @@ function createCountingDecide(runCtx, self) {
658
1785
  }, self);
659
1786
  try {
660
1787
  const { id } = selfIdAndSrc(self);
1788
+ const invokeCallKey = id !== "" ? runCtx.callKey?.(id, self) : void 0;
1789
+ const callKey = invokeCallKey === void 0 ? void 0 : `${invokeCallKey}.${attemptRequest.attempts?.length ?? 0}`;
1790
+ await runCtx.awaitJournal?.();
661
1791
  const result = await runCtx.decide(runCtx.runId !== void 0 ? {
662
1792
  ...attemptRequest,
663
1793
  runId: runCtx.runId
664
1794
  } : attemptRequest, {
665
1795
  ...info,
666
1796
  ...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
667
- ...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
1797
+ ...info?.requestId === void 0 && id !== "" ? { requestId: id } : {},
1798
+ ...info?.callKey === void 0 && callKey !== void 0 ? { callKey } : {}
668
1799
  });
669
1800
  const usage = require_decision.getCallUsage(result);
1801
+ const finishReason = require_decision.getCallFinishReason(result);
670
1802
  if (usage) {
671
1803
  const { src } = selfIdAndSrc(self);
672
1804
  runCtx.recordUsage?.(usage, {
@@ -681,7 +1813,8 @@ function createCountingDecide(runCtx, self) {
681
1813
  request: attemptRequest,
682
1814
  output: result.event,
683
1815
  raw: result,
684
- ...usage !== void 0 ? { usage } : {}
1816
+ ...usage !== void 0 ? { usage } : {},
1817
+ ...finishReason !== void 0 ? { finishReason } : {}
685
1818
  }, self);
686
1819
  return result;
687
1820
  } catch (error) {
@@ -721,9 +1854,12 @@ function bindDecisionLogic(logic, runCtx) {
721
1854
  schemas: runCtx.schemas,
722
1855
  eventTypes: declaredEventTypes
723
1856
  }) : [];
1857
+ const lowered = logic.request(input);
724
1858
  const chosen = await require_decision.resolveDecision({
725
- ...logic.request(input),
1859
+ ...lowered,
726
1860
  id,
1861
+ name: lowered.name ?? id,
1862
+ input,
727
1863
  events
728
1864
  }, { decide: createCountingDecide(runCtx, self) }, {
729
1865
  maxRetries: logic.maxRetries,
@@ -828,19 +1964,26 @@ function provideBindContext(machine, executors, options) {
828
1964
  }) : void 0,
829
1965
  consumeModelCall: () => {},
830
1966
  recordUsage: (usage, source, self) => {
831
- deliverUsageEvent(usage, source ?? {}, () => self?._parent);
1967
+ deliverUsageEvent({
1968
+ type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
1969
+ ...source,
1970
+ usage
1971
+ }, () => self?._parent);
832
1972
  },
833
1973
  actorHolder: { actorRef: void 0 },
834
1974
  schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
835
1975
  };
836
1976
  }
837
1977
  /**
838
- * The single reserved-`@agent.usage` delivery seam, shared by both bind paths:
839
- * after a bound call settles with reported usage, send the event to the machine
840
- * actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
1978
+ * The single reserved-`@agent.usage` DELIVERY seam, shared by both bind paths:
1979
+ * after a bound call settles with reported usage, send the prebuilt `event` to
1980
+ * the machine actor `resolveActorRef` names — the run's root actor on the `runAgent` path,
841
1981
  * the settling request actor's `self._parent` (always the invoking machine
842
1982
  * under a live `createActor` tree) on the `provideExecutors` path.
843
1983
  *
1984
+ * Journaling is separate and unconditional (see `recordUsage`): a call's tokens
1985
+ * are recorded in the event log whether or not the machine reacts to them.
1986
+ *
844
1987
  * Gating is identical on both: the target snapshot must be active, must declare
845
1988
  * an `'@agent.usage'` transition — explicitly, or through a catch-all
846
1989
  * `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
@@ -853,16 +1996,11 @@ function provideBindContext(machine, executors, options) {
853
1996
  * here, so an invoked child machine that was not itself passed through
854
1997
  * `provideExecutors` reports nothing. @internal
855
1998
  */
856
- function deliverUsageEvent(usage, source, resolveActorRef, onDropped) {
1999
+ function deliverUsageEvent(event, resolveActorRef, onDropped) {
857
2000
  const actorRef = resolveActorRef();
858
2001
  if (!actorRef) return;
859
2002
  const snapshot = actorRef.getSnapshot();
860
2003
  if (snapshot?.status !== "active" || !declaresUsageTransition(snapshot)) return;
861
- const event = {
862
- type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
863
- ...source,
864
- usage
865
- };
866
2004
  if (onDropped?.(event)) return;
867
2005
  if (!snapshot.can(event)) return;
868
2006
  actorRef.send(event);
@@ -915,7 +2053,7 @@ function resolveMachineInput(machine, input) {
915
2053
  try {
916
2054
  return require_decision.validateSchemaSync(schema, input);
917
2055
  } catch (error) {
918
- throw new require_errors.AgentError("invalid-machine-input", `runAgent: machine input failed validation against the declared input schema: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2056
+ throw new require_decision.AgentError("invalid-machine-input", `runAgent: machine input failed validation against the declared input schema: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
919
2057
  }
920
2058
  }
921
2059
  /**
@@ -995,47 +2133,47 @@ function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
995
2133
  * lives in an adapter — use `createAiSdkExecutors` from '@statelyai/agent/ai-sdk'.
996
2134
  */
997
2135
  async function runAgent(machine, options) {
998
- return createAgentSession(machine, options, { oneShot: true }).settled();
2136
+ const store = options.store;
2137
+ if (store === void 0) return createAgentSession(machine, options).settled();
2138
+ const threadId = options.threadId;
2139
+ if (threadId === void 0 || threadId === "") throw new require_decision.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
2140
+ if (options.events !== void 0) {
2141
+ const storedThread = await store.read(threadId);
2142
+ if (storedThread.length !== options.events.length) throw new AgentEventLogConflictError(threadId, options.events.length, storedThread.length);
2143
+ assertThreadMatchesEvents(threadId, storedThread, options.events);
2144
+ return createAgentSession(machine, options).settled();
2145
+ }
2146
+ const stored = await store.read(threadId);
2147
+ return createAgentSession(machine, stored.length > 0 ? {
2148
+ ...options,
2149
+ events: stored
2150
+ } : options).settled();
999
2151
  }
1000
2152
  /**
1001
- * Session mode: {@link runAgent}'s engine with a long-lived actor. Use it when
1002
- * the agent is a *session* fed by external events (chat turns, device or
1003
- * timer events, a socket) rather than a one-shot job — you keep the log,
1004
- * budget, traces, and idle semantics that bare `provideExecutors` +
1005
- * `createActor` would forfeit.
1006
- *
1007
- * ```ts
1008
- * const session = createAgentActor(machine, { input, executors });
1009
- * let result = await session.settled(); // first quiescence
1010
- * while (result.status === "idle") {
1011
- * session.actor.send(await nextUserEvent(result.snapshot));
1012
- * result = await session.settled(); // next quiescence, same log
1013
- * }
1014
- * session.stop();
1015
- * ```
1016
- *
1017
- * Accepts the same options as {@link runAgent} (including `snapshot`/`events`
1018
- * resume). Not yet supported in session mode: `getRequests` re-interpretation
1019
- * across cycles behaves per-cycle exactly as in `runAgent`.
2153
+ * Entry-by-entry check that the caller's `events` IS the store's thread. Equal
2154
+ * lengths prove nothing: a fork, a rolled-back replay, or a concurrent writer
2155
+ * that appended and truncated all produce a divergent log of the same size,
2156
+ * and appending this run's entries onto it would splice two lineages together.
1020
2157
  */
1021
- function createAgentActor(machine, options) {
1022
- return createAgentSession(machine, options, { oneShot: false });
2158
+ function assertThreadMatchesEvents(threadId, stored, events) {
2159
+ for (let index = 0; index < stored.length; index++) {
2160
+ const storedEntry = stored[index];
2161
+ const givenEntry = events[index];
2162
+ const storedHash = storedEntry.verification?.stateHash;
2163
+ const givenHash = givenEntry.verification?.stateHash;
2164
+ if (!(storedEntry.id === givenEntry.id && storedEntry.index === givenEntry.index && canonicalEventJson(storedEntry.event) === canonicalEventJson(givenEntry.event) && (storedHash === void 0 || givenHash === void 0 || storedHash === givenHash))) throw new require_decision.AgentError("event-log-conflict", `runAgent: the given \`events\` diverge from thread "${threadId}" at index ${index} (stored entry '${storedEntry.id}', given '${givenEntry.id}') — the log passed in is not this thread's log.`);
2165
+ }
1023
2166
  }
1024
- function createAgentSession(machine, options, lifecycle) {
2167
+ function createAgentSession(machine, options) {
1025
2168
  const maxModelCalls = options.maxModelCalls ?? 100;
1026
2169
  let modelCallCount = 0;
1027
2170
  let budgetExceeded = false;
1028
2171
  let warnedNonSerializable = false;
1029
- let warnedHeuristicIdle = false;
1030
2172
  const runId = `run_${nextRunAgentTraceId++}`;
1031
2173
  let traceSeq = 0;
1032
2174
  const resolvedInput = resolveMachineInput(machine, options.input);
1033
2175
  const machineId = machine.config.id ?? machine.id ?? "(machine)";
1034
2176
  const machineVersion = require_decision.resolveMachineVersion(machine);
1035
- const agentMeta = {
1036
- machineId,
1037
- version: machineVersion
1038
- };
1039
2177
  const onTrace = createTraceDispatch({
1040
2178
  onTrace: (payload) => {
1041
2179
  options.onTrace?.({
@@ -1060,25 +2198,109 @@ function createAgentSession(machine, options, lifecycle) {
1060
2198
  }
1061
2199
  modelCallCount += 1;
1062
2200
  };
1063
- const tokenTotals = {};
1064
2201
  const cycleGate = { isResolved: () => false };
1065
- const recordUsage = (usage, source = {}) => {
1066
- for (const field of require_decision.AGENT_USAGE_TOKEN_FIELDS) {
1067
- const value = usage[field];
1068
- if (typeof value === "number" && Number.isFinite(value)) tokenTotals[field] = (tokenTotals[field] ?? 0) + value;
1069
- }
1070
- deliverUsageEvent(usage, source, () => actorHolder.actorRef, (event) => {
1071
- if (!cycleGate.isResolved()) return false;
1072
- onTrace({
1073
- type: "usage.dropped",
1074
- event,
1075
- reason: "settled"
2202
+ const logEntries = [];
2203
+ const store = options.store;
2204
+ const threadId = options.threadId;
2205
+ if (store !== void 0 && (threadId === void 0 || threadId === "")) throw new require_decision.AgentError("missing-thread-id", "runAgent: `threadId` is required when `store` is given — it names the log thread to read and append to.");
2206
+ let journalChain = Promise.resolve();
2207
+ let journalPending = 0;
2208
+ let journalError;
2209
+ const journalEntry = (entry) => {
2210
+ if (store === void 0) return;
2211
+ journalPending++;
2212
+ journalChain = journalChain.then(async () => {
2213
+ if (journalError !== void 0) return;
2214
+ await store.append({
2215
+ threadId,
2216
+ expectedIndex: entry.index,
2217
+ entries: [entry]
1076
2218
  });
1077
- return true;
2219
+ }).catch((error) => {
2220
+ journalError ??= error;
2221
+ failRunOnJournalError(error);
2222
+ }).finally(() => {
2223
+ journalPending--;
2224
+ });
2225
+ };
2226
+ /**
2227
+ * Every journal write ISSUED SO FAR, including the stragglers appended after
2228
+ * the run settled. Re-reads the chain tail until it stops moving, so a write
2229
+ * queued while an earlier one was in flight is covered too. Never rejects: a
2230
+ * failed write already settled the run with `cause: 'journal'`.
2231
+ */
2232
+ const drainJournal = async () => {
2233
+ if (store === void 0) return;
2234
+ let tail = journalChain;
2235
+ for (;;) {
2236
+ await tail;
2237
+ if (journalPending === 0 && journalChain === tail) return;
2238
+ tail = journalChain;
2239
+ }
2240
+ };
2241
+ /** The barrier: settle the pending chain, then surface any failure. */
2242
+ const awaitJournal = async () => {
2243
+ if (store === void 0) return;
2244
+ while (journalPending > 0) await journalChain;
2245
+ if (journalError !== void 0) throw journalError;
2246
+ };
2247
+ const failRunOnJournalError = (error) => {
2248
+ if (settled) return;
2249
+ settle({
2250
+ status: "error",
2251
+ cause: "journal",
2252
+ error,
2253
+ snapshot: actor.getSnapshot()
1078
2254
  });
1079
2255
  };
2256
+ let resumedLogLength = 0;
2257
+ let logExecutionId;
2258
+ const verificationEnabled = options.verification !== false;
2259
+ let lastRootSnapshot;
2260
+ let logMachine = machine;
2261
+ let loggingEnabled = true;
2262
+ const appendLogEntry = (event, snapshot) => {
2263
+ if (!loggingEnabled) return;
2264
+ const entry = createReplayEntry(logMachine, logEntries, event, {
2265
+ machineVersion,
2266
+ verification: verificationEnabled,
2267
+ ...verificationEnabled && snapshot !== void 0 ? { snapshot } : {}
2268
+ });
2269
+ logEntries.push(entry);
2270
+ journalEntry(entry);
2271
+ options.onEvent?.(entry);
2272
+ return entry;
2273
+ };
2274
+ const usageEntrySnapshot = () => {
2275
+ const current = lastRootSnapshot;
2276
+ if (current && (current.status !== "active" || !declaresUsageTransition(current))) return current;
2277
+ };
2278
+ const recordUsage = (usage, source = {}) => {
2279
+ const event = {
2280
+ type: require_setup_agent.AGENT_USAGE_EVENT_TYPE,
2281
+ ...source,
2282
+ usage
2283
+ };
2284
+ const journal = { event };
2285
+ pendingUsageJournal = journal;
2286
+ try {
2287
+ deliverUsageEvent(event, () => actorHolder.actorRef, (dropped) => {
2288
+ if (!cycleGate.isResolved()) return false;
2289
+ onTrace({
2290
+ type: "usage.dropped",
2291
+ event: dropped,
2292
+ reason: "settled"
2293
+ });
2294
+ return true;
2295
+ });
2296
+ } finally {
2297
+ pendingUsageJournal = void 0;
2298
+ }
2299
+ if (journal.entry === void 0) appendLogEntry(event, usageEntrySnapshot());
2300
+ };
2301
+ let pendingUsageJournal;
1080
2302
  const runUsage = () => ({
1081
- ...tokenTotals,
2303
+ ...getUsageFromEvents(logEntries.slice(resumedLogLength)),
1082
2304
  modelCalls: modelCallCount
1083
2305
  });
1084
2306
  const warnNonSerializableContext = (snapshot) => {
@@ -1094,18 +2316,23 @@ function createAgentSession(machine, options, lifecycle) {
1094
2316
  console.warn(`runAgent: context holds value(s) that will not survive snapshot persist/resume (JSON round-trip): ${offending.join(", ")}. Persist only JSON-serializable context, or convert these before the run settles.`);
1095
2317
  };
1096
2318
  const provided = machine.provide({ actors: options.actors });
2319
+ logMachine = provided;
1097
2320
  const effectiveSources = provided.sources.actors;
1098
- assertBindable(provided, effectiveSources, options.executors ?? {});
1099
- 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.");
2321
+ const executors = {
2322
+ ...require_decision.getRegisteredAgentExecutionOptions(machine).models?.[require_decision.DEFAULT_AGENT_EXECUTORS]?.(),
2323
+ ...options.executors
2324
+ };
2325
+ assertBindable(provided, effectiveSources, executors);
1100
2326
  const actorHolder = { actorRef: void 0 };
1101
2327
  const runCtx = {
1102
- generateText: options.executors?.generateText,
1103
- streamText: options.executors?.streamText,
1104
- decide: options.executors?.decide,
2328
+ generateText: executors.generateText,
2329
+ streamText: executors.streamText,
2330
+ decide: executors.decide,
1105
2331
  emitTrace: onTrace,
1106
2332
  consumeModelCall,
1107
2333
  recordUsage,
1108
2334
  actorHolder,
2335
+ awaitJournal,
1109
2336
  runId,
1110
2337
  schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas
1111
2338
  };
@@ -1137,103 +2364,178 @@ function createAgentSession(machine, options, lifecycle) {
1137
2364
  }
1138
2365
  }
1139
2366
  const boundMachine = provided.provide({ actors: wrappedSources });
1140
- const declaredIdlePredicate = options.isIdle ?? require_decision.getMachineIdlePredicate(machine);
1141
- const isIdle = declaredIdlePredicate ?? (() => false);
1142
- const machineDeclaresMigrate = typeof machine.config.migrate === "function";
2367
+ const isIdle = require_decision.getMachineIdlePredicate(machine) ?? isAgentIdle;
2368
+ const isIntentionalIdle = (snapshot) => isIdle(snapshot) || collectPendingUserInputs(snapshot).length > 0;
2369
+ const resumeEvents = options.events;
1143
2370
  let effectiveSnapshot = options.snapshot;
1144
- if (effectiveSnapshot !== void 0) {
1145
- const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
1146
- if (from !== void 0 && from !== machineVersion && !(machineDeclaresMigrate && !options.migrateSnapshot)) {
1147
- const info = {
1148
- from,
1149
- to: machineVersion
1150
- };
1151
- if (options.migrateSnapshot) effectiveSnapshot = options.migrateSnapshot(effectiveSnapshot, info);
1152
- else {
1153
- const mode = options.onVersionMismatch ?? "throw";
1154
- if (mode === "throw") throw new AgentSnapshotVersionMismatchError(from, machineVersion, machineId);
1155
- if (mode === "warn") console.warn(`runAgent: resuming a snapshot stamped with machine version '${from}' against machine '${machineId}' at version '${machineVersion}'. Structural changes may not resume cleanly.`);
2371
+ /** Deferred until after the illegal-resume check, so a rejected resume emits no entries. */
2372
+ let seedInitEntry;
2373
+ const restoredPersistedSnapshot = () => {
2374
+ try {
2375
+ const restored = logMachine.restoreSnapshot(options.snapshot);
2376
+ return logMachine.getPersistedSnapshot(restored);
2377
+ } catch {
2378
+ return;
2379
+ }
2380
+ };
2381
+ /**
2382
+ * Appends the log's reserved first entry, trying each candidate `init` in
2383
+ * turn and, for each, an unverified entry if the hash cannot be computed.
2384
+ * A machine or snapshot that defeats every attempt leaves the run WITHOUT a
2385
+ * log rather than failing it: `runAgent` predates the log, and an exotic
2386
+ * machine that cannot be folded must still run.
2387
+ */
2388
+ const appendInitEntry = (candidates, metadata) => {
2389
+ for (const init of candidates) for (const verification of verificationEnabled ? [true, false] : [false]) try {
2390
+ const entry = initEntry(logMachine, init, {
2391
+ machineVersion,
2392
+ verification,
2393
+ metadata
2394
+ });
2395
+ logEntries.push(entry);
2396
+ journalEntry(entry);
2397
+ options.onEvent?.(entry);
2398
+ return;
2399
+ } catch {}
2400
+ loggingEnabled = false;
2401
+ };
2402
+ /** Init forms for a snapshot resume, most faithful first. */
2403
+ const snapshotInitCandidates = () => {
2404
+ const restored = restoredPersistedSnapshot();
2405
+ return [...restored !== void 0 ? [{ snapshot: restored }] : [], { snapshot: options.snapshot }];
2406
+ };
2407
+ if (resumeEvents !== void 0 && resumeEvents.length > 0) {
2408
+ validateReplayEntries(resumeEvents);
2409
+ const tail = resumeEvents[resumeEvents.length - 1];
2410
+ const inheritedExecutionId = getLogExecutionId(resumeEvents);
2411
+ if (resumeEvents[0].machineId !== machineId) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
2412
+ machineId,
2413
+ machineVersion
2414
+ }, {
2415
+ machineId: resumeEvents[0].machineId,
2416
+ machineVersion: tail.machineVersion
2417
+ });
2418
+ if (tail.machineVersion === machineVersion) {
2419
+ logEntries.push(...resumeEvents);
2420
+ resumedLogLength = logEntries.length;
2421
+ logExecutionId = inheritedExecutionId;
2422
+ const cachedMeta = readAgentMeta(options.snapshot);
2423
+ const tailStateHash = tail.verification?.stateHash;
2424
+ if (!(options.snapshot !== void 0 && inheritedExecutionId !== void 0 && cachedMeta?.logId === inheritedExecutionId && cachedMeta.logIndex === resumeEvents.length && tailStateHash !== void 0 && hashResumeSnapshot(options.snapshot) === tailStateHash)) {
2425
+ const cachedIndex = cachedMeta?.logIndex;
2426
+ if (options.snapshot !== void 0 && typeof cachedIndex === "number" && cachedIndex >= 1 && cachedIndex <= resumeEvents.length && resumeEvents[cachedIndex - 1].verification?.stateHash !== void 0) {
2427
+ const expected = getSnapshotStateHash(replay(logMachine, resumeEvents.slice(0, cachedIndex), {
2428
+ machineVersion,
2429
+ verify: false
2430
+ }).persistedSnapshot);
2431
+ const actual = hashResumeSnapshot(options.snapshot);
2432
+ if (expected !== actual) throw new AgentSnapshotDivergedError(expected, actual, cachedIndex);
2433
+ }
2434
+ effectiveSnapshot = replay(logMachine, resumeEvents, {
2435
+ machineVersion,
2436
+ verify: true
2437
+ }).persistedSnapshot;
1156
2438
  }
2439
+ } else {
2440
+ if (options.snapshot === void 0) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
2441
+ machineId,
2442
+ machineVersion
2443
+ }, {
2444
+ machineId: tail.machineId,
2445
+ machineVersion: tail.machineVersion
2446
+ });
2447
+ const bridgeMeta = readAgentMeta(options.snapshot);
2448
+ if (inheritedExecutionId === void 0 || bridgeMeta?.logId !== inheritedExecutionId) throw new AgentMachineVersionMismatchError(tail.id, tail.index, {
2449
+ machineId,
2450
+ machineVersion
2451
+ }, {
2452
+ machineId: tail.machineId,
2453
+ machineVersion: tail.machineVersion
2454
+ });
2455
+ const bridgeTailHash = tail.verification?.stateHash;
2456
+ const bridgeSnapshotHash = hashResumeSnapshot(options.snapshot);
2457
+ if (bridgeMeta.logIndex !== resumeEvents.length) throw new AgentSnapshotDivergedError(bridgeTailHash ?? "(unrecorded)", bridgeSnapshotHash, resumeEvents.length);
2458
+ if (bridgeTailHash !== void 0 && bridgeSnapshotHash !== bridgeTailHash) throw new AgentSnapshotDivergedError(bridgeTailHash, bridgeSnapshotHash, resumeEvents.length);
2459
+ logExecutionId = inheritedExecutionId;
2460
+ const migratedFrom = {
2461
+ machineVersion: tail.machineVersion,
2462
+ logIndex: resumeEvents.length
2463
+ };
2464
+ seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), {
2465
+ ...logExecutionId !== void 0 ? { executionId: logExecutionId } : {},
2466
+ migratedFrom
2467
+ });
1157
2468
  }
2469
+ } else if (options.snapshot !== void 0) {
2470
+ logExecutionId = crypto.randomUUID();
2471
+ seedInitEntry = () => appendInitEntry(snapshotInitCandidates(), { executionId: logExecutionId });
2472
+ } else {
2473
+ logExecutionId = crypto.randomUUID();
2474
+ seedInitEntry = () => appendInitEntry([resolvedInput !== void 0 ? { input: resolvedInput } : {}], { executionId: logExecutionId });
1158
2475
  }
1159
- const resumeEvents = options.events;
1160
- if (effectiveSnapshot === void 0 && options.events !== void 0 && options.events[0]?.event.type === "@agent.init") {
1161
- const { snapshot: replayedSnapshot } = require_setup_agent.replay(machine, resumeEvents, { machineVersion });
1162
- effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
1163
- }
1164
- const machineOwnVersion = machine.version;
1165
- if (!machineDeclaresMigrate && effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
1166
- const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
1167
- if (machineOwnVersion === void 0) delete aligned.version;
1168
- else aligned.version = machineOwnVersion;
1169
- delete aligned.machine;
1170
- effectiveSnapshot = aligned;
1171
- }
1172
- const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
1173
- const messages = typeof options.messages === "function" ? [...options.messages([...priorMessages])] : [...priorMessages, ...options.messages ?? []];
1174
- const stampSettledSnapshot = (snapshot) => {
1175
- if (!snapshot || typeof snapshot !== "object") return;
1176
- snapshot.agentMeta = agentMeta;
1177
- if (!options.getRequests && !options.messages && messages.length === 0) return;
1178
- snapshot.messages = [...messages];
1179
- };
1180
- if (effectiveSnapshot !== void 0 && options.event !== void 0) {
1181
- const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
1182
- const eventType = options.event.type;
1183
- if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
1184
- }
1185
- const replayEvents = [...resumeEvents ?? []];
1186
- require_setup_agent.validateReplayEntries(replayEvents, {
1187
- machineId,
1188
- machineVersion
1189
- }, "runAgent events");
1190
- const replayEventIds = new Set(replayEvents.map((entry) => entry.id));
1191
- const hasCompleteReplayHistory = replayEvents[0]?.event.type === "@agent.init" || effectiveSnapshot === void 0 && replayEvents.length === 0;
1192
- const appendReplayEvent = (event) => {
1193
- const entry = require_setup_agent.createReplayEntry(machine, replayEvents, event, {
1194
- machineVersion,
1195
- verification: hasCompleteReplayHistory
1196
- });
1197
- if (replayEventIds.has(entry.id)) throw new Error(`runAgent generated duplicate event id '${entry.id}'.`);
1198
- replayEventIds.add(entry.id);
1199
- replayEvents.push(entry);
1200
- options.onEvent?.(entry);
1201
- return entry;
2476
+ const resumedHistory = [...logEntries];
2477
+ const siteCallCounts = /* @__PURE__ */ new Map();
2478
+ const mintedCallKeys = /* @__PURE__ */ new WeakMap();
2479
+ runCtx.callKey = (siteId, self) => {
2480
+ if (logExecutionId === void 0) return;
2481
+ const memoized = self === void 0 ? void 0 : mintedCallKeys.get(self);
2482
+ if (memoized !== void 0) return memoized;
2483
+ const startedHere = siteCallCounts.get(siteId) ?? 0;
2484
+ siteCallCounts.set(siteId, startedHere + 1);
2485
+ const key = `${logExecutionId}:${siteId}#${agentCallOccurrence(resumedHistory, siteId) + startedHere}`;
2486
+ if (self !== void 0) mintedCallKeys.set(self, key);
2487
+ return key;
1202
2488
  };
1203
- if (replayEvents.length === 0 && effectiveSnapshot === void 0) {
1204
- const entry = require_setup_agent.initEntry(machine, resolvedInput, { machineVersion });
1205
- replayEventIds.add(entry.id);
1206
- replayEvents.push(entry);
1207
- options.onEvent?.(entry);
1208
- }
2489
+ const resumeEventToSend = options.event;
2490
+ let ignoredEvent;
2491
+ seedInitEntry?.();
1209
2492
  let settled = false;
1210
- let finalized = false;
2493
+ let journalAtSettle;
1211
2494
  cycleGate.isResolved = () => settled;
1212
2495
  let lastResult;
1213
2496
  const waiters = [];
1214
2497
  let idleTimer;
1215
2498
  let actor;
1216
- let deliveringResumeEvent = options.event !== void 0;
2499
+ let actorStarted = false;
2500
+ let deliveringResumeEvent = resumeEventToSend !== void 0;
1217
2501
  const settle = (outcome) => {
1218
2502
  if (settled) return;
1219
2503
  settled = true;
2504
+ let persistedSnapshot;
2505
+ let persistenceError;
2506
+ if (actorStarted && outcome.status !== "done") try {
2507
+ persistedSnapshot = actor.getPersistedSnapshot();
2508
+ } catch (error) {
2509
+ persistenceError = error;
2510
+ }
2511
+ const logIndexAtSettle = logEntries.length;
2512
+ journalAtSettle = journalChain;
2513
+ const persist = () => {
2514
+ if (persistenceError !== void 0) throw persistenceError;
2515
+ persistedSnapshot ??= actor.getPersistedSnapshot();
2516
+ persistedSnapshot.agentMeta = {
2517
+ machineId,
2518
+ version: machineVersion,
2519
+ ...logExecutionId !== void 0 ? { logId: logExecutionId } : {},
2520
+ logIndex: logIndexAtSettle
2521
+ };
2522
+ return persistedSnapshot;
2523
+ };
1220
2524
  const result = {
1221
2525
  ...outcome,
1222
- events: [...replayEvents],
1223
- usage: runUsage()
2526
+ events: [...logEntries],
2527
+ persist,
2528
+ drain: drainJournal,
2529
+ usage: runUsage(),
2530
+ ...ignoredEvent !== void 0 ? { ignored: ignoredEvent } : {}
1224
2531
  };
1225
2532
  if (idleTimer !== void 0) clearTimeout(idleTimer);
1226
- stampSettledSnapshot(result.snapshot);
1227
- if ("persistedSnapshot" in result) stampSettledSnapshot(result.persistedSnapshot);
1228
2533
  onTrace({
1229
2534
  type: "run.end",
1230
2535
  ...outcome
1231
2536
  });
1232
- if (lifecycle.oneShot || result.status !== "idle") {
1233
- finalized = true;
1234
- if (options.signal) options.signal.removeEventListener("abort", onAbort);
1235
- actor.stop();
1236
- }
2537
+ if (options.signal) options.signal.removeEventListener("abort", onAbort);
2538
+ actor.stop();
1237
2539
  lastResult = result;
1238
2540
  for (const resolve of waiters.splice(0)) resolve(result);
1239
2541
  };
@@ -1246,90 +2548,22 @@ function createAgentSession(machine, options, lifecycle) {
1246
2548
  });
1247
2549
  };
1248
2550
  const settleIdle = (current) => {
1249
- warnNonSerializableContext(current);
1250
- const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
1251
- settle({
1252
- status: "idle",
1253
- snapshot: current,
1254
- persistedSnapshot: actor.getPersistedSnapshot(),
1255
- ...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
1256
- });
1257
- };
1258
- let interpreting = false;
1259
- let interpretSeq = 0;
1260
- const appendToLog = (...items) => {
1261
- messages.push(...items);
1262
- if (options.onMessage) {
1263
- const info = {
1264
- runId,
1265
- machineId,
1266
- machineVersion
1267
- };
1268
- for (const item of items) options.onMessage(item, info);
1269
- }
1270
- };
1271
- const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
1272
- const settleInterpretError = (error) => {
1273
- settle({
1274
- status: "error",
1275
- cause: runErrorCause(error),
1276
- error,
1277
- snapshot: actor.getSnapshot()
1278
- });
1279
- };
1280
- const passDeps = {
1281
- getSnapshot: () => actor.getSnapshot(),
1282
- send: (event) => actor.send(event),
1283
- isSettled: () => settled,
1284
- messages,
1285
- appendToLog,
1286
- generateText: runCtx.generateText,
1287
- decide: runCtx.decide ? createCountingDecide(runCtx, void 0) : void 0,
1288
- consumeModelCall,
1289
- recordUsage,
1290
- nextRequestId: () => `interpret_${++interpretSeq}`,
1291
- onTrace,
1292
- schemas: runCtx.schemas,
1293
- signal: options.signal
1294
- };
1295
- const maybeInterpret = (snapshot) => {
1296
- if (!options.getRequests || settled) return false;
1297
- if (interpreting) return true;
1298
- let requested;
1299
- try {
1300
- requested = options.getRequests(snapshot, { messages });
1301
- } catch (error) {
1302
- settleInterpretError(error);
1303
- return true;
1304
- }
1305
- const requests = (Array.isArray(requested) ? requested : requested ? [requested] : []).filter((stateRequest) => Boolean(stateRequest));
1306
- if (requests.length === 0) return false;
1307
- interpreting = true;
1308
- runStateRequestPass(requests, passDeps).then(({ sentAny }) => {
1309
- if (settled || sentAny) return;
1310
- const current = actor.getSnapshot();
1311
- if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
1312
- }).catch((error) => settleInterpretError(error)).finally(() => {
1313
- interpreting = false;
1314
- if (!settled) scheduleIdleCheck();
2551
+ warnNonSerializableContext(current);
2552
+ const pendingUserInputs = userInputIsPlaceholder ? collectPendingUserInputs(current) : [];
2553
+ settle({
2554
+ status: "idle",
2555
+ snapshot: current,
2556
+ ...pendingUserInputs.length > 0 ? { pendingUserInputs } : {}
1315
2557
  });
1316
- return true;
1317
2558
  };
2559
+ const runErrorCause = (error) => budgetExceeded ? "max-model-calls" : wrapsDecisionExhausted(error) ? "decision-exhausted" : "machine";
1318
2560
  const scheduleIdleCheck = () => {
1319
2561
  if (idleTimer !== void 0) clearTimeout(idleTimer);
1320
2562
  idleTimer = setTimeout(() => {
1321
2563
  idleTimer = void 0;
1322
2564
  if (settled) return;
1323
2565
  const current = actor.getSnapshot();
1324
- if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1325
- if (!maybeInterpret(current)) {
1326
- if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
1327
- warnedHeuristicIdle = true;
1328
- console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no idle predicate declared). This is best-effort; for deterministic idle detection, declare setupAgent({ isIdle }) or pass runAgent(machine, { isIdle }), e.g. (s) => s.hasTag('waiting').");
1329
- }
1330
- settleIdle(current);
1331
- }
1332
- }
2566
+ if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
1333
2567
  }, 0);
1334
2568
  };
1335
2569
  actor = (0, xstate.createActor)(boundMachine, {
@@ -1339,20 +2573,16 @@ function createAgentSession(machine, options, lifecycle) {
1339
2573
  if (typeof options.inspect === "function") options.inspect(event);
1340
2574
  else options.inspect?.next?.(event);
1341
2575
  if (event.type !== "@xstate.transition" || event.actorRef !== actor.ref) return;
1342
- if (settled) {
1343
- if (finalized) return;
1344
- settled = false;
1345
- lastResult = void 0;
1346
- }
2576
+ if (settled) return;
1347
2577
  const snapshot = event.snapshot;
1348
- let eventId;
1349
- if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) eventId = appendReplayEvent(event.event).id;
1350
- else if (event.event.type === "@xstate.init") eventId = replayEvents[0]?.event.type === "@agent.init" ? replayEvents[0].id : void 0;
2578
+ lastRootSnapshot = snapshot;
2579
+ if (resumeEventToSend !== void 0 && event.event === resumeEventToSend && event.microsteps.length === 0 && event.actions.length === 0 && event.sent.length === 0) ignoredEvent = resumeEventToSend;
2580
+ if (pendingUsageJournal !== void 0 && event.event === pendingUsageJournal.event) pendingUsageJournal.entry = appendLogEntry(event.event, snapshot);
2581
+ else if (event.event.type !== "@xstate.init" && (event.sourceRef !== event.actorRef || event.event.type === "xstate.timer")) appendLogEntry(event.event, snapshot);
1351
2582
  onTrace({
1352
2583
  type: "machine.transition",
1353
2584
  snapshot,
1354
- event: event.event,
1355
- ...eventId !== void 0 ? { eventId } : {}
2585
+ event: event.event
1356
2586
  });
1357
2587
  if (snapshot.status === "done") {
1358
2588
  settle({
@@ -1380,13 +2610,12 @@ function createAgentSession(machine, options, lifecycle) {
1380
2610
  });
1381
2611
  return;
1382
2612
  }
1383
- if (!deliveringResumeEvent && isIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
2613
+ if (!deliveringResumeEvent && isIntentionalIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1384
2614
  queueMicrotask(() => {
1385
2615
  if (settled) return;
1386
2616
  const current = actor.getSnapshot();
1387
- if (isIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
1388
- if (!maybeInterpret(current)) settleIdle(current);
1389
- } else scheduleIdleCheck();
2617
+ if (isIntentionalIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) settleIdle(current);
2618
+ else scheduleIdleCheck();
1390
2619
  });
1391
2620
  return;
1392
2621
  }
@@ -1402,19 +2631,13 @@ function createAgentSession(machine, options, lifecycle) {
1402
2631
  });
1403
2632
  });
1404
2633
  for (const [type, handler] of Object.entries(options.on ?? {})) if (typeof handler === "function") actor.on(type, handler);
1405
- const sessionApi = {
1406
- actor,
1407
- get events() {
1408
- return replayEvents;
1409
- },
1410
- usage: runUsage,
1411
- settled: () => settled && lastResult !== void 0 ? Promise.resolve(lastResult) : new Promise((resolve) => {
2634
+ const sessionApi = { settled: async () => {
2635
+ const result = settled && lastResult !== void 0 ? lastResult : await new Promise((resolve) => {
1412
2636
  waiters.push(resolve);
1413
- }),
1414
- stop: () => {
1415
- actor.stop();
1416
- }
1417
- };
2637
+ });
2638
+ await journalAtSettle;
2639
+ return result;
2640
+ } };
1418
2641
  if (options.signal) {
1419
2642
  if (options.signal.aborted) {
1420
2643
  settle({
@@ -1431,47 +2654,38 @@ function createAgentSession(machine, options, lifecycle) {
1431
2654
  type: "run.start",
1432
2655
  ...resolvedInput !== void 0 ? { input: resolvedInput } : {},
1433
2656
  ...effectiveSnapshot !== void 0 ? { snapshot: effectiveSnapshot } : {},
1434
- ...options.event !== void 0 ? { event: options.event } : {}
2657
+ ...resumeEventToSend !== void 0 ? { event: resumeEventToSend } : {}
1435
2658
  });
2659
+ actorStarted = true;
1436
2660
  actor.start();
1437
- if (options.event) {
2661
+ if (!settled) {
2662
+ const startedSnapshot = actor.getSnapshot();
2663
+ lastRootSnapshot ??= startedSnapshot;
2664
+ if (startedSnapshot.status === "done") settle({
2665
+ status: "done",
2666
+ output: startedSnapshot.output,
2667
+ snapshot: startedSnapshot
2668
+ });
2669
+ else if (startedSnapshot.status === "error") settle({
2670
+ status: "error",
2671
+ cause: runErrorCause(startedSnapshot.error),
2672
+ error: startedSnapshot.error,
2673
+ snapshot: startedSnapshot
2674
+ });
2675
+ else if (startedSnapshot.status === "stopped") settle({
2676
+ status: "error",
2677
+ cause: "stopped",
2678
+ error: /* @__PURE__ */ new Error("Actor stopped externally."),
2679
+ snapshot: startedSnapshot
2680
+ });
2681
+ }
2682
+ if (resumeEventToSend && !settled) {
1438
2683
  deliveringResumeEvent = false;
1439
- actor.send(options.event);
2684
+ actor.send(resumeEventToSend);
1440
2685
  }
1441
2686
  return sessionApi;
1442
2687
  }
1443
2688
  /**
1444
- * Runs an agent machine to a **final state**, for run-to-done flows where an
1445
- * idle pause is unexpected. Wraps {@link runAgent}:
1446
- *
1447
- * - `done` → resolves with the done result: `result.output` (the machine's
1448
- * `OutputFrom`) plus metadata — `result.snapshot`, the replayable
1449
- * `result.events`, and the aggregated `result.usage` — the same shape
1450
- * `generateText` users expect (`text` + call metadata).
1451
- * - `idle` → throws {@link AgentIdleError} carrying the idle snapshot and the
1452
- * event types that could resume it.
1453
- * - `error` → throws `result.error` when it is an `Error`; otherwise wraps it
1454
- * in an `Error` whose `.cause` is the {@link RunAgentErrorCause} and whose
1455
- * `.error` is the raw thrown value.
1456
- *
1457
- * Use {@link runAgent} directly when idle is an expected outcome you handle
1458
- * (human-in-the-loop, resumable flows); use `generateResult` when the
1459
- * machine is meant to run straight through to a final state.
1460
- */
1461
- async function generateResult(machine, options) {
1462
- const result = await runAgent(machine, options);
1463
- if (result.status === "done") return result;
1464
- if (result.status === "idle") {
1465
- const acceptedTypes = require_decision.getAcceptedEvents(result.snapshot, { schemas: require_decision.getRegisteredAgentExecutionOptions(machine).schemas }).map((descriptor) => descriptor.type);
1466
- throw new AgentIdleError(result.snapshot, acceptedTypes);
1467
- }
1468
- if (result.error instanceof Error) throw result.error;
1469
- const wrapped = /* @__PURE__ */ new Error(`generateResult: run failed with cause '${result.cause}'.`);
1470
- wrapped.cause = result.cause;
1471
- wrapped.error = result.error;
1472
- throw wrapped;
1473
- }
1474
- /**
1475
2689
  * Wraps a `(snapshot, actorRef) => void` handler into a function usable as
1476
2690
  * {@link RunAgentOptions.inspect}: it filters the raw inspection stream to
1477
2691
  * `@xstate.transition` events and hands the handler the typed
@@ -1533,6 +2747,24 @@ function isIdleSnapshot(snapshot, { ignoreUserInputChildren }) {
1533
2747
  })) return false;
1534
2748
  return !(0, xstate.getNextTransitions)(snapshot).some((transitionDef) => transitionDef.eventType === "" || transitionDef.eventType.startsWith("xstate.after"));
1535
2749
  }
2750
+ /**
2751
+ * Default machine-owned idle signal used by {@link runAgent}: an active
2752
+ * snapshot that accepts an external event anywhere in its active hierarchy,
2753
+ * or declares interaction metadata on an active state.
2754
+ *
2755
+ * Compose this in `setupAgent({ isIdle })` when the application has additional
2756
+ * wait states: `isIdle: (snapshot) => isAgentIdle(snapshot) || snapshot.hasTag('waiting')`.
2757
+ * Pending work and active invoked children are checked separately by the
2758
+ * runner, so this function only answers whether the state is an intentional
2759
+ * external wait.
2760
+ */
2761
+ function isAgentIdle(snapshot) {
2762
+ if (snapshot.status !== "active") return false;
2763
+ return snapshot.nodes.some((node) => {
2764
+ const meta = node.meta;
2765
+ return typeof meta === "object" && meta !== null && "interaction" in meta;
2766
+ }) || require_decision.getAcceptedEvents(snapshot).length > 0;
2767
+ }
1536
2768
  function collectPendingUserInputs(snapshot) {
1537
2769
  const pending = [];
1538
2770
  for (const [id, child] of Object.entries(snapshot.children ?? {})) {
@@ -1646,6 +2878,124 @@ function createAgentRun(machine, options) {
1646
2878
  result
1647
2879
  };
1648
2880
  }
2881
+ function requestIdentity(request) {
2882
+ const candidate = request;
2883
+ const id = candidate.id ?? candidate.name ?? "agent.request";
2884
+ return {
2885
+ name: candidate.kind === "text" ? candidate.input?.name ?? id : candidate.name ?? id,
2886
+ id
2887
+ };
2888
+ }
2889
+ /**
2890
+ * Streams one {@link runAgent} call as agent-level events. The machine remains
2891
+ * the single artifact; this function only projects its requests, chunks,
2892
+ * transitions, emitted events, and terminal result into an async iterable.
2893
+ */
2894
+ async function* runAgentStream(machine, options) {
2895
+ const run = createAgentRun(machine, options);
2896
+ for await (const event of run.events) switch (event.type) {
2897
+ case "request.start":
2898
+ yield {
2899
+ kind: "request",
2900
+ phase: "start",
2901
+ ...requestIdentity(event.request),
2902
+ request: event.request
2903
+ };
2904
+ break;
2905
+ case "request.end":
2906
+ yield {
2907
+ kind: "request",
2908
+ phase: "end",
2909
+ ...requestIdentity(event.request),
2910
+ request: event.request,
2911
+ output: event.output,
2912
+ raw: event.raw
2913
+ };
2914
+ break;
2915
+ case "request.error":
2916
+ yield {
2917
+ kind: "request",
2918
+ phase: "error",
2919
+ ...requestIdentity(event.request),
2920
+ request: event.request,
2921
+ error: event.error
2922
+ };
2923
+ break;
2924
+ case "stream.chunk":
2925
+ yield {
2926
+ kind: "chunk",
2927
+ ...requestIdentity(event.request),
2928
+ delta: event.chunk
2929
+ };
2930
+ break;
2931
+ case "machine.transition":
2932
+ yield {
2933
+ kind: "transition",
2934
+ value: event.snapshot.value,
2935
+ event: event.event
2936
+ };
2937
+ break;
2938
+ case "emit":
2939
+ yield {
2940
+ kind: "emit",
2941
+ event: event.event
2942
+ };
2943
+ break;
2944
+ }
2945
+ const result = await run.result;
2946
+ if (result.status === "done") yield {
2947
+ kind: "done",
2948
+ result
2949
+ };
2950
+ else if (result.status === "idle") yield {
2951
+ kind: "idle",
2952
+ result
2953
+ };
2954
+ else yield {
2955
+ kind: "error",
2956
+ result
2957
+ };
2958
+ }
2959
+ //#endregion
2960
+ //#region src/run-loop.ts
2961
+ function addUsage(total, next) {
2962
+ const merged = { ...total };
2963
+ for (const [key, value] of Object.entries(next)) if (typeof value === "number") merged[key] = (merged[key] ?? 0) + value;
2964
+ return merged;
2965
+ }
2966
+ /** Drives one machine artifact across as many idle/resume turns as the host
2967
+ * supplies. XState snapshots remain the continuation; this helper owns no
2968
+ * persistence, retry, tool-loop, or storage policy. */
2969
+ async function runAgentLoop(machine, options) {
2970
+ const { onIdle, persist, maxTurns = 100, events: initialEvents, ...runOptions } = options;
2971
+ if (!Number.isInteger(maxTurns) || maxTurns < 0) throw new Error("runAgentLoop: maxTurns must be a non-negative integer.");
2972
+ let usage = { modelCalls: 0 };
2973
+ let snapshot;
2974
+ let event;
2975
+ const stored = options.store !== void 0;
2976
+ let events = initialEvents;
2977
+ for (let turn = 0;; turn++) {
2978
+ const result = await runAgent(machine, {
2979
+ ...runOptions,
2980
+ ...events ? { events } : {},
2981
+ ...snapshot ? {
2982
+ snapshot,
2983
+ event
2984
+ } : {}
2985
+ });
2986
+ events = stored ? void 0 : result.events;
2987
+ usage = addUsage(usage, result.usage);
2988
+ const cumulative = {
2989
+ ...result,
2990
+ usage
2991
+ };
2992
+ if (result.status !== "idle" || !onIdle || turn >= maxTurns) return cumulative;
2993
+ snapshot = result.persist();
2994
+ await persist?.(snapshot, result);
2995
+ event = await onIdle(result) ?? void 0;
2996
+ if (!event) return cumulative;
2997
+ }
2998
+ }
1649
2999
  //#endregion
1650
3000
  //#region src/provide-executors.ts
1651
3001
  /**
@@ -1790,98 +3140,6 @@ function buildStateIndex(rootConfig) {
1790
3140
  walk(rootConfig.states, "");
1791
3141
  return index;
1792
3142
  }
1793
- function childrenOf(index, parentPath) {
1794
- const out = [];
1795
- for (const node of index.values()) if (node.parentPath === parentPath) out.push(node);
1796
- return out;
1797
- }
1798
- function collectTransitionTargets(value, fromNode, index, out) {
1799
- if (value === void 0 || value === null) return;
1800
- if (Array.isArray(value)) {
1801
- for (const item of value) collectTransitionTargets(item, fromNode, index, out);
1802
- return;
1803
- }
1804
- if (typeof value === "function") {
1805
- out.opaque = true;
1806
- return;
1807
- }
1808
- if (typeof value === "string") {
1809
- resolveTargetString(value, fromNode, index, out);
1810
- return;
1811
- }
1812
- if (typeof value === "object") {
1813
- const { target, to } = value;
1814
- if (target !== void 0) {
1815
- collectTransitionTargets(target, fromNode, index, out);
1816
- return;
1817
- }
1818
- if (to !== void 0) {
1819
- collectTransitionTargets(to, fromNode, index, out);
1820
- return;
1821
- }
1822
- }
1823
- }
1824
- function resolveTargetString(target, fromNode, index, out) {
1825
- if (target.startsWith("#")) {
1826
- out.opaque = true;
1827
- return;
1828
- }
1829
- const resolved = target.startsWith(".") ? `${fromNode.path}.${target.slice(1)}` : fromNode.parentPath ? `${fromNode.parentPath}.${target}` : target;
1830
- if (index.has(resolved)) out.targets.push(resolved);
1831
- else out.opaque = true;
1832
- }
1833
- function outgoingTargets(node, index, staticTargets) {
1834
- const out = {
1835
- targets: [],
1836
- opaque: false
1837
- };
1838
- const { config } = node;
1839
- if (staticTargets) {
1840
- for (const target of staticTargets[node.path] ?? []) resolveTargetString(target, node, index, out);
1841
- return out;
1842
- }
1843
- for (const value of Object.values(config.on ?? {})) collectTransitionTargets(value, node, index, out);
1844
- collectTransitionTargets(config.always, node, index, out);
1845
- collectTransitionTargets(config.choice, node, index, out);
1846
- for (const value of Object.values(config.after ?? {})) collectTransitionTargets(value, node, index, out);
1847
- collectTransitionTargets(config.onDone, node, index, out);
1848
- for (const invoke of node.invokes) {
1849
- collectTransitionTargets(invoke.onDone, node, index, out);
1850
- collectTransitionTargets(invoke.onError, node, index, out);
1851
- }
1852
- return out;
1853
- }
1854
- function computeReachable(rootConfig, index, staticTargets) {
1855
- const reachable = /* @__PURE__ */ new Set();
1856
- const queue = [];
1857
- const markAncestors = (path) => {
1858
- let parent = index.get(path)?.parentPath ?? "";
1859
- while (parent) {
1860
- reachable.add(parent);
1861
- parent = index.get(parent)?.parentPath ?? "";
1862
- }
1863
- };
1864
- const enter = (path) => {
1865
- if (reachable.has(path)) return;
1866
- const node = index.get(path);
1867
- if (!node) return;
1868
- reachable.add(path);
1869
- queue.push(path);
1870
- markAncestors(path);
1871
- if (node.isParallel) for (const child of childrenOf(index, path)) enter(child.path);
1872
- else if (node.isCompound && node.config.initial) enter(`${path}.${node.config.initial}`);
1873
- };
1874
- if (rootConfig.type === "parallel") for (const child of childrenOf(index, "")) enter(child.path);
1875
- else if (rootConfig.initial) enter(rootConfig.initial);
1876
- while (queue.length > 0) {
1877
- const node = index.get(queue.shift());
1878
- if (!node) continue;
1879
- const { targets, opaque } = outgoingTargets(node, index, staticTargets);
1880
- for (const target of targets) enter(target);
1881
- if (opaque) for (const sibling of childrenOf(index, node.parentPath)) enter(sibling.path);
1882
- }
1883
- return reachable;
1884
- }
1885
3143
  function ancestorChain(node, index) {
1886
3144
  const chain = [];
1887
3145
  let parent = node.parentPath;
@@ -1903,24 +3161,6 @@ function isDecisionInvoke(src, actors) {
1903
3161
  function isAgentLogicNeedingBinding(src) {
1904
3162
  return (require_decision.isTextLogic(src) || require_decision.isDecisionLogic(src)) && !require_decision.executorBoundLogics.has(src);
1905
3163
  }
1906
- function readJsonSchema(schema) {
1907
- if (!schema) return;
1908
- try {
1909
- return require_decision.getJsonSchemaSync(schema);
1910
- } catch {
1911
- return;
1912
- }
1913
- }
1914
- function checkUnreachableStates(ctx) {
1915
- const out = [];
1916
- for (const node of ctx.index.values()) if (!ctx.reachable.has(node.path)) out.push({
1917
- code: "unreachable-state",
1918
- severity: "error",
1919
- path: node.path,
1920
- message: `State '${node.path}' is unreachable: no transition, always, onDone, or onError target (from any reachable state) leads to it. Remove it or add a transition.`
1921
- });
1922
- return out;
1923
- }
1924
3164
  function checkDecideWithoutEvents(ctx) {
1925
3165
  const out = [];
1926
3166
  for (const node of ctx.index.values()) for (const invoke of node.invokes) {
@@ -1939,17 +3179,6 @@ function checkDecideWithoutEvents(ctx) {
1939
3179
  }
1940
3180
  return out;
1941
3181
  }
1942
- function checkUnserializableContext(ctx) {
1943
- const contextSchema = ctx.schemas?.context;
1944
- if (!contextSchema) return [];
1945
- if (readJsonSchema(contextSchema)) return [];
1946
- return [{
1947
- code: "unserializable-context",
1948
- severity: "warning",
1949
- path: "context",
1950
- message: "The context schema does not expose a JSON schema (e.g. a `z.custom` without a `jsonSchema` extension, such as a messages array), so its fields cannot be statically checked for JSON persist/resume round-tripping. This is expected for message transcripts; verify any other custom-typed context is JSON-serializable."
1951
- }];
1952
- }
1953
3182
  function checkDirectObjectSrc(ctx) {
1954
3183
  const out = [];
1955
3184
  for (const node of ctx.index.values()) for (const invoke of node.invokes) {
@@ -1965,80 +3194,48 @@ function checkDirectObjectSrc(ctx) {
1965
3194
  }
1966
3195
  return out;
1967
3196
  }
1968
- function checkFinalWithoutOutput(ctx) {
1969
- const outputJson = readJsonSchema(ctx.schemas?.output);
1970
- const properties = outputJson?.properties;
1971
- const required = outputJson?.required;
1972
- if (!(outputJson?.type === "object" && !!properties && Object.keys(properties).length > 0 || Array.isArray(required) && required.length > 0)) return [];
1973
- if (ctx.config.output !== void 0) return [];
1974
- const out = [];
1975
- for (const node of ctx.index.values()) {
1976
- if (node.parentPath !== "" || !node.isFinal) continue;
1977
- if (node.config.output === void 0) out.push({
1978
- code: "final-without-output",
1979
- severity: "error",
1980
- path: node.path,
1981
- message: `The machine declares an output schema, but top-level final state '${node.path}' has no 'output'. Its snapshot output will be undefined and fail the schema. Add an 'output' to this final state (or a root 'output').`
1982
- });
1983
- }
1984
- return out;
1985
- }
1986
- function outputFnReadsEvent(fn) {
1987
- return /\bevent\s*(?:\.|\?\.|\[)/.test(fn.toString());
1988
- }
1989
- function checkFinalOutputReadsEvent(ctx) {
1990
- const out = [];
1991
- for (const node of ctx.index.values()) {
1992
- if (node.parentPath !== "" || !node.isFinal) continue;
1993
- const output = node.config.output;
1994
- if (typeof output !== "function") continue;
1995
- if (!outputFnReadsEvent(output)) continue;
1996
- out.push({
1997
- code: "final-output-reads-event",
1998
- severity: "warning",
1999
- path: node.path,
2000
- message: `Final state '${node.path}' has an 'output' function that reads 'event'. Final-state 'output' functions are evaluated more than once with different events (the entering event, then the machine-done computation) under current xstate behavior, so 'event' is unreliable here. Read 'context' only; capture what you need from the entering event into context in the transition that targets this state. (This guard can relax if xstate guarantees a stable entering event across evaluations.)`
2001
- });
2002
- }
2003
- return out;
2004
- }
2005
- function isBuiltinOrWildcardEvent(eventType) {
2006
- return eventType === "*" || eventType.includes("*") || eventType.startsWith("xstate.") || eventType.startsWith("done.") || eventType.startsWith("error.");
3197
+ function handlesActorError(config) {
3198
+ if (config["onError"] !== void 0) return true;
3199
+ return Object.keys(config.on ?? {}).some((type) => type === "*" || type.startsWith("xstate.error"));
2007
3200
  }
2008
- function checkUndeclaredEvents(ctx) {
2009
- const declared = ctx.schemas?.events;
2010
- const declaredTypes = new Set(Object.keys(declared ?? {}));
2011
- if ([...declaredTypes].every((type) => type.startsWith("@agent."))) return [];
3201
+ function checkInvokeWithoutOnError(ctx) {
2012
3202
  const out = [];
2013
- for (const node of ctx.index.values()) for (const eventType of Object.keys(node.config.on ?? {})) {
2014
- if (declaredTypes.has(eventType) || isBuiltinOrWildcardEvent(eventType)) continue;
3203
+ for (const node of ctx.index.values()) for (const invoke of node.invokes) {
3204
+ if (invoke.onError !== void 0 || handlesActorError(node.config)) continue;
3205
+ if (handlesActorError(ctx.config) || ancestorChain(node, ctx.index).some((ancestor) => handlesActorError(ancestor.config))) continue;
3206
+ const srcName = typeof invoke.src === "string" ? invoke.src : "(inline logic)";
2015
3207
  out.push({
2016
- code: "undeclared-event",
3208
+ code: "invoke-without-on-error",
2017
3209
  severity: "warning",
2018
3210
  path: node.path,
2019
- message: `State '${node.path}' handles event '${eventType}' in 'on:', but it is not declared in schemas.events and is not a builtin/wildcard pattern. If intentional its payload stays unvalidated; otherwise this is likely a typo.`
3211
+ message: `State '${node.path}' invokes '${srcName}' with no 'onError' transition, and no ancestor handles an actor error. A rejected request or actor puts the machine in an error state with no modeled recovery. Add 'onError' (a 'failed' final state, or a bounded retry).`
2020
3212
  });
2021
3213
  }
2022
3214
  return out;
2023
3215
  }
2024
- function checkMissingFinal(ctx) {
2025
- for (const node of ctx.index.values()) if (node.isFinal && ctx.reachable.has(node.path)) return [];
2026
- return [{
2027
- code: "missing-final",
3216
+ function checkUnhandledAgentMessages(ctx) {
3217
+ const handlesMessages = (config) => config.on?.["agent.messages"] !== void 0 || config.on?.["*"] !== void 0;
3218
+ if (handlesMessages(ctx.config) || [...ctx.index.values()].some((node) => handlesMessages(node.config))) return [];
3219
+ let contextJsonSchema;
3220
+ try {
3221
+ contextJsonSchema = require_decision.getJsonSchemaSync(ctx.schemas?.context);
3222
+ } catch {}
3223
+ if (!contextJsonSchema?.properties?.messages) return [];
3224
+ return [...ctx.index.values()].some((node) => node.invokes.some((invoke) => {
3225
+ if (typeof invoke.src !== "string") return false;
3226
+ return invoke.src === "agent.generateText" || invoke.src === "agent.streamText" || require_decision.isTextLogic(ctx.actors[invoke.src]);
3227
+ })) ? [{
3228
+ code: "unhandled-agent-messages",
2028
3229
  severity: "warning",
2029
3230
  path: "(root)",
2030
- message: "The machine has no reachable final state, so a run can never settle 'done' (only idle/looping). This is legal for agents that only idle, but verify it is intended."
2031
- }];
3231
+ message: `Text requests may return framework messages, but the machine does not handle '${require_decision.AGENT_MESSAGES_EVENT_TYPE}'. Add on: { '${require_decision.AGENT_MESSAGES_EVENT_TYPE}': appendMessages() } when transcript retention is intended, or disable this warning when messages are intentionally ignored.`
3232
+ }] : [];
2032
3233
  }
2033
3234
  const LINT_CHECKS = [
2034
- checkUnreachableStates,
2035
3235
  checkDecideWithoutEvents,
2036
- checkUnserializableContext,
2037
3236
  checkDirectObjectSrc,
2038
- checkFinalWithoutOutput,
2039
- checkFinalOutputReadsEvent,
2040
- checkUndeclaredEvents,
2041
- checkMissingFinal
3237
+ checkInvokeWithoutOnError,
3238
+ checkUnhandledAgentMessages
2042
3239
  ];
2043
3240
  /**
2044
3241
  * Runs static structural checks over a built agent machine and returns the
@@ -2048,8 +3245,9 @@ const LINT_CHECKS = [
2048
3245
  * plus the schemas/actor sources the library already retains per machine.
2049
3246
  *
2050
3247
  * No model calls, no API keys — a coding agent that emits an agent machine can
2051
- * call this to catch dead states, undeliverable decisions, un-rebindable
2052
- * invoke srcs, and output-contract gaps before ever running it.
3248
+ * call this to catch agent-specific mistakes such as undeliverable decisions,
3249
+ * un-rebindable request sources, and an unhandled transcript event. General
3250
+ * state-machine lint belongs to XState tooling.
2053
3251
  *
2054
3252
  * Pass `{ throw: true }` for the one-liner form used in tests and generation
2055
3253
  * loops: it returns silently when the machine is clean and throws
@@ -2072,12 +3270,10 @@ const LINT_CHECKS = [
2072
3270
  function lintAgentMachine(machine, options = {}) {
2073
3271
  const config = machine.config ?? {};
2074
3272
  const index = buildStateIndex(config);
2075
- const reachable = computeReachable(config, index, require_decision.getMachineStaticTransitionTargets(machine));
2076
3273
  const registered = require_decision.getRegisteredAgentExecutionOptions(machine);
2077
3274
  const ctx = {
2078
3275
  config,
2079
3276
  index,
2080
- reachable,
2081
3277
  schemas: registered.schemas,
2082
3278
  actors: registered.actors ?? machine.sources?.actors ?? {}
2083
3279
  };
@@ -2095,7 +3291,7 @@ function lintAgentMachine(machine, options = {}) {
2095
3291
  * `diagnostics` holds the findings; the message lists them one per finding,
2096
3292
  * so a test runner's failure output reads like the CLI's lint report.
2097
3293
  */
2098
- var AgentLintError = class extends require_errors.AgentError {
3294
+ var AgentLintError = class extends require_decision.AgentError {
2099
3295
  diagnostics;
2100
3296
  constructor(machineId, diagnostics) {
2101
3297
  const lines = diagnostics.map((d) => ` ${d.severity === "error" ? "error" : "warn "} ${d.code} ${d.path}\n ${d.message}`);
@@ -2126,13 +3322,10 @@ function assertAgentMachine(machine, options = {}) {
2126
3322
  }
2127
3323
  function pendingInvokes(step) {
2128
3324
  const out = [];
2129
- for (const action of step.actions) {
2130
- const metadata = require_setup_agent.getInvokeEffectMetadata(action);
2131
- if (typeof metadata?.src === "string" && typeof metadata.id === "string") out.push({
2132
- id: metadata.id,
2133
- src: metadata.src
2134
- });
2135
- }
3325
+ for (const metadata of getPendingInvokes(step.snapshot)) if (typeof metadata.src === "string") out.push({
3326
+ id: metadata.id,
3327
+ src: metadata.src
3328
+ });
2136
3329
  return out;
2137
3330
  }
2138
3331
  function takeFromQueue(channel, src) {
@@ -2186,10 +3379,11 @@ async function simulateAgent(machine, options) {
2186
3379
  text: mapValues(options.script.text ?? {}, (arr) => [...arr]),
2187
3380
  decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
2188
3381
  invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr]),
3382
+ errors: mapValues(options.script.errors ?? {}, (arr) => [...arr]),
2189
3383
  events: [...options.script.events ?? []]
2190
3384
  };
2191
3385
  if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
2192
- let step = require_setup_agent.initialAgentStep(machine, options.input);
3386
+ let step = initialAgentStep(machine, options.input);
2193
3387
  const trail = [{ state: step.snapshot.value }];
2194
3388
  for (let i = 0; i < maxSteps; i++) {
2195
3389
  if (step.done) return {
@@ -2203,33 +3397,63 @@ async function simulateAgent(machine, options) {
2203
3397
  const invokeMeta = findInvokeMetadata(step, request.id);
2204
3398
  const src = invokeMeta?.src ?? request.id;
2205
3399
  const decisionSrc = src in (script.decisions ?? {}) ? src : request.id in (script.decisions ?? {}) ? request.id : src;
3400
+ const errorSrc = queueKey(script.errors, src, request.id);
3401
+ const failure = takeFromQueue(script.errors, errorSrc);
3402
+ if (failure.found) {
3403
+ step = rejectScriptedRequest(machine, step, request.id, failure.value, {
3404
+ kind: "decision",
3405
+ src: errorSrc,
3406
+ trail
3407
+ });
3408
+ continue;
3409
+ }
2206
3410
  step = await applyScriptedDecision(machine, step, request, decisionSrc, invokeMeta?.logic, script, trail);
2207
3411
  continue;
2208
3412
  }
3413
+ const failure = takeFromQueue(script.errors, request.src);
3414
+ if (failure.found) {
3415
+ step = rejectScriptedRequest(machine, step, request, failure.value, {
3416
+ kind: "text",
3417
+ src: request.src,
3418
+ trail
3419
+ });
3420
+ continue;
3421
+ }
2209
3422
  const taken = takeFromQueue(script.text, request.src);
2210
3423
  if (!taken.found) throw scriptDryError("text", request.src, request.id);
2211
- step = require_setup_agent.resolveAgentStep(machine, step, request, taken.value);
3424
+ step = resolveAgentStep(machine, step, request, taken.value);
2212
3425
  trail.push({
2213
3426
  state: step.snapshot.value,
2214
3427
  resolvedRequest: {
2215
3428
  kind: "text",
2216
3429
  src: request.src,
2217
- id: request.id
3430
+ id: request.id,
3431
+ outcome: "output"
2218
3432
  }
2219
3433
  });
2220
3434
  continue;
2221
3435
  }
2222
3436
  const [invoke] = pendingInvokes(step);
2223
3437
  if (invoke) {
3438
+ const failure = takeFromQueue(script.errors, invoke.src);
3439
+ if (failure.found) {
3440
+ step = rejectScriptedRequest(machine, step, invoke.id, failure.value, {
3441
+ kind: "userInput",
3442
+ src: invoke.src,
3443
+ trail
3444
+ });
3445
+ continue;
3446
+ }
2224
3447
  const taken = takeFromQueue(script.invokes, invoke.src);
2225
3448
  if (!taken.found) throw scriptDryError("userInput", invoke.src, invoke.id);
2226
- step = require_setup_agent.resolveAgentStep(machine, step, invoke.id, taken.value);
3449
+ step = resolveAgentStep(machine, step, invoke.id, taken.value);
2227
3450
  trail.push({
2228
3451
  state: step.snapshot.value,
2229
3452
  resolvedRequest: {
2230
3453
  kind: "userInput",
2231
3454
  src: invoke.src,
2232
- id: invoke.id
3455
+ id: invoke.id,
3456
+ outcome: "output"
2233
3457
  }
2234
3458
  });
2235
3459
  continue;
@@ -2238,7 +3462,7 @@ async function simulateAgent(machine, options) {
2238
3462
  if (external.length > 0) {
2239
3463
  const event = external.shift();
2240
3464
  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);
3465
+ step = transitionAgentStep(machine, step, event);
2242
3466
  trail.push({
2243
3467
  state: step.snapshot.value,
2244
3468
  appliedEvent: event,
@@ -2258,6 +3482,29 @@ async function simulateAgent(machine, options) {
2258
3482
  trail
2259
3483
  };
2260
3484
  }
3485
+ function queueKey(channel, src, id) {
3486
+ return channel && !(src in channel) && id in channel ? id : src;
3487
+ }
3488
+ function rejectScriptedRequest(machine, step, request, error, entry) {
3489
+ const id = typeof request === "string" ? request : request.id;
3490
+ const next = rejectAgentStep(machine, step, request, error);
3491
+ if (next.snapshot.status === "error") throw unhandledScriptedError(error, entry.kind, entry.src, id);
3492
+ entry.trail.push({
3493
+ state: next.snapshot.value,
3494
+ resolvedRequest: {
3495
+ kind: entry.kind,
3496
+ src: entry.src,
3497
+ id,
3498
+ outcome: "error",
3499
+ error
3500
+ }
3501
+ });
3502
+ return next;
3503
+ }
3504
+ function unhandledScriptedError(error, kind, src, id) {
3505
+ if (error instanceof Error) return error;
3506
+ return new Error(`simulateAgent: the scripted error for the ${kind} request '${src}' (id '${id}') reached no onError transition, so the machine errored. Add an onError to that invoke, or drop the errors['${src}'] entry.`, { cause: error });
3507
+ }
2261
3508
  async function applyScriptedDecision(machine, step, request, decisionSrc, invokeLogic, script, trail) {
2262
3509
  const dequeued = [];
2263
3510
  const decide = async () => {
@@ -2278,12 +3525,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
2278
3525
  });
2279
3526
  } catch (error) {
2280
3527
  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
- });
3528
+ const next = rejectAgentStep(machine, step, request.id, error);
2287
3529
  if (next.snapshot.status === "error") throw error;
2288
3530
  trail.push({
2289
3531
  state: next.snapshot.value,
@@ -2291,7 +3533,7 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
2291
3533
  });
2292
3534
  return next;
2293
3535
  }
2294
- const next = require_setup_agent.transitionAgentStep(machine, step, chosen);
3536
+ const next = transitionAgentStep(machine, step, chosen);
2295
3537
  const rejected = dequeued.slice(0, -1);
2296
3538
  trail.push({
2297
3539
  state: next.snapshot.value,
@@ -2301,9 +3543,8 @@ async function applyScriptedDecision(machine, step, request, decisionSrc, invoke
2301
3543
  return next;
2302
3544
  }
2303
3545
  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;
3546
+ for (const metadata of getPendingInvokes(step.snapshot)) {
3547
+ if (metadata.id !== id) continue;
2307
3548
  return {
2308
3549
  ...typeof metadata.src === "string" ? { src: metadata.src } : {},
2309
3550
  logic: metadata.logic ?? (typeof metadata.src === "object" ? metadata.src : void 0)
@@ -2314,10 +3555,6 @@ function resolveRegisteredDecisionLogic(machine, src) {
2314
3555
  const candidate = require_decision.getRegisteredAgentExecutionOptions(machine).actors?.[src] ?? machine.sources?.actors?.[src];
2315
3556
  return require_decision.isDecisionLogic(candidate) ? candidate : void 0;
2316
3557
  }
2317
- function sessionIdOf(snapshot, id) {
2318
- const child = snapshot.children[id];
2319
- return typeof child?.sessionId === "string" ? { sessionId: child.sessionId } : {};
2320
- }
2321
3558
  function mapValues(obj, fn) {
2322
3559
  return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
2323
3560
  }
@@ -2327,12 +3564,25 @@ function scriptDryError(kind, src, id, request) {
2327
3564
  return /* @__PURE__ */ new Error(`simulateAgent: script ran dry on a pending ${kind} request for src '${src}' (id '${id}'). Add an entry to the script's \`${key}\` queue.${events}`);
2328
3565
  }
2329
3566
  const MAX_ADVANCE_STEPS = 1e3;
3567
+ function invokeFork(id, src, outputs) {
3568
+ return src in outputs ? {
3569
+ id,
3570
+ src,
3571
+ hasOutput: true,
3572
+ output: outputs[src]
3573
+ } : {
3574
+ id,
3575
+ src,
3576
+ hasOutput: false
3577
+ };
3578
+ }
2330
3579
  async function explore(machine, options, stopWhen) {
2331
3580
  const maxDepth = options.maxDepth ?? 8;
2332
3581
  const maxPaths = options.maxPaths ?? 200;
2333
3582
  const textScript = options.text ?? {};
2334
3583
  const invokeOutputs = { ...options.invokes ?? {} };
2335
3584
  if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
3585
+ const errorScript = options.errors ?? {};
2336
3586
  const reachedStates = /* @__PURE__ */ new Set();
2337
3587
  const reachedValues = [];
2338
3588
  const terminals = [];
@@ -2348,7 +3598,7 @@ async function explore(machine, options, stopWhen) {
2348
3598
  reachedValues.push(snapshot.value);
2349
3599
  }
2350
3600
  };
2351
- const initial = require_setup_agent.initialAgentStep(machine, options.input);
3601
+ const initial = initialAgentStep(machine, options.input);
2352
3602
  recordState(initial.snapshot);
2353
3603
  if (stopWhen?.(initial.snapshot)) witness = [];
2354
3604
  const advance = (step) => {
@@ -2361,22 +3611,42 @@ async function explore(machine, options, stopWhen) {
2361
3611
  if (current.done) return { step: current };
2362
3612
  const request = current.requests[0];
2363
3613
  if (request && request.kind === "text") {
3614
+ if (request.src in errorScript) return {
3615
+ step: current,
3616
+ fork: invokeFork(request.id, request.src, textScript)
3617
+ };
2364
3618
  if (!(request.src in textScript)) return {
2365
3619
  step: current,
2366
3620
  blockedSrc: request.src
2367
3621
  };
2368
- current = require_setup_agent.resolveAgentStep(machine, current, request, textScript[request.src]);
3622
+ current = resolveAgentStep(machine, current, request, textScript[request.src]);
2369
3623
  recordState(current.snapshot);
2370
3624
  continue;
2371
3625
  }
2372
- if (request && request.kind === "decision") return { step: current };
3626
+ if (request && request.kind === "decision") {
3627
+ const src = findInvokeMetadata(current, request.id)?.src;
3628
+ const errorSrc = src !== void 0 && src in errorScript ? src : request.id in errorScript ? request.id : void 0;
3629
+ return errorSrc !== void 0 ? {
3630
+ step: current,
3631
+ fork: {
3632
+ id: request.id,
3633
+ src: errorSrc,
3634
+ hasOutput: false,
3635
+ isDecision: true
3636
+ }
3637
+ } : { step: current };
3638
+ }
2373
3639
  const [invoke] = pendingInvokes(current);
2374
3640
  if (invoke) {
3641
+ if (invoke.src in errorScript) return {
3642
+ step: current,
3643
+ fork: invokeFork(invoke.id, invoke.src, invokeOutputs)
3644
+ };
2375
3645
  if (!(invoke.src in invokeOutputs)) return {
2376
3646
  step: current,
2377
3647
  blockedSrc: invoke.src
2378
3648
  };
2379
- current = require_setup_agent.resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
3649
+ current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
2380
3650
  recordState(current.snapshot);
2381
3651
  continue;
2382
3652
  }
@@ -2384,17 +3654,43 @@ async function explore(machine, options, stopWhen) {
2384
3654
  }
2385
3655
  return { step: current };
2386
3656
  };
2387
- const visit = async (step, path, depth) => {
3657
+ const visit = async (step, path, startDepth) => {
3658
+ let depth = startDepth;
2388
3659
  if (witness !== void 0) return;
2389
3660
  if (pathsExplored >= maxPaths) {
2390
3661
  hitPathCap = true;
2391
3662
  return;
2392
3663
  }
2393
- const { step: settled, blockedSrc, hit } = advance(step);
3664
+ const { step: settled, blockedSrc, hit, fork } = advance(step);
2394
3665
  if (hit) {
2395
3666
  witness = path;
2396
3667
  return;
2397
3668
  }
3669
+ if (fork) {
3670
+ if (depth >= maxDepth) {
3671
+ pathsExplored++;
3672
+ terminals.push({
3673
+ status: "max-depth",
3674
+ path,
3675
+ state: settled.snapshot.value
3676
+ });
3677
+ unexplored.push(`max-depth: stopped at path [${path.map((e) => e.type).join(", ")}]`);
3678
+ return;
3679
+ }
3680
+ const failed = rejectAgentStep(machine, settled, fork.id, errorScript[fork.src]);
3681
+ recordState(failed.snapshot);
3682
+ await visit(failed, [...path, { type: `xstate.error.actor.${fork.id}` }], depth + 1);
3683
+ if (!fork.isDecision) {
3684
+ if (fork.hasOutput) {
3685
+ const resolved = resolveAgentStep(machine, settled, fork.id, fork.output);
3686
+ recordState(resolved.snapshot);
3687
+ await visit(resolved, path, depth + 1);
3688
+ }
3689
+ return;
3690
+ }
3691
+ depth += 1;
3692
+ if (witness !== void 0) return;
3693
+ }
2398
3694
  if (blockedSrc) {
2399
3695
  pathsExplored++;
2400
3696
  terminals.push({
@@ -2415,6 +3711,16 @@ async function explore(machine, options, stopWhen) {
2415
3711
  });
2416
3712
  return;
2417
3713
  }
3714
+ if (settled.snapshot.status === "error") {
3715
+ pathsExplored++;
3716
+ terminals.push({
3717
+ status: "error",
3718
+ path,
3719
+ state: settled.snapshot.value,
3720
+ error: settled.snapshot.error
3721
+ });
3722
+ return;
3723
+ }
2418
3724
  const request = settled.requests[0];
2419
3725
  const branchEvents = request?.kind === "decision" ? request.events.map((descriptor) => ({ type: descriptor.type })) : require_decision.getAcceptedEvents(settled.snapshot).map((descriptor) => ({ type: descriptor.type }));
2420
3726
  if (branchEvents.length === 0) {
@@ -2445,7 +3751,7 @@ async function explore(machine, options, stopWhen) {
2445
3751
  prunedByGuard++;
2446
3752
  continue;
2447
3753
  }
2448
- const next = require_setup_agent.transitionAgentStep(machine, settled, event);
3754
+ const next = transitionAgentStep(machine, settled, event);
2449
3755
  recordState(next.snapshot);
2450
3756
  await visit(next, [...path, event], depth + 1);
2451
3757
  }
@@ -2471,7 +3777,9 @@ async function explore(machine, options, stopWhen) {
2471
3777
  * an idle wait it forks per externally-accepted event. Text requests resolve
2472
3778
  * from `text`, other invokes from `invokes` (or `userInput` for
2473
3779
  * `agent.userInput`) — all by-src canned-output maps, and a missing src halts
2474
- * that branch with a `needs-output` terminal rather than throwing.
3780
+ * that branch with a `needs-output` terminal rather than throwing. A src in
3781
+ * `errors` forks an extra branch where that invoke is rejected, so states
3782
+ * behind an `onError` are explored too.
2475
3783
  *
2476
3784
  * Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
2477
3785
  * 200, reported via `hitPathCap`).
@@ -2485,6 +3793,24 @@ async function explore(machine, options, stopWhen) {
2485
3793
  async function explorePaths(machine, options = {}) {
2486
3794
  return (await explore(machine, options)).report;
2487
3795
  }
3796
+ /** Thrown when a string reachability target is not a state in the machine. */
3797
+ var AgentUnknownStateError = class extends require_decision.AgentError {
3798
+ target;
3799
+ constructor(machineId, target) {
3800
+ super("unknown-state", `canReach: machine '${machineId}' has no state matching '${target}'.`);
3801
+ this.name = "AgentUnknownStateError";
3802
+ this.target = target;
3803
+ }
3804
+ };
3805
+ function resolveStateTarget(machine, target) {
3806
+ const stack = [machine.root];
3807
+ while (stack.length > 0) {
3808
+ const node = stack.pop();
3809
+ if (node.id === target.replace(/^#/, "") || node.path.join(".") === target) return node.id;
3810
+ stack.push(...Object.values(node.states));
3811
+ }
3812
+ throw new AgentUnknownStateError(machine.id, target);
3813
+ }
2488
3814
  /**
2489
3815
  * Answers "can the machine reach this?" by exploring its branches (a thin
2490
3816
  * wrapper over {@link explorePaths}). The target is either a state path string
@@ -2511,23 +3837,16 @@ async function explorePaths(machine, options = {}) {
2511
3837
  * ```
2512
3838
  */
2513
3839
  async function canReach(machine, target, options = {}) {
2514
- const { witness } = await explore(machine, options, typeof target === "function" ? target : (snapshot) => {
2515
- try {
2516
- return snapshot.matches(target);
2517
- } catch {
2518
- return false;
2519
- }
2520
- });
3840
+ const resolvedTarget = typeof target === "string" ? resolveStateTarget(machine, target) : void 0;
3841
+ const { witness } = await explore(machine, options, typeof target === "function" ? target : (snapshot) => snapshot.nodes.some((node) => node.id === resolvedTarget));
2521
3842
  return witness !== void 0 ? {
2522
3843
  reachable: true,
2523
- witness
2524
- } : { reachable: false };
2525
- }
2526
- //#endregion
2527
- //#region src/internal/is-record.ts
2528
- /** True for a plain object-like value (arrays excluded). @internal */
2529
- function isRecord(value) {
2530
- return typeof value === "object" && value !== null && !Array.isArray(value);
3844
+ witness,
3845
+ ...resolvedTarget ? { target: resolvedTarget } : {}
3846
+ } : {
3847
+ reachable: false,
3848
+ ...resolvedTarget ? { target: resolvedTarget } : {}
3849
+ };
2531
3850
  }
2532
3851
  //#endregion
2533
3852
  //#region src/trajectory.ts
@@ -2537,9 +3856,9 @@ function isRecord(value) {
2537
3856
  * @internal
2538
3857
  */
2539
3858
  function normalize(item) {
2540
- if (isRecord(item)) {
3859
+ if (require_decision.isRecord(item)) {
2541
3860
  const nested = item["event"];
2542
- if (isRecord(nested) && typeof nested["type"] === "string") return {
3861
+ if (require_decision.isRecord(nested) && typeof nested["type"] === "string") return {
2543
3862
  kind: "event",
2544
3863
  event: nested
2545
3864
  };
@@ -2547,7 +3866,7 @@ function normalize(item) {
2547
3866
  kind: "event",
2548
3867
  event: item
2549
3868
  };
2550
- if ("value" in item && "status" in item) return {
3869
+ if ("value" in item) return {
2551
3870
  kind: "state",
2552
3871
  value: item["value"]
2553
3872
  };
@@ -2560,7 +3879,7 @@ function normalize(item) {
2560
3879
  /** Every leaf of a state value as a dot path (`{ review: 'editing' }` → `review.editing`). @internal */
2561
3880
  function statePaths(value) {
2562
3881
  if (typeof value === "string") return [value];
2563
- if (!isRecord(value)) return [];
3882
+ if (!require_decision.isRecord(value)) return [];
2564
3883
  return Object.entries(value).flatMap(([key, child]) => statePaths(child).map((path) => `${key}.${path}`));
2565
3884
  }
2566
3885
  /**
@@ -2573,7 +3892,7 @@ function stateMatchesPath(path, value) {
2573
3892
  function deepEqual(a, b) {
2574
3893
  if (Object.is(a, b)) return true;
2575
3894
  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]));
2576
- if (!isRecord(a) || !isRecord(b)) return false;
3895
+ if (!require_decision.isRecord(a) || !require_decision.isRecord(b)) return false;
2577
3896
  const keys = Object.keys(a);
2578
3897
  return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
2579
3898
  }
@@ -2607,8 +3926,8 @@ function matcherFor(expected) {
2607
3926
  /**
2608
3927
  * Matches a run's trajectory against an expected one.
2609
3928
  *
2610
- * Both trajectories may be state values (collected from `onTransition`) or
2611
- * events (`result.events`, whose {@link AgentLogEntry} envelopes are unwrapped);
3929
+ * Both trajectories may be state values or events collected from
3930
+ * `onTransition`;
2612
3931
  * items are compared by shape, so `['prompting', 'drafting']` scores a state
2613
3932
  * path and `['PROMPT_SUBMITTED', 'SEND']` scores an event log with the same
2614
3933
  * call.
@@ -2628,7 +3947,7 @@ function matcherFor(expected) {
2628
3947
  *
2629
3948
  * const path = matchesTrajectory(statePath, ['prompting', 'drafting', 'sent']);
2630
3949
  * expect(path.matched, JSON.stringify(path.firstMiss)).toBe(true);
2631
- * expect(matchesTrajectory(result.events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
3950
+ * expect(matchesTrajectory(events, ['PROMPT_SUBMITTED', 'SEND']).matched).toBe(true);
2632
3951
  * ```
2633
3952
  *
2634
3953
  * @example Partial credit in an eval scorer
@@ -2639,6 +3958,7 @@ function matcherFor(expected) {
2639
3958
  */
2640
3959
  function matchesTrajectory(actual, expected, options = {}) {
2641
3960
  const expectedCount = expected.length;
3961
+ if (expectedCount === 0) throw new require_decision.AgentError("empty-trajectory", "matchesTrajectory: expected trajectory must contain at least one item.");
2642
3962
  const exact = options.exact === true;
2643
3963
  let cursor = 0;
2644
3964
  let matchedCount = 0;
@@ -2679,17 +3999,41 @@ function matchesTrajectory(actual, expected, options = {}) {
2679
3999
  //#endregion
2680
4000
  //#region src/scripted-executors.ts
2681
4001
  /**
2682
- * Scripted executors — a keyless, deterministic stand-in for a model host.
4002
+ * Scripted executors — a deterministic stand-in for a model host.
2683
4003
  *
2684
4004
  * `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
2685
- * set (plus a `userInput` handler) that plays back canned answers from FIFO
2686
- * queues, so `runAgent` (or
2687
- * `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
2688
- * network. It is the fastest way to see a machine run, and the least ceremonial
2689
- * way to test one: same machine, same executor contract, scripted answers.
4005
+ * set (plus a `userInput` handler) that plays back canned answers keyed by
4006
+ * request name, so `runAgent` (or `provideExecutors`, or a bare
4007
+ * `TextLogic.execute`) runs with no API key and no network.
4008
+ *
4009
+ * One rule: entries for a name are consumed in order, and the last entry
4010
+ * repeats forever.
2690
4011
  *
2691
4012
  * @module
2692
4013
  */
4014
+ function toQueues(value) {
4015
+ return new Map(Object.entries(value ?? {}).map(([name, entries]) => [name, {
4016
+ entries: Array.isArray(entries) ? [...entries] : [entries],
4017
+ index: 0
4018
+ }]));
4019
+ }
4020
+ /**
4021
+ * Takes the next entry for `name`, holding on the last entry once the list is
4022
+ * spent. `undefined` means the script has no route for this name at all.
4023
+ * @internal
4024
+ */
4025
+ function takeEntry(queues, name) {
4026
+ const queue = queues.get(name) ?? queues.get("*");
4027
+ if (!queue || queue.entries.length === 0) return;
4028
+ const entry = queue.entries[Math.min(queue.index, queue.entries.length - 1)];
4029
+ queue.index++;
4030
+ return entry;
4031
+ }
4032
+ /** @internal */
4033
+ function noAnswer(name, queues) {
4034
+ const known = [...queues.keys()].join(", ") || "(none)";
4035
+ return /* @__PURE__ */ new Error(`No scripted answer for request '${name}'. Known: ${known}`);
4036
+ }
2693
4037
  /** The only own keys an executor-result envelope carries. @internal */
2694
4038
  const TEXT_ENVELOPE_KEYS = new Set([
2695
4039
  "output",
@@ -2714,7 +4058,7 @@ function isTextEnvelope(value) {
2714
4058
  */
2715
4059
  async function resolveScriptedTextEntry(entry, request, info) {
2716
4060
  const value = typeof entry === "function" ? await entry(request, info) : entry;
2717
- return isRecord(value) && isTextEnvelope(value) ? value : { output: value };
4061
+ return require_decision.isRecord(value) && isTextEnvelope(value) ? value : { output: value };
2718
4062
  }
2719
4063
  /**
2720
4064
  * Names a pending text request in an error message. Shared with `runSeam`.
@@ -2728,26 +4072,30 @@ function describeText(request) {
2728
4072
  * with `runSeam`, whose scripted answers stream the same way. @internal
2729
4073
  */
2730
4074
  function emitScriptedChunk(result, info) {
2731
- const output = isRecord(result) ? result["output"] : void 0;
4075
+ const output = require_decision.isRecord(result) ? result["output"] : void 0;
2732
4076
  if (typeof output === "string") info?.onChunk?.(output);
2733
4077
  }
2734
4078
  /**
2735
- * Keyless executors that replay a script instead of calling a model. Every
2736
- * slot is provided, so any machine binds; a request with no entry left throws a
2737
- * descriptive error naming what was pending.
4079
+ * Executors that replay a script instead of calling a model. Every slot is
4080
+ * provided, so any machine binds.
4081
+ *
4082
+ * Answers are keyed by the request's resolved name — its authored `name`, else
4083
+ * the invoke `id` — and `"*"` serves anything unmatched.
4084
+ * Entries for a name are consumed in order and the last one repeats forever, so
4085
+ * a looping machine needs no extra option. A request the script has no route
4086
+ * for throws an ordinary `Error` from inside the executor, which reaches the
4087
+ * machine as an actor error.
2738
4088
  *
2739
- * Queues are consumed FIFO and are copied on creation, so the caller's arrays
2740
- * are never mutated and each call builds a fresh, independent playthrough.
2741
- * Entries may be plain values or functions of the request, which is how one
2742
- * script serves a machine that loops or branches: route on `request.name` (the
2743
- * `setupAgent({ requests })` key) or on the decision's candidate `events`.
4089
+ * Queues are copied on creation, so the caller's arrays are never mutated and
4090
+ * each call builds a fresh, independent playthrough. Entries may be plain
4091
+ * values or functions of the request.
2744
4092
  *
2745
4093
  * @example
2746
4094
  * ```ts
2747
4095
  * const result = await runAgent(moderationMachine, {
2748
4096
  * input: { comment: 'honestly this update is terrible', trust: 20 },
2749
4097
  * executors: createScriptedExecutors({
2750
- * decisions: [{ type: 'FLAG', reason: 'Borderline tone.' }],
4098
+ * decisions: { moderate: [{ type: 'FLAG', reason: 'Borderline tone.' }] },
2751
4099
  * }),
2752
4100
  * });
2753
4101
  * ```
@@ -2755,8 +4103,8 @@ function emitScriptedChunk(result, info) {
2755
4103
  * @example Dynamic entries
2756
4104
  * ```ts
2757
4105
  * createScriptedExecutors({
2758
- * text: [(request) => `Draft about ${request.prompt}`],
2759
- * decisions: [(request) => ({ type: request.events[0]!.type })],
4106
+ * text: { draft: (request) => `Draft about ${request.prompt}` },
4107
+ * decisions: { route: (request) => ({ type: request.events[0]!.type }) },
2760
4108
  * });
2761
4109
  * ```
2762
4110
  *
@@ -2767,30 +4115,72 @@ function emitScriptedChunk(result, info) {
2767
4115
  * ```
2768
4116
  */
2769
4117
  function createScriptedExecutors(script = {}) {
2770
- const decisions = [...script.decisions ?? []];
2771
- const text = [...script.text ?? []];
4118
+ const decisions = toQueues(script.decisions);
4119
+ const text = toQueues(script.text);
2772
4120
  const userInput = [...script.userInput ?? []];
2773
- const nextText = async (request, info) => {
2774
- if (text.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
2775
- return resolveScriptedTextEntry(text.shift(), request, info);
4121
+ let userInputIndex = 0;
4122
+ const calls = [];
4123
+ const withDefaultUsage = (result) => result.usage || !script.usage ? result : {
4124
+ ...result,
4125
+ usage: script.usage
4126
+ };
4127
+ const nextText = async (kind, request, info) => {
4128
+ const name = request.name ?? "*";
4129
+ calls.push({
4130
+ kind,
4131
+ name,
4132
+ input: request.input,
4133
+ request
4134
+ });
4135
+ const entry = takeEntry(text, name);
4136
+ if (entry === void 0) throw noAnswer(name, text);
4137
+ return withDefaultUsage(await resolveScriptedTextEntry(entry, request, info));
2776
4138
  };
2777
4139
  return {
4140
+ calls,
2778
4141
  userInput: async (input) => {
2779
- if (userInput.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending userInput request (prompt: ${input.prompt ? `'${input.prompt}'` : "(none)"}). Add another entry to the script's \`userInput\` queue.`);
2780
- const entry = userInput.shift();
4142
+ calls.push({
4143
+ kind: "userInput",
4144
+ name: "agent.userInput",
4145
+ input,
4146
+ request: input
4147
+ });
4148
+ if (userInput.length === 0) throw new Error("No scripted answer for request 'agent.userInput'. Add an entry to the script's `userInput` array.");
4149
+ const entry = userInput[Math.min(userInputIndex++, userInput.length - 1)];
2781
4150
  return typeof entry === "function" ? await entry(input) : entry;
2782
4151
  },
2783
- generateText: nextText,
4152
+ generateText: (request, info) => nextText("generateText", request, info),
2784
4153
  streamText: async (request, info) => {
2785
- const result = await nextText(request, info);
4154
+ const name = request.name ?? "*";
4155
+ const streamEntry = script.stream?.[name] ?? script.stream?.["*"];
4156
+ if (streamEntry !== void 0) {
4157
+ calls.push({
4158
+ kind: "streamText",
4159
+ name,
4160
+ input: request.input,
4161
+ request
4162
+ });
4163
+ const resolved = typeof streamEntry === "function" ? await streamEntry(request, info) : streamEntry;
4164
+ const chunks = typeof resolved === "string" ? [resolved] : [...resolved];
4165
+ for (const chunk of chunks) info?.onChunk?.(chunk);
4166
+ return withDefaultUsage({ output: chunks.join("") });
4167
+ }
4168
+ const result = await nextText("streamText", request, info);
2786
4169
  emitScriptedChunk(result, info);
2787
4170
  return result;
2788
4171
  },
2789
4172
  decide: async (request) => {
2790
- if (decisions.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending decision request (id '${request.id}'). Add another entry to the script's \`decisions\` queue. Candidate events: ${request.events.map((event) => event.type).join(", ") || "(none)"}.`);
2791
- const entry = decisions.shift();
4173
+ const name = request.name ?? request.id;
4174
+ calls.push({
4175
+ kind: "decide",
4176
+ name,
4177
+ input: request.input,
4178
+ request
4179
+ });
4180
+ const entry = takeEntry(decisions, name);
4181
+ if (entry === void 0) throw noAnswer(name, decisions);
2792
4182
  const value = typeof entry === "function" ? await entry(request) : entry;
2793
- return isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value };
4183
+ return withDefaultUsage(require_decision.isRecord(value) && typeof value["type"] !== "string" && "event" in value ? value : { event: value });
2794
4184
  }
2795
4185
  };
2796
4186
  }
@@ -2805,7 +4195,7 @@ function createScriptedExecutors(script = {}) {
2805
4195
  * @internal
2806
4196
  */
2807
4197
  async function seamOutputOf(result, request) {
2808
- if (!isRecord(result)) return;
4198
+ if (!require_decision.isRecord(result)) return;
2809
4199
  if ("output" in result) return await result["output"];
2810
4200
  if ("textStream" in result) return;
2811
4201
  return await require_decision.normalizeGeneratorResult(result, `seam '${request.name ?? request.model}'`, { request });
@@ -2821,7 +4211,7 @@ async function seamOutputOf(result, request) {
2821
4211
  * seam's own effect completion (the first `xstate.done.*`/`xstate.error.*`
2822
4212
  * entry appended after the call was made).
2823
4213
  *
2824
- * @example Keyless: the seam is scripted too, so the whole thing runs offline.
4214
+ * @example No API key: the seam is scripted too, so the whole thing runs offline.
2825
4215
  * ```ts
2826
4216
  * const run = await runSeam(emailDrafter, {
2827
4217
  * scripts: { promptEvaluator: [vague, complete], emailDrafter: [draft] },
@@ -2865,7 +4255,7 @@ async function runSeam(machine, options) {
2865
4255
  };
2866
4256
  const scriptedAnswer = async (request, info) => {
2867
4257
  const scripted = await takeScriptedSlot(request, info);
2868
- if (!scripted) throw new require_errors.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
4258
+ if (!scripted) throw new require_decision.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
2869
4259
  return scripted;
2870
4260
  };
2871
4261
  const route = async (request, info) => {
@@ -2914,27 +4304,22 @@ async function runSeam(machine, options) {
2914
4304
  };
2915
4305
  let snapshot;
2916
4306
  let event;
2917
- let events = [];
4307
+ const events = [];
2918
4308
  let result;
2919
4309
  const maxTurns = options.maxTurns ?? 12;
2920
4310
  for (let turn = 0; turn <= maxTurns; turn++) {
2921
4311
  result = await runAgent(machine, {
2922
4312
  ...snapshot ? { snapshot } : { input: options.input },
2923
4313
  ...event ? { event } : {},
2924
- ...options.isIdle ? { isIdle: options.isIdle } : {},
2925
4314
  ...options.actors ? { actors: options.actors } : {},
2926
- events,
2927
4315
  executors,
2928
- onEvent: () => {
2929
- liveEvents++;
2930
- },
2931
4316
  onTransition: (next, causedBy) => {
2932
4317
  if (turn > 0 && causedBy.type === "@xstate.init") return;
4318
+ events.push(causedBy);
4319
+ liveEvents = events.length;
2933
4320
  statePath.push(next.value);
2934
4321
  }
2935
4322
  });
2936
- events = result.events;
2937
- liveEvents = events.length;
2938
4323
  if (result.status !== "idle" || turn === maxTurns) break;
2939
4324
  const next = options.respond?.({
2940
4325
  snapshot: result.snapshot,
@@ -2945,13 +4330,13 @@ async function runSeam(machine, options) {
2945
4330
  });
2946
4331
  if (!next) break;
2947
4332
  event = next;
2948
- snapshot = result.snapshot;
4333
+ snapshot = result.persist();
2949
4334
  }
2950
4335
  let splitAt = events.length;
2951
4336
  if (seamReached) {
2952
4337
  splitAt = seamEventAt;
2953
4338
  for (let index = seamEventAt; index < events.length; index++) {
2954
- const type = events[index].event.type;
4339
+ const type = events[index].type;
2955
4340
  if (type.startsWith("xstate.done") || type.startsWith("xstate.error")) {
2956
4341
  splitAt = index;
2957
4342
  break;
@@ -2976,275 +4361,81 @@ async function runSeam(machine, options) {
2976
4361
  };
2977
4362
  }
2978
4363
  //#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
3184
- exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
3185
- exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
4364
+ exports.AGENT_EVENT_SCHEMA_VERSION = AGENT_EVENT_SCHEMA_VERSION;
4365
+ exports.AGENT_INIT_EVENT_TYPE = AGENT_INIT_EVENT_TYPE;
4366
+ exports.AGENT_MESSAGES_EVENT_TYPE = require_decision.AGENT_MESSAGES_EVENT_TYPE;
3186
4367
  exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
3187
4368
  exports.AGENT_USAGE_EVENT_TYPE = require_setup_agent.AGENT_USAGE_EVENT_TYPE;
3188
4369
  exports.AgentDecisionExhaustedError = require_decision.AgentDecisionExhaustedError;
3189
- exports.AgentError = require_errors.AgentError;
3190
- exports.AgentEventLogConflictError = require_event_log_store.AgentEventLogConflictError;
3191
- exports.AgentIdleError = AgentIdleError;
3192
- exports.AgentIllegalResumeEventError = AgentIllegalResumeEventError;
4370
+ exports.AgentError = require_decision.AgentError;
4371
+ exports.AgentEventLogConflictError = AgentEventLogConflictError;
4372
+ exports.AgentEventLogError = AgentEventLogError;
4373
+ exports.AgentInvalidEventPayloadError = require_decision.AgentInvalidEventPayloadError;
3193
4374
  exports.AgentLintError = AgentLintError;
4375
+ exports.AgentMachineVersionMismatchError = AgentMachineVersionMismatchError;
3194
4376
  exports.AgentMaxModelCallsExceededError = AgentMaxModelCallsExceededError;
3195
- exports.AgentReplayDivergenceError = require_setup_agent.AgentReplayDivergenceError;
3196
- exports.AgentReplayMachineMismatchError = require_setup_agent.AgentReplayMachineMismatchError;
3197
- exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
3198
- exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
3199
- exports.appendMessages = require_setup_agent.appendMessages;
3200
- exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
4377
+ exports.AgentReplayDivergenceError = AgentReplayDivergenceError;
4378
+ exports.AgentSnapshotDivergedError = AgentSnapshotDivergedError;
4379
+ exports.AgentTruncatedError = require_decision.AgentTruncatedError;
4380
+ exports.AgentUnknownStateError = AgentUnknownStateError;
4381
+ exports.NonSerializableAgentEventError = NonSerializableAgentEventError;
4382
+ exports.agentCallOccurrence = agentCallOccurrence;
4383
+ exports.appendMessages = require_decision.appendMessages;
4384
+ exports.assertAgentLogEntry = assertAgentLogEntry;
3201
4385
  exports.assertAgentMachine = assertAgentMachine;
3202
- exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
3203
- exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
4386
+ exports.assertEventLogStoreConformance = assertEventLogStoreConformance;
4387
+ exports.assertJsonSerializable = assertJsonSerializable;
3204
4388
  exports.assistantMessage = require_decision.assistantMessage;
3205
4389
  exports.bindRequestExecutor = require_decision.bindRequestExecutor;
3206
4390
  exports.buildEnvelopeSchema = require_decision.buildEnvelopeSchema;
3207
4391
  exports.canReach = canReach;
3208
- exports.createAgentActor = createAgentActor;
3209
- exports.createAgentRun = createAgentRun;
3210
4392
  exports.createAgentSchemas = require_setup_agent.createAgentSchemas;
3211
4393
  exports.createDecisionLogic = require_decision.createDecisionLogic;
3212
- exports.createInMemoryEventLogStore = require_event_log_store.createInMemoryEventLogStore;
3213
- exports.createReplayEntry = require_setup_agent.createReplayEntry;
4394
+ exports.createDecisionRequest = require_decision.createDecisionRequest;
4395
+ exports.createInMemoryEventLogStore = createInMemoryEventLogStore;
4396
+ exports.createReplayEntry = createReplayEntry;
3214
4397
  exports.createScriptedExecutors = createScriptedExecutors;
3215
4398
  exports.createTextLogic = require_decision.createTextLogic;
3216
- exports.diffEventLogs = require_setup_agent.diffEventLogs;
3217
- exports.executeAgentRequest = require_setup_agent.executeAgentRequest;
4399
+ exports.eventFromInteraction = eventFromInteraction;
4400
+ exports.executeAgentRequest = executeAgentRequest;
3218
4401
  exports.explorePaths = explorePaths;
3219
- exports.generateResult = generateResult;
4402
+ exports.forkEventLog = forkEventLog;
3220
4403
  exports.getAcceptedEvents = require_decision.getAcceptedEvents;
3221
- exports.getAgentEffects = require_setup_agent.getAgentEffects;
3222
- exports.getAgentMessages = require_decision.getAgentMessages;
3223
4404
  exports.getAgentOutputMode = require_decision.getAgentOutputMode;
3224
4405
  exports.getAgentSchemas = require_setup_agent.getAgentSchemas;
4406
+ exports.getCallFinishReason = require_decision.getCallFinishReason;
3225
4407
  exports.getCallUsage = require_decision.getCallUsage;
4408
+ exports.getInteraction = getInteraction;
3226
4409
  exports.getJsonSchema = require_decision.getJsonSchema;
3227
4410
  exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
4411
+ exports.getLogExecutionId = getLogExecutionId;
3228
4412
  exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
3229
- exports.getSnapshotNodes = getSnapshotNodes;
3230
- exports.getSnapshotRequests = getSnapshotRequests;
4413
+ exports.getMessageText = require_decision.getMessageText;
4414
+ exports.getSnapshotStateHash = getSnapshotStateHash;
3231
4415
  exports.getStateMeta = require_decision.getStateMeta;
3232
- exports.initEntry = require_setup_agent.initEntry;
4416
+ exports.getStatePath = require_decision.getStatePath;
4417
+ exports.getUsageFromEvents = getUsageFromEvents;
4418
+ exports.initEntry = initEntry;
3233
4419
  exports.inspectTransitions = inspectTransitions;
4420
+ exports.interactionMetaSchema = interactionMetaSchema;
4421
+ exports.isAgentIdle = isAgentIdle;
4422
+ exports.isAgentMessages = require_decision.isAgentMessages;
3234
4423
  exports.isStandardSchema = require_decision.isStandardSchema;
3235
4424
  exports.lintAgentMachine = lintAgentMachine;
3236
4425
  exports.matchesTrajectory = matchesTrajectory;
3237
- exports.messagesSchema = require_setup_agent.messagesSchema;
4426
+ exports.messagesSchema = require_decision.messagesSchema;
3238
4427
  exports.parseAgentEvent = require_decision.parseAgentEvent;
3239
4428
  exports.parseModelRef = require_decision.parseModelRef;
3240
4429
  exports.parseOutput = require_decision.parseOutput;
3241
4430
  exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
3242
4431
  exports.provideExecutors = provideExecutors;
4432
+ exports.rebindActorSession = rebindActorSession;
3243
4433
  exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
3244
- exports.replay = require_setup_agent.replay;
4434
+ exports.replay = replay;
3245
4435
  exports.resolveDecision = require_decision.resolveDecision;
3246
4436
  exports.runAgent = runAgent;
3247
- exports.runDurableAgent = runDurableAgent;
4437
+ exports.runAgentLoop = runAgentLoop;
4438
+ exports.runAgentStream = runAgentStream;
3248
4439
  exports.runSeam = runSeam;
3249
4440
  exports.serializeTraceEvent = serializeTraceEvent;
3250
4441
  Object.defineProperty(exports, "setupAgent", {
@@ -3258,3 +4449,4 @@ exports.systemMessage = require_decision.systemMessage;
3258
4449
  exports.toolMessage = require_decision.toolMessage;
3259
4450
  exports.traceTransitions = traceTransitions;
3260
4451
  exports.userMessage = require_decision.userMessage;
4452
+ exports.validateReplayEntries = validateReplayEntries;