@statelyai/agent 2.0.0-alpha.18 → 2.0.0-alpha.20
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.
- package/dist/ai-sdk.cjs +4 -2
- package/dist/ai-sdk.d.cts +1 -1
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +3 -2
- package/dist/{decision-C11xuud2.mjs → decision-BfhSgCc6.mjs} +48 -28
- package/dist/{decision-DnQCQPew.cjs → decision-DhsKLYAI.cjs} +64 -43
- package/dist/{event-log-store-CQJq8_v4.d.cts → event-log-store-Bz7HDBkE.d.cts} +11 -12
- package/dist/{event-log-store-B-1fcfkT.mjs → event-log-store-DmIDosD6.mjs} +22 -14
- package/dist/{event-log-store-yquOV1TX.cjs → event-log-store-a_TKy1gk.cjs} +22 -14
- package/dist/{event-log-store-BrC9Q1xW.d.mts → event-log-store-hrA1vqtN.d.mts} +11 -12
- package/dist/index.cjs +458 -109
- package/dist/index.d.cts +209 -81
- package/dist/index.d.mts +209 -81
- package/dist/index.mjs +455 -108
- package/dist/machines.cjs +13 -17
- package/dist/machines.d.cts +14 -17
- package/dist/machines.d.mts +14 -17
- package/dist/machines.mjs +13 -17
- package/dist/otel.cjs +1 -0
- package/dist/otel.d.cts +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/{run-agent-r9OD4z8F.d.cts → run-agent--4bbms-D.d.cts} +121 -48
- package/dist/{run-agent-DQIDikfd.d.mts → run-agent-CwmzAZwj.d.mts} +121 -48
- package/dist/{setup-agent-BrE2zFDy.mjs → setup-agent-BOcSpsIq.mjs} +38 -28
- package/dist/{setup-agent-C3ETi_HZ.cjs → setup-agent-gISRLxRe.cjs} +44 -33
- package/dist/sqlite.cjs +3 -9
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.mts +1 -1
- package/dist/sqlite.mjs +3 -9
- package/dist/{text-logic-VcWqO-Cl.d.cts → text-logic-Cavva1W6.d.cts} +24 -8
- package/dist/{text-logic-RvnlD-An.d.mts → text-logic-Er5KkTX6.d.mts} +24 -8
- package/dist/validate.cjs +436 -0
- package/dist/validate.d.cts +31 -0
- package/dist/validate.d.mts +31 -0
- package/dist/validate.mjs +411 -0
- package/package.json +17 -3
- package/schemas/agent-workflow.json +2 -2
- package/skills/generate-machine/SKILL.md +12 -14
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_errors = require("./errors-DUBBzRLP.cjs");
|
|
3
|
-
const require_setup_agent = require("./setup-agent-
|
|
4
|
-
const require_decision = require("./decision-
|
|
5
|
-
const require_event_log_store = require("./event-log-store-
|
|
3
|
+
const require_setup_agent = require("./setup-agent-gISRLxRe.cjs");
|
|
4
|
+
const require_decision = require("./decision-DhsKLYAI.cjs");
|
|
5
|
+
const require_event_log_store = require("./event-log-store-a_TKy1gk.cjs");
|
|
6
|
+
require("./validate.cjs");
|
|
6
7
|
let xstate = require("xstate");
|
|
8
|
+
let xstate_durable = require("xstate/durable");
|
|
7
9
|
//#region src/internal/state-request-pass.ts
|
|
8
10
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
9
11
|
const { model, system } = stateRequest;
|
|
@@ -103,7 +105,7 @@ async function runAdvancePhase(plan, deps) {
|
|
|
103
105
|
messages: [...deps.messages],
|
|
104
106
|
events,
|
|
105
107
|
attempts: []
|
|
106
|
-
}, deps.decide, {
|
|
108
|
+
}, { decide: deps.decide }, {
|
|
107
109
|
signal: deps.signal,
|
|
108
110
|
canTake: (event) => deps.getSnapshot().can(event)
|
|
109
111
|
});
|
|
@@ -139,8 +141,7 @@ async function runStateRequestPass(requests, deps) {
|
|
|
139
141
|
* {@link getAcceptedEvents}). A programmer/integration error, in the same
|
|
140
142
|
* class as runAgent's bind-time throws — it throws rather than settling an
|
|
141
143
|
* `error` result. A type-legal event a guard rejects is NOT this error (the
|
|
142
|
-
* machine simply takes no transition).
|
|
143
|
-
* {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
|
|
144
|
+
* machine simply takes no transition). Always enforced; there is no opt-out.
|
|
144
145
|
*/
|
|
145
146
|
var AgentIllegalResumeEventError = class extends require_errors.AgentError {
|
|
146
147
|
eventType;
|
|
@@ -258,7 +259,7 @@ function toJsonValue(value, ancestors) {
|
|
|
258
259
|
* in a JSONL file). Live values are sanitized rather than trusted:
|
|
259
260
|
*
|
|
260
261
|
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
261
|
-
* same JSON round-trip as
|
|
262
|
+
* same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
|
|
262
263
|
* what a resume would see.
|
|
263
264
|
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
264
265
|
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
@@ -285,11 +286,92 @@ function serializeTraceEvent(event, options = {}) {
|
|
|
285
286
|
}
|
|
286
287
|
return out;
|
|
287
288
|
}
|
|
289
|
+
function snapshotNodes(snapshot) {
|
|
290
|
+
return (snapshot.nodes ?? []).map((raw) => {
|
|
291
|
+
const node = raw;
|
|
292
|
+
return {
|
|
293
|
+
id: node.id ?? "",
|
|
294
|
+
key: node.key ?? "",
|
|
295
|
+
...node.description !== void 0 ? { description: node.description } : {},
|
|
296
|
+
tags: [...node.tags ?? []],
|
|
297
|
+
...node.meta !== void 0 ? { meta: node.meta } : {},
|
|
298
|
+
ownEvents: [...node.ownEvents ?? []],
|
|
299
|
+
leaf: Object.keys(node.states ?? {}).length === 0
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
|
|
305
|
+
* descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
|
|
306
|
+
* your `getRequests` hook needs to build requests some other way, so host code
|
|
307
|
+
* never touches xstate's private `snapshot._nodes`.
|
|
308
|
+
*/
|
|
309
|
+
function getSnapshotNodes(snapshot) {
|
|
310
|
+
return snapshotNodes(snapshot);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Builds the {@link AgentStateRequest}s for a snapshot straight from its active
|
|
314
|
+
* state nodes — the prompts-in-descriptions recipe as a function, so a
|
|
315
|
+
* {@link RunAgentOptions.getRequests} hook is one line and never reaches into
|
|
316
|
+
* xstate's private `snapshot._nodes`:
|
|
317
|
+
*
|
|
318
|
+
* ```ts
|
|
319
|
+
* runAgent(machine, {
|
|
320
|
+
* executors,
|
|
321
|
+
* getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
|
|
322
|
+
* });
|
|
323
|
+
* ```
|
|
324
|
+
*
|
|
325
|
+
* Each described active node becomes one request: `prompt` from the node's
|
|
326
|
+
* `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
|
|
327
|
+
* from `meta.role` when present, `allowedEvents` scoped to the node's own
|
|
328
|
+
* events, and an explicit `onDone` when the node has exactly one own event
|
|
329
|
+
* (single-outcome states advance deterministically; anything else falls
|
|
330
|
+
* through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
|
|
331
|
+
* run settles idle for a human. Override any of it with `filter`/`map`.
|
|
332
|
+
*/
|
|
333
|
+
function getSnapshotRequests(snapshot, options) {
|
|
334
|
+
const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
|
|
335
|
+
const requests = [];
|
|
336
|
+
for (const node of snapshotNodes(snapshot)) {
|
|
337
|
+
if (!filter(node)) continue;
|
|
338
|
+
const system = node.meta?.role;
|
|
339
|
+
const request = {
|
|
340
|
+
model: options.model,
|
|
341
|
+
prompt: node.description ?? "",
|
|
342
|
+
kind: node.tags.includes("decision") ? "decision" : "text",
|
|
343
|
+
...system !== void 0 ? { system } : {},
|
|
344
|
+
...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
|
|
345
|
+
...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
|
|
346
|
+
};
|
|
347
|
+
const mapped = options.map ? options.map(request, node) : request;
|
|
348
|
+
if (mapped) requests.push(mapped);
|
|
349
|
+
}
|
|
350
|
+
return requests;
|
|
351
|
+
}
|
|
288
352
|
let nextRunAgentTraceId = 1;
|
|
353
|
+
/**
|
|
354
|
+
* Thrown into the invoke that would have made the call once
|
|
355
|
+
* {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
|
|
356
|
+
* through the normal error channel, so an invoke's `onError` can branch on it
|
|
357
|
+
* (`error.code === 'max-model-calls'`, the same string the settled result's
|
|
358
|
+
* `cause` uses) and route to a degraded/finish state instead of failing the
|
|
359
|
+
* run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
|
|
360
|
+
*
|
|
361
|
+
* ```ts
|
|
362
|
+
* onError: [
|
|
363
|
+
* { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
|
|
364
|
+
* { target: 'failed' },
|
|
365
|
+
* ]
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
289
368
|
var AgentMaxModelCallsExceededError = class extends require_errors.AgentError {
|
|
290
|
-
|
|
291
|
-
|
|
369
|
+
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
370
|
+
maxModelCalls;
|
|
371
|
+
constructor(maxModelCalls) {
|
|
372
|
+
super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
|
|
292
373
|
this.name = "AgentMaxModelCallsExceededError";
|
|
374
|
+
this.maxModelCalls = maxModelCalls;
|
|
293
375
|
}
|
|
294
376
|
};
|
|
295
377
|
function wrapsDecisionExhausted(error) {
|
|
@@ -329,7 +411,7 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
|
329
411
|
* walk into invoked child machines (their internal agent requests are opaque
|
|
330
412
|
* to the parent-level source walk otherwise).
|
|
331
413
|
*/
|
|
332
|
-
function
|
|
414
|
+
function isStateMachineLogic(logic) {
|
|
333
415
|
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
334
416
|
}
|
|
335
417
|
/**
|
|
@@ -361,7 +443,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
361
443
|
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
362
444
|
for (const { stateName, src } of invokes) {
|
|
363
445
|
if (typeof src !== "string") {
|
|
364
|
-
if (
|
|
446
|
+
if (isStateMachineLogic(src)) {
|
|
365
447
|
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
366
448
|
continue;
|
|
367
449
|
}
|
|
@@ -370,7 +452,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
370
452
|
}
|
|
371
453
|
const logic = effectiveSources[src];
|
|
372
454
|
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
373
|
-
if (
|
|
455
|
+
if (isStateMachineLogic(logic)) {
|
|
374
456
|
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
375
457
|
continue;
|
|
376
458
|
}
|
|
@@ -416,18 +498,16 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
|
416
498
|
return /* @__PURE__ */ new Error(`runAgent: child machine '${childPath}' (state '${stateName}') invokes ${kind} source '${requestSrc}', which has no host execution and is reached through a direct-object invoke src that runAgent cannot rebind. Requests reached through string-keyed actor sources inherit runAgent's generateText/streamText/decide executors automatically; a direct-object child machine does not. Either bind the request with its own executor (requestLogic.withExecutor(...)), or register the child as a string-keyed actor source (machine.provide({ actors: { <child>: childMachine } })) and invoke it by name.`);
|
|
417
499
|
}
|
|
418
500
|
/**
|
|
419
|
-
* True when the snapshot's active states declare a transition
|
|
420
|
-
* `'@agent.usage'` type
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
* a wildcard machine's context and event log byte-identical to a run without
|
|
427
|
-
* the feature. @internal
|
|
501
|
+
* True when the snapshot's active states declare a transition that would
|
|
502
|
+
* receive the reserved `'@agent.usage'` type — an explicit `on: { '@agent.usage'
|
|
503
|
+
* }` OR a catch-all `on: { '*': … }`. Plain XState semantics apply unmodified:
|
|
504
|
+
* a wildcard matches every event delivered to the machine, reserved ones
|
|
505
|
+
* included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
|
|
506
|
+
* `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
|
|
507
|
+
* the reserved event as a decision candidate.) @internal
|
|
428
508
|
*/
|
|
429
509
|
function declaresUsageTransition(snapshot) {
|
|
430
|
-
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType ===
|
|
510
|
+
return (0, xstate.getNextTransitions)(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
|
|
431
511
|
}
|
|
432
512
|
/**
|
|
433
513
|
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
@@ -494,7 +574,7 @@ function bindTextLogic(logic, runCtx) {
|
|
|
494
574
|
const self = selfArg;
|
|
495
575
|
const { id, src } = selfIdAndSrc(self);
|
|
496
576
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
497
|
-
if (!executor) throw new Error(`
|
|
577
|
+
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
498
578
|
const requestWithTools = {
|
|
499
579
|
...request,
|
|
500
580
|
tools: request.tools ?? {}
|
|
@@ -566,17 +646,22 @@ function bindTextLogic(logic, runCtx) {
|
|
|
566
646
|
});
|
|
567
647
|
}
|
|
568
648
|
function createCountingDecide(runCtx, self) {
|
|
569
|
-
return async (attemptRequest) => {
|
|
649
|
+
return async (attemptRequest, info) => {
|
|
570
650
|
runCtx.consumeModelCall();
|
|
571
651
|
runCtx.emitTrace?.({
|
|
572
652
|
type: "request.start",
|
|
573
653
|
request: attemptRequest
|
|
574
654
|
}, self);
|
|
575
655
|
try {
|
|
656
|
+
const { id } = selfIdAndSrc(self);
|
|
576
657
|
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
577
658
|
...attemptRequest,
|
|
578
659
|
runId: runCtx.runId
|
|
579
|
-
} : attemptRequest
|
|
660
|
+
} : attemptRequest, {
|
|
661
|
+
...info,
|
|
662
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
663
|
+
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
|
|
664
|
+
});
|
|
580
665
|
const usage = require_decision.getCallUsage(result);
|
|
581
666
|
if (usage) {
|
|
582
667
|
const { src } = selfIdAndSrc(self);
|
|
@@ -622,7 +707,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
622
707
|
*/
|
|
623
708
|
function bindDecisionLogic(logic, runCtx) {
|
|
624
709
|
const decisionLogic = (0, xstate.createAsyncLogic)({ run: async ({ input, signal, self: selfArg }) => {
|
|
625
|
-
if (!runCtx.decide) throw new Error("
|
|
710
|
+
if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
|
|
626
711
|
const self = selfArg;
|
|
627
712
|
const { id } = selfIdAndSrc(self);
|
|
628
713
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
@@ -636,7 +721,7 @@ function bindDecisionLogic(logic, runCtx) {
|
|
|
636
721
|
...logic.request(input),
|
|
637
722
|
id,
|
|
638
723
|
events
|
|
639
|
-
}, createCountingDecide(runCtx, self), {
|
|
724
|
+
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
640
725
|
maxRetries: logic.maxRetries,
|
|
641
726
|
signal,
|
|
642
727
|
canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
|
|
@@ -681,7 +766,7 @@ function rootTraceState(root) {
|
|
|
681
766
|
if (!state) {
|
|
682
767
|
const logic = root.logic;
|
|
683
768
|
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
684
|
-
const machineVersion = logic ? require_decision.
|
|
769
|
+
const machineVersion = logic ? require_decision.resolveMachineVersion(logic) : "";
|
|
685
770
|
state = {
|
|
686
771
|
runId: `run_${nextProvideRunId++}`,
|
|
687
772
|
seq: 0,
|
|
@@ -753,9 +838,9 @@ function provideBindContext(machine, executors, options) {
|
|
|
753
838
|
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
754
839
|
*
|
|
755
840
|
* Gating is identical on both: the target snapshot must be active, must declare
|
|
756
|
-
* an `'@agent.usage'` transition
|
|
757
|
-
*
|
|
758
|
-
* event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
841
|
+
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
842
|
+
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
843
|
+
* take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
759
844
|
* call that settled after the cycle resolved, which drops the event (traced as
|
|
760
845
|
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
761
846
|
* settle, so it passes no gate and has no dropped stragglers.
|
|
@@ -797,6 +882,19 @@ function bindDecisionForProvide(machine, logic, executors, options) {
|
|
|
797
882
|
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
798
883
|
}
|
|
799
884
|
/**
|
|
885
|
+
* Recursively binds an invoked child state machine for {@link provideExecutors},
|
|
886
|
+
* with the same semantics `runAgent` applies ({@link rebindChildMachine}):
|
|
887
|
+
* string-keyed text/decision sources at any depth inherit the host executors,
|
|
888
|
+
* a source that carries its own executor is left alone, and a cycle is
|
|
889
|
+
* returned as-is. Each machine in the tree is bound with its own registered
|
|
890
|
+
* `setupAgent` schemas. Returns the original machine when nothing needed
|
|
891
|
+
* wrapping. @internal
|
|
892
|
+
*/
|
|
893
|
+
function bindChildMachineForProvide(childMachine, executors, options, visited) {
|
|
894
|
+
const ctxFor = (target) => provideBindContext(target, executors, options);
|
|
895
|
+
return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
800
898
|
* Validates `input` against the machine's registered input schema, returning
|
|
801
899
|
* the schema's output — so defaults are filled and transforms applied before
|
|
802
900
|
* the value reaches `createActor` or the replayable event log.
|
|
@@ -830,9 +928,10 @@ function resolveMachineInput(machine, input) {
|
|
|
830
928
|
* (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
|
|
831
929
|
* Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
|
|
832
930
|
*/
|
|
833
|
-
function rebindChildMachine(childMachine, runCtx, visited) {
|
|
931
|
+
function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
834
932
|
if (visited.has(childMachine)) return childMachine;
|
|
835
933
|
const childVisited = new Set([...visited, childMachine]);
|
|
934
|
+
runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
|
|
836
935
|
const sources = childMachine.sources.actors;
|
|
837
936
|
const wrapped = {};
|
|
838
937
|
for (const [key, logic] of Object.entries(sources)) {
|
|
@@ -844,8 +943,8 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
844
943
|
if (!require_decision.executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
845
944
|
continue;
|
|
846
945
|
}
|
|
847
|
-
if (
|
|
848
|
-
const rebound = rebindChildMachine(logic, runCtx, childVisited);
|
|
946
|
+
if (isStateMachineLogic(logic)) {
|
|
947
|
+
const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
|
|
849
948
|
if (rebound !== logic) wrapped[key] = rebound;
|
|
850
949
|
continue;
|
|
851
950
|
}
|
|
@@ -928,7 +1027,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
928
1027
|
let traceSeq = 0;
|
|
929
1028
|
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
930
1029
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
931
|
-
const machineVersion =
|
|
1030
|
+
const machineVersion = require_decision.resolveMachineVersion(machine);
|
|
932
1031
|
const agentMeta = {
|
|
933
1032
|
machineId,
|
|
934
1033
|
version: machineVersion
|
|
@@ -950,10 +1049,10 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
950
1049
|
onTransition: options.onTransition
|
|
951
1050
|
});
|
|
952
1051
|
const consumeModelCall = () => {
|
|
953
|
-
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
1052
|
+
if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
954
1053
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
955
1054
|
budgetExceeded = true;
|
|
956
|
-
throw new AgentMaxModelCallsExceededError();
|
|
1055
|
+
throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
957
1056
|
}
|
|
958
1057
|
modelCallCount += 1;
|
|
959
1058
|
};
|
|
@@ -1027,19 +1126,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1027
1126
|
if (!require_decision.executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1028
1127
|
continue;
|
|
1029
1128
|
}
|
|
1030
|
-
if (
|
|
1129
|
+
if (isStateMachineLogic(logic)) {
|
|
1031
1130
|
const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
|
|
1032
1131
|
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1033
1132
|
continue;
|
|
1034
1133
|
}
|
|
1035
1134
|
}
|
|
1036
1135
|
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1136
|
+
const declaredIdlePredicate = options.isIdle ?? require_decision.getMachineIdlePredicate(machine);
|
|
1137
|
+
const isIdle = declaredIdlePredicate ?? (() => false);
|
|
1138
|
+
const machineDeclaresMigrate = typeof machine.config.migrate === "function";
|
|
1039
1139
|
let effectiveSnapshot = options.snapshot;
|
|
1040
1140
|
if (effectiveSnapshot !== void 0) {
|
|
1041
1141
|
const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
|
|
1042
|
-
if (from !== void 0 && from !== machineVersion) {
|
|
1142
|
+
if (from !== void 0 && from !== machineVersion && !(machineDeclaresMigrate && !options.migrateSnapshot)) {
|
|
1043
1143
|
const info = {
|
|
1044
1144
|
from,
|
|
1045
1145
|
to: machineVersion
|
|
@@ -1058,10 +1158,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1058
1158
|
effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
|
|
1059
1159
|
}
|
|
1060
1160
|
const machineOwnVersion = machine.version;
|
|
1061
|
-
if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1161
|
+
if (!machineDeclaresMigrate && effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1062
1162
|
const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
|
|
1063
1163
|
if (machineOwnVersion === void 0) delete aligned.version;
|
|
1064
1164
|
else aligned.version = machineOwnVersion;
|
|
1165
|
+
delete aligned.machine;
|
|
1065
1166
|
effectiveSnapshot = aligned;
|
|
1066
1167
|
}
|
|
1067
1168
|
const priorMessages = require_decision.getAgentMessages(effectiveSnapshot);
|
|
@@ -1072,7 +1173,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1072
1173
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1073
1174
|
snapshot.messages = [...messages];
|
|
1074
1175
|
};
|
|
1075
|
-
if (effectiveSnapshot !== void 0 && options.event !== void 0
|
|
1176
|
+
if (effectiveSnapshot !== void 0 && options.event !== void 0) {
|
|
1076
1177
|
const acceptedTypes = require_decision.getAcceptedEvents((0, xstate.createActor)(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
1077
1178
|
const eventType = options.event.type;
|
|
1078
1179
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
@@ -1218,9 +1319,9 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1218
1319
|
const current = actor.getSnapshot();
|
|
1219
1320
|
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1220
1321
|
if (!maybeInterpret(current)) {
|
|
1221
|
-
if (!
|
|
1322
|
+
if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1222
1323
|
warnedHeuristicIdle = true;
|
|
1223
|
-
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no
|
|
1324
|
+
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').");
|
|
1224
1325
|
}
|
|
1225
1326
|
settleIdle(current);
|
|
1226
1327
|
}
|
|
@@ -1275,11 +1376,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1275
1376
|
});
|
|
1276
1377
|
return;
|
|
1277
1378
|
}
|
|
1278
|
-
if (!deliveringResumeEvent &&
|
|
1379
|
+
if (!deliveringResumeEvent && isIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1279
1380
|
queueMicrotask(() => {
|
|
1280
1381
|
if (settled) return;
|
|
1281
1382
|
const current = actor.getSnapshot();
|
|
1282
|
-
if (
|
|
1383
|
+
if (isIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1283
1384
|
if (!maybeInterpret(current)) settleIdle(current);
|
|
1284
1385
|
} else scheduleIdleCheck();
|
|
1285
1386
|
});
|
|
@@ -1576,9 +1677,12 @@ function createAgentRun(machine, options) {
|
|
|
1576
1677
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1577
1678
|
* not provide.
|
|
1578
1679
|
*
|
|
1579
|
-
*
|
|
1580
|
-
* machine
|
|
1581
|
-
*
|
|
1680
|
+
* Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
|
|
1681
|
+
* invoked child machine is rebound too, so its own text/decision requests — at
|
|
1682
|
+
* any depth — reach the same host executors. A direct-object invoke `src`
|
|
1683
|
+
* cannot be swapped via `.provide`, so nothing under one inherits; bind those
|
|
1684
|
+
* with `.withExecutor(...)` or register the child as a string-keyed source. A
|
|
1685
|
+
* source that already carries its own executor is never overwritten.
|
|
1582
1686
|
*/
|
|
1583
1687
|
function provideExecutors(machine, executors, options = {}) {
|
|
1584
1688
|
const bindOptions = {
|
|
@@ -1592,21 +1696,18 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1592
1696
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1593
1697
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1594
1698
|
if (key === "agent.userInput") continue;
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
};
|
|
1601
|
-
else if (require_decision.isTextLogic(logic)) {
|
|
1602
|
-
const streaming = logic.mode === "stream";
|
|
1603
|
-
binding = {
|
|
1604
|
-
executorKey: streaming ? "streamText" : "generateText",
|
|
1605
|
-
kind: streaming ? "streaming text" : "text",
|
|
1606
|
-
bind: () => bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1607
|
-
};
|
|
1699
|
+
if (isStateMachineLogic(logic)) {
|
|
1700
|
+
if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
|
|
1701
|
+
const rebound = bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
|
|
1702
|
+
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1703
|
+
continue;
|
|
1608
1704
|
}
|
|
1609
|
-
|
|
1705
|
+
const requirement = executorRequirementOf(logic);
|
|
1706
|
+
if (!requirement) continue;
|
|
1707
|
+
const binding = {
|
|
1708
|
+
...requirement,
|
|
1709
|
+
bind: () => require_decision.isDecisionLogic(logic) ? bindDecisionForProvide(provided, logic, executors, bindOptions) : bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1710
|
+
};
|
|
1610
1711
|
if (require_decision.executorBoundLogics.has(logic)) continue;
|
|
1611
1712
|
if (!executors[binding.executorKey]) {
|
|
1612
1713
|
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
@@ -1616,12 +1717,50 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1616
1717
|
}
|
|
1617
1718
|
return withActors(provided, wrappedSources);
|
|
1618
1719
|
}
|
|
1720
|
+
/** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
|
|
1721
|
+
function executorRequirementOf(logic) {
|
|
1722
|
+
if (require_decision.isDecisionLogic(logic)) return {
|
|
1723
|
+
executorKey: "decide",
|
|
1724
|
+
kind: "decision"
|
|
1725
|
+
};
|
|
1726
|
+
if (require_decision.isTextLogic(logic)) return logic.mode === "stream" ? {
|
|
1727
|
+
executorKey: "streamText",
|
|
1728
|
+
kind: "streaming text"
|
|
1729
|
+
} : {
|
|
1730
|
+
executorKey: "generateText",
|
|
1731
|
+
kind: "text"
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Walks an invoked child machine's own invoked sources (recursively, at any
|
|
1736
|
+
* depth) and throws the same missing-executor error `provideExecutors` throws
|
|
1737
|
+
* for the top-level machine — before any actor starts. Mirrors runAgent's
|
|
1738
|
+
* `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
|
|
1739
|
+
*/
|
|
1740
|
+
function assertChildBindable(childMachine, executors, path, visited) {
|
|
1741
|
+
if (visited.has(childMachine)) return;
|
|
1742
|
+
const nextVisited = new Set([...visited, childMachine]);
|
|
1743
|
+
const sources = childMachine.sources.actors;
|
|
1744
|
+
for (const src of getConfiguredInvokeSrcs(childMachine)) {
|
|
1745
|
+
if (src === "agent.userInput") continue;
|
|
1746
|
+
const logic = sources[src];
|
|
1747
|
+
if (!logic) continue;
|
|
1748
|
+
if (isStateMachineLogic(logic)) {
|
|
1749
|
+
assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const requirement = executorRequirementOf(logic);
|
|
1753
|
+
if (!requirement || require_decision.executorBoundLogics.has(logic)) continue;
|
|
1754
|
+
if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1619
1757
|
function missingExecutorError(src, kind, executor) {
|
|
1620
1758
|
return /* @__PURE__ */ new Error(`provideExecutors: actor source '${src}' is a ${kind} source but no '${executor}' executor was provided. Add it to the executors object, or bind the source with its own executor (logic.withExecutor(...)) before calling provideExecutors.`);
|
|
1621
1759
|
}
|
|
1622
1760
|
//#endregion
|
|
1623
1761
|
//#region src/verify.ts
|
|
1624
1762
|
const DECIDE_SRC = "agent.decide";
|
|
1763
|
+
const USER_INPUT_SRC = "agent.userInput";
|
|
1625
1764
|
function normalizeInvokes(invoke) {
|
|
1626
1765
|
if (invoke === void 0 || invoke === null) return [];
|
|
1627
1766
|
return Array.isArray(invoke) ? invoke : [invoke];
|
|
@@ -1908,11 +2047,23 @@ const LINT_CHECKS = [
|
|
|
1908
2047
|
* call this to catch dead states, undeliverable decisions, un-rebindable
|
|
1909
2048
|
* invoke srcs, and output-contract gaps before ever running it.
|
|
1910
2049
|
*
|
|
2050
|
+
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
2051
|
+
* loops: it returns silently when the machine is clean and throws
|
|
2052
|
+
* {@link AgentLintError} (findings on `.diagnostics`) on error-severity
|
|
2053
|
+
* findings, or on warnings too with `{ throw: true, warnings: true }`.
|
|
2054
|
+
*
|
|
1911
2055
|
* @example
|
|
1912
2056
|
* ```ts
|
|
1913
2057
|
* const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
|
|
1914
2058
|
* if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
1915
2059
|
* ```
|
|
2060
|
+
*
|
|
2061
|
+
* @example Throwing form
|
|
2062
|
+
* ```ts
|
|
2063
|
+
* test('agent machine is structurally sound', () => {
|
|
2064
|
+
* lintAgentMachine(machine, { throw: true });
|
|
2065
|
+
* });
|
|
2066
|
+
* ```
|
|
1916
2067
|
*/
|
|
1917
2068
|
function lintAgentMachine(machine, options = {}) {
|
|
1918
2069
|
const config = machine.config ?? {};
|
|
@@ -1927,10 +2078,16 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1927
2078
|
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
1928
2079
|
};
|
|
1929
2080
|
const disabled = new Set(options.disable ?? []);
|
|
1930
|
-
|
|
2081
|
+
const diagnostics = LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
|
|
2082
|
+
if (options.throw) {
|
|
2083
|
+
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
2084
|
+
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
2085
|
+
}
|
|
2086
|
+
return diagnostics;
|
|
1931
2087
|
}
|
|
1932
2088
|
/**
|
|
1933
|
-
* Thrown by {
|
|
2089
|
+
* Thrown by `lintAgentMachine(machine, { throw: true })` when lint finds
|
|
2090
|
+
* failing diagnostics.
|
|
1934
2091
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
1935
2092
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
1936
2093
|
*/
|
|
@@ -1943,24 +2100,6 @@ var AgentLintError = class extends require_errors.AgentError {
|
|
|
1943
2100
|
this.diagnostics = diagnostics;
|
|
1944
2101
|
}
|
|
1945
2102
|
};
|
|
1946
|
-
/**
|
|
1947
|
-
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
1948
|
-
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
1949
|
-
* otherwise. Fails on error-severity findings; set `warnings: true` to fail on
|
|
1950
|
-
* warnings too. The one-liner for tests and generation loops:
|
|
1951
|
-
*
|
|
1952
|
-
* @example
|
|
1953
|
-
* ```ts
|
|
1954
|
-
* test('agent machine is structurally sound', () => {
|
|
1955
|
-
* assertAgentMachine(machine);
|
|
1956
|
-
* });
|
|
1957
|
-
* ```
|
|
1958
|
-
*/
|
|
1959
|
-
function assertAgentMachine(machine, options = {}) {
|
|
1960
|
-
const diagnostics = lintAgentMachine(machine, options);
|
|
1961
|
-
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
1962
|
-
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
1963
|
-
}
|
|
1964
2103
|
function pendingInvokes(step) {
|
|
1965
2104
|
const out = [];
|
|
1966
2105
|
for (const action of step.actions) {
|
|
@@ -2006,6 +2145,7 @@ async function simulateAgent(machine, options) {
|
|
|
2006
2145
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2007
2146
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2008
2147
|
};
|
|
2148
|
+
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
2009
2149
|
let step = require_setup_agent.initialAgentStep(machine, options.input);
|
|
2010
2150
|
const trail = [];
|
|
2011
2151
|
for (let i = 0; i < maxSteps; i++) {
|
|
@@ -2072,13 +2212,16 @@ function mapValues(obj, fn) {
|
|
|
2072
2212
|
}
|
|
2073
2213
|
function scriptDryError(kind, src, id, request) {
|
|
2074
2214
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2075
|
-
|
|
2215
|
+
const key = kind === "text" ? `text['${src}']` : kind === "decision" ? `decisions['${src}']` : src === USER_INPUT_SRC ? "userInput" : `invokes['${src}']`;
|
|
2216
|
+
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}`);
|
|
2076
2217
|
}
|
|
2077
2218
|
const MAX_ADVANCE_STEPS = 1e3;
|
|
2078
2219
|
async function explore(machine, options, stopWhen) {
|
|
2079
2220
|
const maxDepth = options.maxDepth ?? 8;
|
|
2080
2221
|
const maxPaths = options.maxPaths ?? 200;
|
|
2081
|
-
const
|
|
2222
|
+
const textScript = options.text ?? {};
|
|
2223
|
+
const invokeOutputs = { ...options.invokes ?? {} };
|
|
2224
|
+
if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
|
|
2082
2225
|
const reachedStates = /* @__PURE__ */ new Set();
|
|
2083
2226
|
const reachedValues = [];
|
|
2084
2227
|
const terminals = [];
|
|
@@ -2103,22 +2246,22 @@ async function explore(machine, options, stopWhen) {
|
|
|
2103
2246
|
if (current.done) return { step: current };
|
|
2104
2247
|
const request = current.requests[0];
|
|
2105
2248
|
if (request && request.kind === "text") {
|
|
2106
|
-
if (!(request.src in
|
|
2249
|
+
if (!(request.src in textScript)) return {
|
|
2107
2250
|
step: current,
|
|
2108
2251
|
blockedSrc: request.src
|
|
2109
2252
|
};
|
|
2110
|
-
current = require_setup_agent.resolveAgentStep(machine, current, request,
|
|
2253
|
+
current = require_setup_agent.resolveAgentStep(machine, current, request, textScript[request.src]);
|
|
2111
2254
|
recordState(current.snapshot);
|
|
2112
2255
|
continue;
|
|
2113
2256
|
}
|
|
2114
2257
|
if (request && request.kind === "decision") return { step: current };
|
|
2115
2258
|
const [invoke] = pendingInvokes(current);
|
|
2116
2259
|
if (invoke) {
|
|
2117
|
-
if (!(invoke.src in
|
|
2260
|
+
if (!(invoke.src in invokeOutputs)) return {
|
|
2118
2261
|
step: current,
|
|
2119
2262
|
blockedSrc: invoke.src
|
|
2120
2263
|
};
|
|
2121
|
-
current = require_setup_agent.resolveAgentStep(machine, current, invoke.id,
|
|
2264
|
+
current = require_setup_agent.resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
2122
2265
|
recordState(current.snapshot);
|
|
2123
2266
|
continue;
|
|
2124
2267
|
}
|
|
@@ -2210,9 +2353,10 @@ async function explore(machine, options, stopWhen) {
|
|
|
2210
2353
|
* depth, model-free, and reports which states are reached and how each path
|
|
2211
2354
|
* terminates. At each decision request it forks one branch per candidate event
|
|
2212
2355
|
* (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
|
|
2213
|
-
* an idle wait it forks per externally-accepted event. Text
|
|
2214
|
-
*
|
|
2215
|
-
*
|
|
2356
|
+
* an idle wait it forks per externally-accepted event. Text requests resolve
|
|
2357
|
+
* from `text`, other invokes from `invokes` (or `userInput` for
|
|
2358
|
+
* `agent.userInput`) — all by-src canned-output maps, and a missing src halts
|
|
2359
|
+
* that branch with a `needs-output` terminal rather than throwing.
|
|
2216
2360
|
*
|
|
2217
2361
|
* Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
|
|
2218
2362
|
* 200, reported via `hitPathCap`).
|
|
@@ -2228,13 +2372,14 @@ async function explorePaths(machine, options = {}) {
|
|
|
2228
2372
|
}
|
|
2229
2373
|
/**
|
|
2230
2374
|
* Answers "can the machine reach `statePath`?" by exploring its branches (a
|
|
2231
|
-
* thin wrapper over {@link explorePaths}). Returns
|
|
2232
|
-
* with the event sequence that reaches it, or
|
|
2375
|
+
* thin wrapper over {@link explorePaths}). Returns
|
|
2376
|
+
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
2377
|
+
* `{ reachable: false }`.
|
|
2233
2378
|
*
|
|
2234
2379
|
* @example
|
|
2235
2380
|
* ```ts
|
|
2236
|
-
* const {
|
|
2237
|
-
* //
|
|
2381
|
+
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
2382
|
+
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
2238
2383
|
* ```
|
|
2239
2384
|
*/
|
|
2240
2385
|
async function canReach(machine, statePath, options = {}) {
|
|
@@ -2246,9 +2391,9 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2246
2391
|
}
|
|
2247
2392
|
});
|
|
2248
2393
|
return witness !== void 0 ? {
|
|
2249
|
-
|
|
2394
|
+
reachable: true,
|
|
2250
2395
|
witness
|
|
2251
|
-
} : {
|
|
2396
|
+
} : { reachable: false };
|
|
2252
2397
|
}
|
|
2253
2398
|
//#endregion
|
|
2254
2399
|
//#region src/internal/is-record.ts
|
|
@@ -2409,7 +2554,8 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2409
2554
|
* Scripted executors — a keyless, deterministic stand-in for a model host.
|
|
2410
2555
|
*
|
|
2411
2556
|
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
2412
|
-
* set that plays back canned answers from FIFO
|
|
2557
|
+
* set (plus a `userInput` handler) that plays back canned answers from FIFO
|
|
2558
|
+
* queues, so `runAgent` (or
|
|
2413
2559
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2414
2560
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2415
2561
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
@@ -2485,15 +2631,27 @@ function emitScriptedChunk(result, info) {
|
|
|
2485
2631
|
* decisions: [(request) => ({ type: request.events[0]!.type })],
|
|
2486
2632
|
* });
|
|
2487
2633
|
* ```
|
|
2634
|
+
*
|
|
2635
|
+
* @example Scripted human input
|
|
2636
|
+
* ```ts
|
|
2637
|
+
* const scripted = createScriptedExecutors({ userInput: ['ship it'] });
|
|
2638
|
+
* await runAgent(machine, { executors: scripted, userInput: scripted.userInput });
|
|
2639
|
+
* ```
|
|
2488
2640
|
*/
|
|
2489
2641
|
function createScriptedExecutors(script = {}) {
|
|
2490
2642
|
const decisions = [...script.decisions ?? []];
|
|
2491
2643
|
const text = [...script.text ?? []];
|
|
2644
|
+
const userInput = [...script.userInput ?? []];
|
|
2492
2645
|
const nextText = async (request, info) => {
|
|
2493
2646
|
if (text.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending text request ${describeText(request)}. Add another entry to the script's \`text\` queue.`);
|
|
2494
2647
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2495
2648
|
};
|
|
2496
2649
|
return {
|
|
2650
|
+
userInput: async (input) => {
|
|
2651
|
+
if (userInput.length === 0) throw new require_errors.AgentError("scripted-executors-exhausted", `createScriptedExecutors: script ran dry on a pending userInput request (prompt: ${input.prompt ? `'${input.prompt}'` : "(none)"}). Add another entry to the script's \`userInput\` queue.`);
|
|
2652
|
+
const entry = userInput.shift();
|
|
2653
|
+
return typeof entry === "function" ? await entry(input) : entry;
|
|
2654
|
+
},
|
|
2497
2655
|
generateText: nextText,
|
|
2498
2656
|
streamText: async (request, info) => {
|
|
2499
2657
|
const result = await nextText(request, info);
|
|
@@ -2567,22 +2725,22 @@ async function runSeam(machine, options) {
|
|
|
2567
2725
|
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2568
2726
|
/**
|
|
2569
2727
|
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
2570
|
-
* its queue is dry.
|
|
2571
|
-
*
|
|
2728
|
+
* its queue is dry. With `repeatLast`, the last entry is replayed instead of
|
|
2729
|
+
* running dry.
|
|
2572
2730
|
*/
|
|
2573
2731
|
const takeScriptedSlot = async (request, info) => {
|
|
2574
2732
|
const queue = queues.get(queueKeyOf(request));
|
|
2575
2733
|
if (!queue?.length) return;
|
|
2576
|
-
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2734
|
+
return resolveScriptedTextEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2577
2735
|
};
|
|
2578
2736
|
const scriptedAnswer = async (request, info) => {
|
|
2579
2737
|
const scripted = await takeScriptedSlot(request, info);
|
|
2580
|
-
if (!scripted) throw new require_errors.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}
|
|
2738
|
+
if (!scripted) throw new require_errors.AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}\`, or pass \`repeatLast: true\` to replay its last entry down a longer branch.`);
|
|
2581
2739
|
return scripted;
|
|
2582
2740
|
};
|
|
2583
2741
|
const route = async (request, info) => {
|
|
2584
2742
|
const callIndex = calls++;
|
|
2585
|
-
const isSeam =
|
|
2743
|
+
const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
|
|
2586
2744
|
if (isSeam && candidate) {
|
|
2587
2745
|
await takeScriptedSlot(request, info);
|
|
2588
2746
|
seamReached = true;
|
|
@@ -2620,7 +2778,7 @@ async function runSeam(machine, options) {
|
|
|
2620
2778
|
result = await runAgent(machine, {
|
|
2621
2779
|
...snapshot ? { snapshot } : { input: options.input },
|
|
2622
2780
|
...event ? { event } : {},
|
|
2623
|
-
...options.
|
|
2781
|
+
...options.isIdle ? { isIdle: options.isIdle } : {},
|
|
2624
2782
|
...options.actors ? { actors: options.actors } : {},
|
|
2625
2783
|
events,
|
|
2626
2784
|
executors,
|
|
@@ -2673,6 +2831,196 @@ async function runSeam(machine, options) {
|
|
|
2673
2831
|
};
|
|
2674
2832
|
}
|
|
2675
2833
|
//#endregion
|
|
2834
|
+
//#region src/durable.ts
|
|
2835
|
+
/**
|
|
2836
|
+
* The durable host runner: {@link runDurableAgent} drives an executor-bound
|
|
2837
|
+
* agent machine on xstate's `createDurable` execution (`xstate/durable`),
|
|
2838
|
+
* with the agent event log as the journal.
|
|
2839
|
+
*
|
|
2840
|
+
* Where {@link replay} + `getAgentEffects` hand a host an effect list to run
|
|
2841
|
+
* itself, `runDurableAgent` owns the whole loop on the durable runtime:
|
|
2842
|
+
* invoked actors execute live through xstate's own runtime, every EXTERNAL
|
|
2843
|
+
* event (invoke completions included) is appended to the log, and a resume
|
|
2844
|
+
* folds the log back through pure transitions — an invoke whose completion is
|
|
2845
|
+
* already journaled is never re-started, so recorded model calls are never
|
|
2846
|
+
* re-executed. Crash recovery re-runs only the work that was still in flight.
|
|
2847
|
+
*
|
|
2848
|
+
* @module
|
|
2849
|
+
*/
|
|
2850
|
+
const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
|
|
2851
|
+
const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
|
|
2852
|
+
function completionActorId(event) {
|
|
2853
|
+
if (event.type !== DONE_ACTOR_EVENT_TYPE && event.type !== ERROR_ACTOR_EVENT_TYPE) return;
|
|
2854
|
+
const actorId = event.actorId;
|
|
2855
|
+
return typeof actorId === "string" ? actorId : void 0;
|
|
2856
|
+
}
|
|
2857
|
+
function createMailbox() {
|
|
2858
|
+
const queue = [];
|
|
2859
|
+
const waiters = [];
|
|
2860
|
+
return {
|
|
2861
|
+
push(event) {
|
|
2862
|
+
const waiter = waiters.shift();
|
|
2863
|
+
if (waiter) waiter(event);
|
|
2864
|
+
else queue.push(event);
|
|
2865
|
+
},
|
|
2866
|
+
take() {
|
|
2867
|
+
const next = queue.shift();
|
|
2868
|
+
if (next !== void 0) return Promise.resolve(next);
|
|
2869
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
2870
|
+
},
|
|
2871
|
+
size: () => queue.length
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Runs an agent machine as a durable execution: journal in, journal out.
|
|
2876
|
+
*
|
|
2877
|
+
* A fresh call starts from `input` and appends a reserved init entry; a
|
|
2878
|
+
* resume call folds `entries` through pure transitions first — invokes whose
|
|
2879
|
+
* completions are journaled are suppressed (their recorded results replay
|
|
2880
|
+
* instead of re-executing), while work that was in flight at the crash
|
|
2881
|
+
* re-executes live. After the journal, an optional `options.event` is
|
|
2882
|
+
* delivered. The call settles:
|
|
2883
|
+
*
|
|
2884
|
+
* - `done` when the machine reaches a final state, with `output`;
|
|
2885
|
+
* - `idle` when the frontier needs an external event the host has not
|
|
2886
|
+
* supplied (no live work pending, or `isIdle` says the pending work is a
|
|
2887
|
+
* human wait). Persist `entries` and call again with them later.
|
|
2888
|
+
*
|
|
2889
|
+
* ```ts
|
|
2890
|
+
* const first = await runDurableAgent(machine, { input, executors });
|
|
2891
|
+
* // ... persist first.entries; later, in a new process:
|
|
2892
|
+
* const next = await runDurableAgent(machine, {
|
|
2893
|
+
* entries: first.entries,
|
|
2894
|
+
* event: { type: "APPROVE" },
|
|
2895
|
+
* executors,
|
|
2896
|
+
* });
|
|
2897
|
+
* ```
|
|
2898
|
+
*
|
|
2899
|
+
* @experimental Built on xstate's experimental `xstate/durable` entrypoint.
|
|
2900
|
+
*/
|
|
2901
|
+
async function runDurableAgent(machine, options = {}) {
|
|
2902
|
+
const bound = options.executors ? provideExecutors(machine, options.executors, {
|
|
2903
|
+
actors: options.actors,
|
|
2904
|
+
onChunk: options.onChunk,
|
|
2905
|
+
onTrace: options.onTrace
|
|
2906
|
+
}) : options.actors ? machine.provide({ actors: options.actors }) : machine;
|
|
2907
|
+
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
2908
|
+
const machineVersion = options.machineVersion ?? require_decision.resolveMachineVersion(machine);
|
|
2909
|
+
const priorEntries = options.entries ?? [];
|
|
2910
|
+
if (priorEntries.length > 0) require_setup_agent.validateReplayEntries(priorEntries, {
|
|
2911
|
+
machineId,
|
|
2912
|
+
machineVersion
|
|
2913
|
+
}, "Durable journal entries");
|
|
2914
|
+
const hasInit = priorEntries[0]?.event.type === require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
2915
|
+
const input = hasInit ? priorEntries[0].event.input : options.input;
|
|
2916
|
+
const journal = priorEntries.slice(hasInit ? 1 : 0).map((entry) => entry.event);
|
|
2917
|
+
const journaledCompletions = /* @__PURE__ */ new Map();
|
|
2918
|
+
for (const event of journal) {
|
|
2919
|
+
const actorId = completionActorId(event);
|
|
2920
|
+
if (actorId !== void 0) journaledCompletions.set(actorId, (journaledCompletions.get(actorId) ?? 0) + 1);
|
|
2921
|
+
}
|
|
2922
|
+
const mailbox = createMailbox();
|
|
2923
|
+
const rootAddress = machineId;
|
|
2924
|
+
const suppressedChildren = /* @__PURE__ */ new WeakSet();
|
|
2925
|
+
const startsSeen = /* @__PURE__ */ new Map();
|
|
2926
|
+
const liveInFlight = /* @__PURE__ */ new Set();
|
|
2927
|
+
const findChildRef = (effect) => {
|
|
2928
|
+
const raw = effect;
|
|
2929
|
+
const candidates = [raw.actor, ...Array.isArray(raw.args) ? raw.args : []];
|
|
2930
|
+
for (const candidate of candidates) {
|
|
2931
|
+
const ref = candidate;
|
|
2932
|
+
if (ref && typeof ref.sessionId === "string" && typeof ref.id === "string") return ref;
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
let replaying = journal.length > 0;
|
|
2936
|
+
const execution = (0, xstate_durable.createDurable)(bound, {
|
|
2937
|
+
sendEvent(source, target, event) {
|
|
2938
|
+
if (target.address === rootAddress) {
|
|
2939
|
+
mailbox.push(event);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
(0, xstate.deliverEvent)(source, target, event);
|
|
2943
|
+
},
|
|
2944
|
+
runtime(_metadata, effect) {
|
|
2945
|
+
const type = effect.type;
|
|
2946
|
+
if (type === "@xstate.spawn" || type === "@xstate.start") {
|
|
2947
|
+
const child = findChildRef(effect);
|
|
2948
|
+
if (!child) return {};
|
|
2949
|
+
if (type === "@xstate.spawn") {
|
|
2950
|
+
const seen = (startsSeen.get(child.id) ?? 0) + 1;
|
|
2951
|
+
startsSeen.set(child.id, seen);
|
|
2952
|
+
if (seen <= (journaledCompletions.get(child.id) ?? 0)) suppressedChildren.add(child);
|
|
2953
|
+
else liveInFlight.add(child.id);
|
|
2954
|
+
}
|
|
2955
|
+
if (suppressedChildren.has(child)) return {
|
|
2956
|
+
spawnActor() {},
|
|
2957
|
+
startActor() {}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
return {};
|
|
2961
|
+
},
|
|
2962
|
+
executeAction(action) {
|
|
2963
|
+
if (replaying) return;
|
|
2964
|
+
action.exec?.();
|
|
2965
|
+
},
|
|
2966
|
+
waitForEvent() {
|
|
2967
|
+
return mailbox.take();
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
const entries = [...priorEntries];
|
|
2971
|
+
const entryOptions = {
|
|
2972
|
+
machineVersion,
|
|
2973
|
+
verification: options.verification ?? false
|
|
2974
|
+
};
|
|
2975
|
+
const appendEntry = (event) => {
|
|
2976
|
+
const entry = require_setup_agent.createReplayEntry(machine, entries, event, entryOptions);
|
|
2977
|
+
entries.push(entry);
|
|
2978
|
+
options.onEntry?.(entry);
|
|
2979
|
+
};
|
|
2980
|
+
if (!hasInit) {
|
|
2981
|
+
const entry = require_setup_agent.initEntry(machine, input, entryOptions);
|
|
2982
|
+
entries.push(entry);
|
|
2983
|
+
options.onEntry?.(entry);
|
|
2984
|
+
}
|
|
2985
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
2986
|
+
let journalIndex = 0;
|
|
2987
|
+
let liveEventConsumed = false;
|
|
2988
|
+
let [snapshot, effects] = execution.initialTransition(input);
|
|
2989
|
+
for (;;) {
|
|
2990
|
+
const captured = await execution.executeEffects(effects);
|
|
2991
|
+
for (const rootEvent of captured) mailbox.push(rootEvent.event);
|
|
2992
|
+
const machineSnapshot = snapshot;
|
|
2993
|
+
if (machineSnapshot.status === "done") return {
|
|
2994
|
+
status: "done",
|
|
2995
|
+
output: machineSnapshot.output,
|
|
2996
|
+
snapshot,
|
|
2997
|
+
entries
|
|
2998
|
+
};
|
|
2999
|
+
if (machineSnapshot.status === "error") throw machineSnapshot.error;
|
|
3000
|
+
let event;
|
|
3001
|
+
let fromJournal = false;
|
|
3002
|
+
if (journalIndex < journal.length) {
|
|
3003
|
+
event = require_setup_agent.rebindActorSession(journal[journalIndex], machineSnapshot, sessions);
|
|
3004
|
+
journalIndex++;
|
|
3005
|
+
fromJournal = true;
|
|
3006
|
+
replaying = journalIndex < journal.length;
|
|
3007
|
+
} else if (mailbox.size() > 0) event = await mailbox.take();
|
|
3008
|
+
else if (liveInFlight.size > 0 && !(options.isIdle?.(snapshot) ?? false)) event = await mailbox.take();
|
|
3009
|
+
else if (!liveEventConsumed && options.event !== void 0) {
|
|
3010
|
+
event = options.event;
|
|
3011
|
+
liveEventConsumed = true;
|
|
3012
|
+
} else return {
|
|
3013
|
+
status: "idle",
|
|
3014
|
+
snapshot,
|
|
3015
|
+
entries
|
|
3016
|
+
};
|
|
3017
|
+
const completedId = completionActorId(event);
|
|
3018
|
+
if (completedId !== void 0) liveInFlight.delete(completedId);
|
|
3019
|
+
if (!fromJournal) appendEntry(event);
|
|
3020
|
+
[snapshot, effects] = execution.transition(snapshot, event);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
//#endregion
|
|
2676
3024
|
exports.AGENT_EVENT_SCHEMA_VERSION = require_event_log_store.AGENT_EVENT_SCHEMA_VERSION;
|
|
2677
3025
|
exports.AGENT_INIT_EVENT_TYPE = require_setup_agent.AGENT_INIT_EVENT_TYPE;
|
|
2678
3026
|
exports.AGENT_TRACE_SCHEMA_VERSION = AGENT_TRACE_SCHEMA_VERSION;
|
|
@@ -2683,13 +3031,13 @@ exports.AgentEventLogConflictError = require_event_log_store.AgentEventLogConfli
|
|
|
2683
3031
|
exports.AgentIdleError = AgentIdleError;
|
|
2684
3032
|
exports.AgentIllegalResumeEventError = AgentIllegalResumeEventError;
|
|
2685
3033
|
exports.AgentLintError = AgentLintError;
|
|
3034
|
+
exports.AgentMaxModelCallsExceededError = AgentMaxModelCallsExceededError;
|
|
2686
3035
|
exports.AgentReplayDivergenceError = require_setup_agent.AgentReplayDivergenceError;
|
|
2687
3036
|
exports.AgentReplayMachineMismatchError = require_setup_agent.AgentReplayMachineMismatchError;
|
|
2688
3037
|
exports.AgentSnapshotVersionMismatchError = AgentSnapshotVersionMismatchError;
|
|
2689
3038
|
exports.NonSerializableAgentEventError = require_event_log_store.NonSerializableAgentEventError;
|
|
2690
3039
|
exports.appendMessages = require_setup_agent.appendMessages;
|
|
2691
3040
|
exports.assertAgentLogEntry = require_event_log_store.assertAgentLogEntry;
|
|
2692
|
-
exports.assertAgentMachine = assertAgentMachine;
|
|
2693
3041
|
exports.assertEventLogStoreConformance = require_event_log_store.assertEventLogStoreConformance;
|
|
2694
3042
|
exports.assertJsonSerializable = require_event_log_store.assertJsonSerializable;
|
|
2695
3043
|
exports.assistantMessage = require_decision.assistantMessage;
|
|
@@ -2716,6 +3064,8 @@ exports.getCallUsage = require_decision.getCallUsage;
|
|
|
2716
3064
|
exports.getJsonSchema = require_decision.getJsonSchema;
|
|
2717
3065
|
exports.getJsonSchemaSync = require_decision.getJsonSchemaSync;
|
|
2718
3066
|
exports.getMachineStructuralHash = require_decision.getMachineStructuralHash;
|
|
3067
|
+
exports.getSnapshotNodes = getSnapshotNodes;
|
|
3068
|
+
exports.getSnapshotRequests = getSnapshotRequests;
|
|
2719
3069
|
exports.getStateMeta = require_decision.getStateMeta;
|
|
2720
3070
|
exports.initEntry = require_setup_agent.initEntry;
|
|
2721
3071
|
exports.inspectTransitions = inspectTransitions;
|
|
@@ -2727,12 +3077,12 @@ exports.parseAgentEvent = require_decision.parseAgentEvent;
|
|
|
2727
3077
|
exports.parseModelRef = require_decision.parseModelRef;
|
|
2728
3078
|
exports.parseOutput = require_decision.parseOutput;
|
|
2729
3079
|
exports.parseStructuredEnvelope = require_decision.parseStructuredEnvelope;
|
|
2730
|
-
exports.persistSnapshot = require_decision.persistSnapshot;
|
|
2731
3080
|
exports.provideExecutors = provideExecutors;
|
|
2732
3081
|
exports.renderDecisionAttempts = require_decision.renderDecisionAttempts;
|
|
2733
3082
|
exports.replay = require_setup_agent.replay;
|
|
2734
3083
|
exports.resolveDecision = require_decision.resolveDecision;
|
|
2735
3084
|
exports.runAgent = runAgent;
|
|
3085
|
+
exports.runDurableAgent = runDurableAgent;
|
|
2736
3086
|
exports.runSeam = runSeam;
|
|
2737
3087
|
exports.serializeTraceEvent = serializeTraceEvent;
|
|
2738
3088
|
Object.defineProperty(exports, "setupAgent", {
|
|
@@ -2746,4 +3096,3 @@ exports.systemMessage = require_decision.systemMessage;
|
|
|
2746
3096
|
exports.toolMessage = require_decision.toolMessage;
|
|
2747
3097
|
exports.traceTransitions = traceTransitions;
|
|
2748
3098
|
exports.userMessage = require_decision.userMessage;
|
|
2749
|
-
exports.verifyReplay = require_setup_agent.verifyReplay;
|