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

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