@statelyai/agent 2.0.0-alpha.18 → 2.0.0-alpha.19
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-DsIkEuHz.mjs} +48 -28
- package/dist/{decision-DnQCQPew.cjs → decision-t26zsnSR.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 +265 -109
- package/dist/index.d.cts +136 -80
- package/dist/index.d.mts +136 -80
- package/dist/index.mjs +262 -107
- 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-C3ETi_HZ.cjs → setup-agent-BFA4VKpN.cjs} +31 -33
- package/dist/{setup-agent-BrE2zFDy.mjs → setup-agent-CPFPN06s.mjs} +31 -28
- 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 +15 -1
- package/schemas/agent-workflow.json +2 -2
- package/skills/generate-machine/SKILL.md +12 -14
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as AgentError } from "./errors-CeSXQx0v.mjs";
|
|
2
|
-
import { _ as
|
|
3
|
-
import { A as isUnboundPlaceholder, B as getMachineStructuralHash, C as parseStructuredEnvelope, D as
|
|
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-
|
|
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-CPFPN06s.mjs";
|
|
3
|
+
import { A as isUnboundPlaceholder, B as getMachineStructuralHash, C as parseStructuredEnvelope, D as getMachineIdlePredicate, E as executorBoundLogics, G as toolMessage, H as isStandardSchema, I as findNonSerializableContextPaths, K as userMessage, L as getAgentMessages, O as getMachineStaticTransitionTargets, P as assistantMessage, R as getJsonSchema, S as parseOutput, U as resolveMachineVersion, V as getStateMeta, W as systemMessage, _ as getAgentOutputMode, a as resolveDecision, b as normalizeGeneratorResult, c as AGENT_USAGE_TOKEN_FIELDS, f as bindRequestExecutor, h as createTextLogic, i as renderDecisionAttempts, k as getRegisteredAgentExecutionOptions, o as getAcceptedEvents, p as buildEnvelopeSchema, q as validateSchemaSync, r as isDecisionLogic, s as parseAgentEvent, t as AgentDecisionExhaustedError, u as INTERPRET_SOURCE, v as getCallUsage, x as parseModelRef, y as isTextLogic, z as getJsonSchemaSync } from "./decision-DsIkEuHz.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
5
|
import { createActor, createAsyncLogic, getNextTransitions, isMachineSnapshot } from "xstate";
|
|
6
6
|
//#region src/internal/state-request-pass.ts
|
|
7
7
|
async function runTextPhase(stateRequest, baseMessages, deps) {
|
|
@@ -102,7 +102,7 @@ async function runAdvancePhase(plan, deps) {
|
|
|
102
102
|
messages: [...deps.messages],
|
|
103
103
|
events,
|
|
104
104
|
attempts: []
|
|
105
|
-
}, deps.decide, {
|
|
105
|
+
}, { decide: deps.decide }, {
|
|
106
106
|
signal: deps.signal,
|
|
107
107
|
canTake: (event) => deps.getSnapshot().can(event)
|
|
108
108
|
});
|
|
@@ -138,8 +138,7 @@ async function runStateRequestPass(requests, deps) {
|
|
|
138
138
|
* {@link getAcceptedEvents}). A programmer/integration error, in the same
|
|
139
139
|
* class as runAgent's bind-time throws — it throws rather than settling an
|
|
140
140
|
* `error` result. A type-legal event a guard rejects is NOT this error (the
|
|
141
|
-
* machine simply takes no transition).
|
|
142
|
-
* {@link RunAgentOptions.onIllegalResumeEvent} `'ignore'`.
|
|
141
|
+
* machine simply takes no transition). Always enforced; there is no opt-out.
|
|
143
142
|
*/
|
|
144
143
|
var AgentIllegalResumeEventError = class extends AgentError {
|
|
145
144
|
eventType;
|
|
@@ -257,7 +256,7 @@ function toJsonValue(value, ancestors) {
|
|
|
257
256
|
* in a JSONL file). Live values are sanitized rather than trusted:
|
|
258
257
|
*
|
|
259
258
|
* - Snapshots (`run.start`, `machine.transition`, `run.end`) go through the
|
|
260
|
-
* same JSON round-trip as
|
|
259
|
+
* same JSON round-trip as `machine.getPersistedSnapshot(...)`, so what lands on disk is
|
|
261
260
|
* what a resume would see.
|
|
262
261
|
* - `request.end`'s `raw` (a provider SDK object, frequently cyclic) is DROPPED
|
|
263
262
|
* unless `includeRaw` is set, in which case it is sanitized like everything
|
|
@@ -284,11 +283,92 @@ function serializeTraceEvent(event, options = {}) {
|
|
|
284
283
|
}
|
|
285
284
|
return out;
|
|
286
285
|
}
|
|
286
|
+
function snapshotNodes(snapshot) {
|
|
287
|
+
return (snapshot._nodes ?? []).map((raw) => {
|
|
288
|
+
const node = raw;
|
|
289
|
+
return {
|
|
290
|
+
id: node.id ?? "",
|
|
291
|
+
key: node.key ?? "",
|
|
292
|
+
...node.description !== void 0 ? { description: node.description } : {},
|
|
293
|
+
tags: [...node.tags ?? []],
|
|
294
|
+
...node.meta !== void 0 ? { meta: node.meta } : {},
|
|
295
|
+
ownEvents: [...node.ownEvents ?? []],
|
|
296
|
+
leaf: Object.keys(node.states ?? {}).length === 0
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The active state nodes of a snapshot, as plain {@link AgentSnapshotNode}
|
|
302
|
+
* descriptors. The escape hatch under {@link getSnapshotRequests}: use it when
|
|
303
|
+
* your `getRequests` hook needs to build requests some other way, so host code
|
|
304
|
+
* never touches xstate's private `snapshot._nodes`.
|
|
305
|
+
*/
|
|
306
|
+
function getSnapshotNodes(snapshot) {
|
|
307
|
+
return snapshotNodes(snapshot);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Builds the {@link AgentStateRequest}s for a snapshot straight from its active
|
|
311
|
+
* state nodes — the prompts-in-descriptions recipe as a function, so a
|
|
312
|
+
* {@link RunAgentOptions.getRequests} hook is one line and never reaches into
|
|
313
|
+
* xstate's private `snapshot._nodes`:
|
|
314
|
+
*
|
|
315
|
+
* ```ts
|
|
316
|
+
* runAgent(machine, {
|
|
317
|
+
* executors,
|
|
318
|
+
* getRequests: (snapshot) => getSnapshotRequests(snapshot, { model: 'writer' }),
|
|
319
|
+
* });
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* Each described active node becomes one request: `prompt` from the node's
|
|
323
|
+
* `description`, `kind: 'decision'` when it is tagged `'decision'`, `system`
|
|
324
|
+
* from `meta.role` when present, `allowedEvents` scoped to the node's own
|
|
325
|
+
* events, and an explicit `onDone` when the node has exactly one own event
|
|
326
|
+
* (single-outcome states advance deterministically; anything else falls
|
|
327
|
+
* through to a `decide` call). Nodes tagged `'waiting'` produce nothing, so the
|
|
328
|
+
* run settles idle for a human. Override any of it with `filter`/`map`.
|
|
329
|
+
*/
|
|
330
|
+
function getSnapshotRequests(snapshot, options) {
|
|
331
|
+
const filter = options.filter ?? ((node) => !!node.description && !node.tags.includes("waiting"));
|
|
332
|
+
const requests = [];
|
|
333
|
+
for (const node of snapshotNodes(snapshot)) {
|
|
334
|
+
if (!filter(node)) continue;
|
|
335
|
+
const system = node.meta?.role;
|
|
336
|
+
const request = {
|
|
337
|
+
model: options.model,
|
|
338
|
+
prompt: node.description ?? "",
|
|
339
|
+
kind: node.tags.includes("decision") ? "decision" : "text",
|
|
340
|
+
...system !== void 0 ? { system } : {},
|
|
341
|
+
...node.ownEvents.length > 0 ? { allowedEvents: node.ownEvents } : {},
|
|
342
|
+
...node.ownEvents.length === 1 ? { onDone: { type: node.ownEvents[0] } } : {}
|
|
343
|
+
};
|
|
344
|
+
const mapped = options.map ? options.map(request, node) : request;
|
|
345
|
+
if (mapped) requests.push(mapped);
|
|
346
|
+
}
|
|
347
|
+
return requests;
|
|
348
|
+
}
|
|
287
349
|
let nextRunAgentTraceId = 1;
|
|
350
|
+
/**
|
|
351
|
+
* Thrown into the invoke that would have made the call once
|
|
352
|
+
* {@link RunAgentOptions.maxModelCalls} is spent. It reaches the machine
|
|
353
|
+
* through the normal error channel, so an invoke's `onError` can branch on it
|
|
354
|
+
* (`error.code === 'max-model-calls'`, the same string the settled result's
|
|
355
|
+
* `cause` uses) and route to a degraded/finish state instead of failing the
|
|
356
|
+
* run. Unhandled, it settles `{ status: 'error', cause: 'max-model-calls' }`.
|
|
357
|
+
*
|
|
358
|
+
* ```ts
|
|
359
|
+
* onError: [
|
|
360
|
+
* { guard: ({ event }) => event.error?.code === 'max-model-calls', target: 'budgetSpent' },
|
|
361
|
+
* { target: 'failed' },
|
|
362
|
+
* ]
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
288
365
|
var AgentMaxModelCallsExceededError = class extends AgentError {
|
|
289
|
-
|
|
290
|
-
|
|
366
|
+
/** The budget that was exceeded (`options.maxModelCalls`). */
|
|
367
|
+
maxModelCalls;
|
|
368
|
+
constructor(maxModelCalls) {
|
|
369
|
+
super("max-model-calls", `runAgent exceeded maxModelCalls (${maxModelCalls}). Raise the budget, or handle it in the invoke's onError (error.code === 'max-model-calls').`);
|
|
291
370
|
this.name = "AgentMaxModelCallsExceededError";
|
|
371
|
+
this.maxModelCalls = maxModelCalls;
|
|
292
372
|
}
|
|
293
373
|
};
|
|
294
374
|
function wrapsDecisionExhausted(error) {
|
|
@@ -328,7 +408,7 @@ function collectConfiguredInvokeSrcs(stateConfig, stateName, out) {
|
|
|
328
408
|
* walk into invoked child machines (their internal agent requests are opaque
|
|
329
409
|
* to the parent-level source walk otherwise).
|
|
330
410
|
*/
|
|
331
|
-
function
|
|
411
|
+
function isStateMachineLogic(logic) {
|
|
332
412
|
return !!logic && typeof logic === "object" && "config" in logic && "root" in logic && typeof logic.provide === "function" && typeof logic.sources === "object" && !!logic.sources?.actors;
|
|
333
413
|
}
|
|
334
414
|
/**
|
|
@@ -360,7 +440,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
360
440
|
const where = ctx.isChild ? `child machine '${ctx.childPath}' state` : "state";
|
|
361
441
|
for (const { stateName, src } of invokes) {
|
|
362
442
|
if (typeof src !== "string") {
|
|
363
|
-
if (
|
|
443
|
+
if (isStateMachineLogic(src)) {
|
|
364
444
|
assertChildMachineBindable(src, src, stateName, executors, ctx);
|
|
365
445
|
continue;
|
|
366
446
|
}
|
|
@@ -369,7 +449,7 @@ function assertMachineBindable(machine, effectiveSources, executors, ctx) {
|
|
|
369
449
|
}
|
|
370
450
|
const logic = effectiveSources[src];
|
|
371
451
|
if (logic === void 0) throw new Error(`runAgent: ${where} '${stateName}' invokes unregistered actor source '${src}'. Provide it via machine.provide({ actors: { '${src}': ... } }) or runAgent(machine, { actors: { '${src}': ... } }).`);
|
|
372
|
-
if (
|
|
452
|
+
if (isStateMachineLogic(logic)) {
|
|
373
453
|
assertChildMachineBindable(logic, src, stateName, executors, ctx);
|
|
374
454
|
continue;
|
|
375
455
|
}
|
|
@@ -415,18 +495,16 @@ function unrebindableChildRequestError(childPath, stateName, requestSrc, kind) {
|
|
|
415
495
|
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.`);
|
|
416
496
|
}
|
|
417
497
|
/**
|
|
418
|
-
* True when the snapshot's active states declare a transition
|
|
419
|
-
* `'@agent.usage'` type
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* a wildcard machine's context and event log byte-identical to a run without
|
|
426
|
-
* the feature. @internal
|
|
498
|
+
* True when the snapshot's active states declare a transition that would
|
|
499
|
+
* receive the reserved `'@agent.usage'` type — an explicit `on: { '@agent.usage'
|
|
500
|
+
* }` OR a catch-all `on: { '*': … }`. Plain XState semantics apply unmodified:
|
|
501
|
+
* a wildcard matches every event delivered to the machine, reserved ones
|
|
502
|
+
* included. (The MODEL-facing side stays closed: `getAcceptedEvents` drops
|
|
503
|
+
* `@agent.*` before any `allowedEvents` matching, so a wildcard never offers
|
|
504
|
+
* the reserved event as a decision candidate.) @internal
|
|
427
505
|
*/
|
|
428
506
|
function declaresUsageTransition(snapshot) {
|
|
429
|
-
return getNextTransitions(snapshot).some((transition) => transition.eventType ===
|
|
507
|
+
return getNextTransitions(snapshot).some((transition) => transition.eventType === "@agent.usage" || transition.eventType === "*");
|
|
430
508
|
}
|
|
431
509
|
/**
|
|
432
510
|
* Builds the ONE place a trace payload is emitted: it hands the payload to the
|
|
@@ -493,7 +571,7 @@ function bindTextLogic(logic, runCtx) {
|
|
|
493
571
|
const self = selfArg;
|
|
494
572
|
const { id, src } = selfIdAndSrc(self);
|
|
495
573
|
const executor = logic.mode === "stream" ? runCtx.streamText : runCtx.generateText;
|
|
496
|
-
if (!executor) throw new Error(`
|
|
574
|
+
if (!executor) throw new Error(`No '${logic.mode === "stream" ? "streamText" : "generateText"}' executor provided.`);
|
|
497
575
|
const requestWithTools = {
|
|
498
576
|
...request,
|
|
499
577
|
tools: request.tools ?? {}
|
|
@@ -565,17 +643,22 @@ function bindTextLogic(logic, runCtx) {
|
|
|
565
643
|
});
|
|
566
644
|
}
|
|
567
645
|
function createCountingDecide(runCtx, self) {
|
|
568
|
-
return async (attemptRequest) => {
|
|
646
|
+
return async (attemptRequest, info) => {
|
|
569
647
|
runCtx.consumeModelCall();
|
|
570
648
|
runCtx.emitTrace?.({
|
|
571
649
|
type: "request.start",
|
|
572
650
|
request: attemptRequest
|
|
573
651
|
}, self);
|
|
574
652
|
try {
|
|
653
|
+
const { id } = selfIdAndSrc(self);
|
|
575
654
|
const result = await runCtx.decide(runCtx.runId !== void 0 ? {
|
|
576
655
|
...attemptRequest,
|
|
577
656
|
runId: runCtx.runId
|
|
578
|
-
} : attemptRequest
|
|
657
|
+
} : attemptRequest, {
|
|
658
|
+
...info,
|
|
659
|
+
...runCtx.runId !== void 0 ? { runId: runCtx.runId } : {},
|
|
660
|
+
...info?.requestId === void 0 && id !== "" ? { requestId: id } : {}
|
|
661
|
+
});
|
|
579
662
|
const usage = getCallUsage(result);
|
|
580
663
|
if (usage) {
|
|
581
664
|
const { src } = selfIdAndSrc(self);
|
|
@@ -621,7 +704,7 @@ function createCountingDecide(runCtx, self) {
|
|
|
621
704
|
*/
|
|
622
705
|
function bindDecisionLogic(logic, runCtx) {
|
|
623
706
|
const decisionLogic = createAsyncLogic({ run: async ({ input, signal, self: selfArg }) => {
|
|
624
|
-
if (!runCtx.decide) throw new Error("
|
|
707
|
+
if (!runCtx.decide) throw new Error("No 'decide' executor provided.");
|
|
625
708
|
const self = selfArg;
|
|
626
709
|
const { id } = selfIdAndSrc(self);
|
|
627
710
|
const declaredEventTypes = logic.allowedEventTypes?.(input);
|
|
@@ -635,7 +718,7 @@ function bindDecisionLogic(logic, runCtx) {
|
|
|
635
718
|
...logic.request(input),
|
|
636
719
|
id,
|
|
637
720
|
events
|
|
638
|
-
}, createCountingDecide(runCtx, self), {
|
|
721
|
+
}, { decide: createCountingDecide(runCtx, self) }, {
|
|
639
722
|
maxRetries: logic.maxRetries,
|
|
640
723
|
signal,
|
|
641
724
|
canTake: (event) => actorRef ? actorRef.getSnapshot().can(event) : true
|
|
@@ -680,7 +763,7 @@ function rootTraceState(root) {
|
|
|
680
763
|
if (!state) {
|
|
681
764
|
const logic = root.logic;
|
|
682
765
|
const machineId = (logic?.config)?.id ?? logic?.id ?? "(machine)";
|
|
683
|
-
const machineVersion = logic ?
|
|
766
|
+
const machineVersion = logic ? resolveMachineVersion(logic) : "";
|
|
684
767
|
state = {
|
|
685
768
|
runId: `run_${nextProvideRunId++}`,
|
|
686
769
|
seq: 0,
|
|
@@ -752,9 +835,9 @@ function provideBindContext(machine, executors, options) {
|
|
|
752
835
|
* under a live `createActor` tree) on the `provideExecutors` path.
|
|
753
836
|
*
|
|
754
837
|
* Gating is identical on both: the target snapshot must be active, must declare
|
|
755
|
-
* an `'@agent.usage'` transition
|
|
756
|
-
*
|
|
757
|
-
* event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
838
|
+
* an `'@agent.usage'` transition — explicitly, or through a catch-all
|
|
839
|
+
* `on: { '*' }` (see {@link declaresUsageTransition}) — and must be able to
|
|
840
|
+
* take the event. `onDropped` is the run path's straggler gate: it returns `true` for a
|
|
758
841
|
* call that settled after the cycle resolved, which drops the event (traced as
|
|
759
842
|
* `usage.dropped`) rather than delivering it. Uncontrolled mode has no cycle to
|
|
760
843
|
* settle, so it passes no gate and has no dropped stragglers.
|
|
@@ -796,6 +879,19 @@ function bindDecisionForProvide(machine, logic, executors, options) {
|
|
|
796
879
|
return bindDecisionLogic(logic, provideBindContext(machine, executors, options));
|
|
797
880
|
}
|
|
798
881
|
/**
|
|
882
|
+
* Recursively binds an invoked child state machine for {@link provideExecutors},
|
|
883
|
+
* with the same semantics `runAgent` applies ({@link rebindChildMachine}):
|
|
884
|
+
* string-keyed text/decision sources at any depth inherit the host executors,
|
|
885
|
+
* a source that carries its own executor is left alone, and a cycle is
|
|
886
|
+
* returned as-is. Each machine in the tree is bound with its own registered
|
|
887
|
+
* `setupAgent` schemas. Returns the original machine when nothing needed
|
|
888
|
+
* wrapping. @internal
|
|
889
|
+
*/
|
|
890
|
+
function bindChildMachineForProvide(childMachine, executors, options, visited) {
|
|
891
|
+
const ctxFor = (target) => provideBindContext(target, executors, options);
|
|
892
|
+
return rebindChildMachine(childMachine, ctxFor(childMachine), visited, ctxFor);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
799
895
|
* Validates `input` against the machine's registered input schema, returning
|
|
800
896
|
* the schema's output — so defaults are filled and transforms applied before
|
|
801
897
|
* the value reaches `createActor` or the replayable event log.
|
|
@@ -829,9 +925,10 @@ function resolveMachineInput(machine, input) {
|
|
|
829
925
|
* (`executorBoundLogics`) is left as-is: explicit binding shadows inheritance.
|
|
830
926
|
* Cycle-safe via `visited` (a machine that invokes itself is returned as-is).
|
|
831
927
|
*/
|
|
832
|
-
function rebindChildMachine(childMachine, runCtx, visited) {
|
|
928
|
+
function rebindChildMachine(childMachine, runCtx, visited, ctxFor) {
|
|
833
929
|
if (visited.has(childMachine)) return childMachine;
|
|
834
930
|
const childVisited = new Set([...visited, childMachine]);
|
|
931
|
+
runCtx = ctxFor ? ctxFor(childMachine) : runCtx;
|
|
835
932
|
const sources = childMachine.sources.actors;
|
|
836
933
|
const wrapped = {};
|
|
837
934
|
for (const [key, logic] of Object.entries(sources)) {
|
|
@@ -843,8 +940,8 @@ function rebindChildMachine(childMachine, runCtx, visited) {
|
|
|
843
940
|
if (!executorBoundLogics.has(logic)) wrapped[key] = bindTextLogic(logic, runCtx);
|
|
844
941
|
continue;
|
|
845
942
|
}
|
|
846
|
-
if (
|
|
847
|
-
const rebound = rebindChildMachine(logic, runCtx, childVisited);
|
|
943
|
+
if (isStateMachineLogic(logic)) {
|
|
944
|
+
const rebound = rebindChildMachine(logic, runCtx, childVisited, ctxFor);
|
|
848
945
|
if (rebound !== logic) wrapped[key] = rebound;
|
|
849
946
|
continue;
|
|
850
947
|
}
|
|
@@ -927,7 +1024,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
927
1024
|
let traceSeq = 0;
|
|
928
1025
|
const resolvedInput = resolveMachineInput(machine, options.input);
|
|
929
1026
|
const machineId = machine.config.id ?? machine.id ?? "(machine)";
|
|
930
|
-
const machineVersion =
|
|
1027
|
+
const machineVersion = resolveMachineVersion(machine);
|
|
931
1028
|
const agentMeta = {
|
|
932
1029
|
machineId,
|
|
933
1030
|
version: machineVersion
|
|
@@ -949,10 +1046,10 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
949
1046
|
onTransition: options.onTransition
|
|
950
1047
|
});
|
|
951
1048
|
const consumeModelCall = () => {
|
|
952
|
-
if (budgetExceeded) throw new AgentMaxModelCallsExceededError();
|
|
1049
|
+
if (budgetExceeded) throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
953
1050
|
if (modelCallCount + 1 > maxModelCalls) {
|
|
954
1051
|
budgetExceeded = true;
|
|
955
|
-
throw new AgentMaxModelCallsExceededError();
|
|
1052
|
+
throw new AgentMaxModelCallsExceededError(maxModelCalls);
|
|
956
1053
|
}
|
|
957
1054
|
modelCallCount += 1;
|
|
958
1055
|
};
|
|
@@ -1026,19 +1123,20 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1026
1123
|
if (!executorBoundLogics.has(logic)) wrappedSources[key] = bindTextLogic(logic, runCtx);
|
|
1027
1124
|
continue;
|
|
1028
1125
|
}
|
|
1029
|
-
if (
|
|
1126
|
+
if (isStateMachineLogic(logic)) {
|
|
1030
1127
|
const rebound = rebindChildMachine(logic, runCtx, new Set([machine]));
|
|
1031
1128
|
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1032
1129
|
continue;
|
|
1033
1130
|
}
|
|
1034
1131
|
}
|
|
1035
1132
|
const boundMachine = provided.provide({ actors: wrappedSources });
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1133
|
+
const declaredIdlePredicate = options.isIdle ?? getMachineIdlePredicate(machine);
|
|
1134
|
+
const isIdle = declaredIdlePredicate ?? (() => false);
|
|
1135
|
+
const machineDeclaresMigrate = typeof machine.config.migrate === "function";
|
|
1038
1136
|
let effectiveSnapshot = options.snapshot;
|
|
1039
1137
|
if (effectiveSnapshot !== void 0) {
|
|
1040
1138
|
const from = effectiveSnapshot.agentMeta?.version ?? effectiveSnapshot.version;
|
|
1041
|
-
if (from !== void 0 && from !== machineVersion) {
|
|
1139
|
+
if (from !== void 0 && from !== machineVersion && !(machineDeclaresMigrate && !options.migrateSnapshot)) {
|
|
1042
1140
|
const info = {
|
|
1043
1141
|
from,
|
|
1044
1142
|
to: machineVersion
|
|
@@ -1057,7 +1155,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1057
1155
|
effectiveSnapshot = machine.getPersistedSnapshot(replayedSnapshot);
|
|
1058
1156
|
}
|
|
1059
1157
|
const machineOwnVersion = machine.version;
|
|
1060
|
-
if (effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1158
|
+
if (!machineDeclaresMigrate && effectiveSnapshot !== void 0 && effectiveSnapshot.version !== machineOwnVersion) {
|
|
1061
1159
|
const aligned = Object.assign(Object.create(Object.getPrototypeOf(effectiveSnapshot)), effectiveSnapshot);
|
|
1062
1160
|
if (machineOwnVersion === void 0) delete aligned.version;
|
|
1063
1161
|
else aligned.version = machineOwnVersion;
|
|
@@ -1071,7 +1169,7 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1071
1169
|
if (!options.getRequests && !options.messages && messages.length === 0) return;
|
|
1072
1170
|
snapshot.messages = [...messages];
|
|
1073
1171
|
};
|
|
1074
|
-
if (effectiveSnapshot !== void 0 && options.event !== void 0
|
|
1172
|
+
if (effectiveSnapshot !== void 0 && options.event !== void 0) {
|
|
1075
1173
|
const acceptedTypes = getAcceptedEvents(createActor(boundMachine, { snapshot: effectiveSnapshot }).getSnapshot(), { schemas: runCtx.schemas }).map((descriptor) => descriptor.type);
|
|
1076
1174
|
const eventType = options.event.type;
|
|
1077
1175
|
if (!acceptedTypes.includes(eventType)) throw new AgentIllegalResumeEventError(eventType, acceptedTypes);
|
|
@@ -1217,9 +1315,9 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1217
1315
|
const current = actor.getSnapshot();
|
|
1218
1316
|
if (isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1219
1317
|
if (!maybeInterpret(current)) {
|
|
1220
|
-
if (!
|
|
1318
|
+
if (!declaredIdlePredicate && current.status === "active" && !warnedHeuristicIdle && process.env.NODE_ENV !== "production") {
|
|
1221
1319
|
warnedHeuristicIdle = true;
|
|
1222
|
-
console.warn("[@statelyai/agent] runAgent settled idle via the timing heuristic (no
|
|
1320
|
+
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').");
|
|
1223
1321
|
}
|
|
1224
1322
|
settleIdle(current);
|
|
1225
1323
|
}
|
|
@@ -1274,11 +1372,11 @@ function createAgentSession(machine, options, lifecycle) {
|
|
|
1274
1372
|
});
|
|
1275
1373
|
return;
|
|
1276
1374
|
}
|
|
1277
|
-
if (!deliveringResumeEvent &&
|
|
1375
|
+
if (!deliveringResumeEvent && isIdle(snapshot) && isIdleSnapshot(snapshot, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1278
1376
|
queueMicrotask(() => {
|
|
1279
1377
|
if (settled) return;
|
|
1280
1378
|
const current = actor.getSnapshot();
|
|
1281
|
-
if (
|
|
1379
|
+
if (isIdle(current) && isIdleSnapshot(current, { ignoreUserInputChildren: userInputIsPlaceholder })) {
|
|
1282
1380
|
if (!maybeInterpret(current)) settleIdle(current);
|
|
1283
1381
|
} else scheduleIdleCheck();
|
|
1284
1382
|
});
|
|
@@ -1575,9 +1673,12 @@ function createAgentRun(machine, options) {
|
|
|
1575
1673
|
* Throws at bind time if a source needs an executor kind that `executors` does
|
|
1576
1674
|
* not provide.
|
|
1577
1675
|
*
|
|
1578
|
-
*
|
|
1579
|
-
* machine
|
|
1580
|
-
*
|
|
1676
|
+
* Executor inheritance is RECURSIVE, exactly as in `runAgent`: a string-keyed
|
|
1677
|
+
* invoked child machine is rebound too, so its own text/decision requests — at
|
|
1678
|
+
* any depth — reach the same host executors. A direct-object invoke `src`
|
|
1679
|
+
* cannot be swapped via `.provide`, so nothing under one inherits; bind those
|
|
1680
|
+
* with `.withExecutor(...)` or register the child as a string-keyed source. A
|
|
1681
|
+
* source that already carries its own executor is never overwritten.
|
|
1581
1682
|
*/
|
|
1582
1683
|
function provideExecutors(machine, executors, options = {}) {
|
|
1583
1684
|
const bindOptions = {
|
|
@@ -1591,21 +1692,18 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1591
1692
|
const invokedSrcs = getConfiguredInvokeSrcs(provided);
|
|
1592
1693
|
for (const [key, logic] of Object.entries(effectiveSources)) {
|
|
1593
1694
|
if (key === "agent.userInput") continue;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
};
|
|
1600
|
-
else if (isTextLogic(logic)) {
|
|
1601
|
-
const streaming = logic.mode === "stream";
|
|
1602
|
-
binding = {
|
|
1603
|
-
executorKey: streaming ? "streamText" : "generateText",
|
|
1604
|
-
kind: streaming ? "streaming text" : "text",
|
|
1605
|
-
bind: () => bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1606
|
-
};
|
|
1695
|
+
if (isStateMachineLogic(logic)) {
|
|
1696
|
+
if (invokedSrcs.has(key)) assertChildBindable(logic, executors, key, new Set([provided]));
|
|
1697
|
+
const rebound = bindChildMachineForProvide(logic, executors, bindOptions, new Set([provided]));
|
|
1698
|
+
if (rebound !== logic) wrappedSources[key] = rebound;
|
|
1699
|
+
continue;
|
|
1607
1700
|
}
|
|
1608
|
-
|
|
1701
|
+
const requirement = executorRequirementOf(logic);
|
|
1702
|
+
if (!requirement) continue;
|
|
1703
|
+
const binding = {
|
|
1704
|
+
...requirement,
|
|
1705
|
+
bind: () => isDecisionLogic(logic) ? bindDecisionForProvide(provided, logic, executors, bindOptions) : bindTextForProvide(provided, logic, executors, bindOptions)
|
|
1706
|
+
};
|
|
1609
1707
|
if (executorBoundLogics.has(logic)) continue;
|
|
1610
1708
|
if (!executors[binding.executorKey]) {
|
|
1611
1709
|
if (invokedSrcs.has(key)) throw missingExecutorError(key, binding.kind, binding.executorKey);
|
|
@@ -1615,12 +1713,50 @@ function provideExecutors(machine, executors, options = {}) {
|
|
|
1615
1713
|
}
|
|
1616
1714
|
return withActors(provided, wrappedSources);
|
|
1617
1715
|
}
|
|
1716
|
+
/** The executor slot + label an agent logic needs, or `undefined` for a non-agent actor. */
|
|
1717
|
+
function executorRequirementOf(logic) {
|
|
1718
|
+
if (isDecisionLogic(logic)) return {
|
|
1719
|
+
executorKey: "decide",
|
|
1720
|
+
kind: "decision"
|
|
1721
|
+
};
|
|
1722
|
+
if (isTextLogic(logic)) return logic.mode === "stream" ? {
|
|
1723
|
+
executorKey: "streamText",
|
|
1724
|
+
kind: "streaming text"
|
|
1725
|
+
} : {
|
|
1726
|
+
executorKey: "generateText",
|
|
1727
|
+
kind: "text"
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Walks an invoked child machine's own invoked sources (recursively, at any
|
|
1732
|
+
* depth) and throws the same missing-executor error `provideExecutors` throws
|
|
1733
|
+
* for the top-level machine — before any actor starts. Mirrors runAgent's
|
|
1734
|
+
* `assertBindable` for the uncontrolled path. Cycle-safe via `visited`.
|
|
1735
|
+
*/
|
|
1736
|
+
function assertChildBindable(childMachine, executors, path, visited) {
|
|
1737
|
+
if (visited.has(childMachine)) return;
|
|
1738
|
+
const nextVisited = new Set([...visited, childMachine]);
|
|
1739
|
+
const sources = childMachine.sources.actors;
|
|
1740
|
+
for (const src of getConfiguredInvokeSrcs(childMachine)) {
|
|
1741
|
+
if (src === "agent.userInput") continue;
|
|
1742
|
+
const logic = sources[src];
|
|
1743
|
+
if (!logic) continue;
|
|
1744
|
+
if (isStateMachineLogic(logic)) {
|
|
1745
|
+
assertChildBindable(logic, executors, `${path} > ${src}`, nextVisited);
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
const requirement = executorRequirementOf(logic);
|
|
1749
|
+
if (!requirement || executorBoundLogics.has(logic)) continue;
|
|
1750
|
+
if (!executors[requirement.executorKey]) throw missingExecutorError(`${path} > ${src}`, requirement.kind, requirement.executorKey);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1618
1753
|
function missingExecutorError(src, kind, executor) {
|
|
1619
1754
|
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.`);
|
|
1620
1755
|
}
|
|
1621
1756
|
//#endregion
|
|
1622
1757
|
//#region src/verify.ts
|
|
1623
1758
|
const DECIDE_SRC = "agent.decide";
|
|
1759
|
+
const USER_INPUT_SRC = "agent.userInput";
|
|
1624
1760
|
function normalizeInvokes(invoke) {
|
|
1625
1761
|
if (invoke === void 0 || invoke === null) return [];
|
|
1626
1762
|
return Array.isArray(invoke) ? invoke : [invoke];
|
|
@@ -1907,11 +2043,23 @@ const LINT_CHECKS = [
|
|
|
1907
2043
|
* call this to catch dead states, undeliverable decisions, un-rebindable
|
|
1908
2044
|
* invoke srcs, and output-contract gaps before ever running it.
|
|
1909
2045
|
*
|
|
2046
|
+
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
2047
|
+
* loops: it returns silently when the machine is clean and throws
|
|
2048
|
+
* {@link AgentLintError} (findings on `.diagnostics`) on error-severity
|
|
2049
|
+
* findings, or on warnings too with `{ throw: true, warnings: true }`.
|
|
2050
|
+
*
|
|
1910
2051
|
* @example
|
|
1911
2052
|
* ```ts
|
|
1912
2053
|
* const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
|
|
1913
2054
|
* if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
1914
2055
|
* ```
|
|
2056
|
+
*
|
|
2057
|
+
* @example Throwing form
|
|
2058
|
+
* ```ts
|
|
2059
|
+
* test('agent machine is structurally sound', () => {
|
|
2060
|
+
* lintAgentMachine(machine, { throw: true });
|
|
2061
|
+
* });
|
|
2062
|
+
* ```
|
|
1915
2063
|
*/
|
|
1916
2064
|
function lintAgentMachine(machine, options = {}) {
|
|
1917
2065
|
const config = machine.config ?? {};
|
|
@@ -1926,10 +2074,16 @@ function lintAgentMachine(machine, options = {}) {
|
|
|
1926
2074
|
actors: registered.actors ?? machine.sources?.actors ?? {}
|
|
1927
2075
|
};
|
|
1928
2076
|
const disabled = new Set(options.disable ?? []);
|
|
1929
|
-
|
|
2077
|
+
const diagnostics = LINT_CHECKS.flatMap((check) => check(ctx)).filter((d) => !disabled.has(d.code));
|
|
2078
|
+
if (options.throw) {
|
|
2079
|
+
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
2080
|
+
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
2081
|
+
}
|
|
2082
|
+
return diagnostics;
|
|
1930
2083
|
}
|
|
1931
2084
|
/**
|
|
1932
|
-
* Thrown by {
|
|
2085
|
+
* Thrown by `lintAgentMachine(machine, { throw: true })` when lint finds
|
|
2086
|
+
* failing diagnostics.
|
|
1933
2087
|
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
1934
2088
|
* so a test runner's failure output reads like the CLI's lint report.
|
|
1935
2089
|
*/
|
|
@@ -1942,24 +2096,6 @@ var AgentLintError = class extends AgentError {
|
|
|
1942
2096
|
this.diagnostics = diagnostics;
|
|
1943
2097
|
}
|
|
1944
2098
|
};
|
|
1945
|
-
/**
|
|
1946
|
-
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
1947
|
-
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
1948
|
-
* otherwise. Fails on error-severity findings; set `warnings: true` to fail on
|
|
1949
|
-
* warnings too. The one-liner for tests and generation loops:
|
|
1950
|
-
*
|
|
1951
|
-
* @example
|
|
1952
|
-
* ```ts
|
|
1953
|
-
* test('agent machine is structurally sound', () => {
|
|
1954
|
-
* assertAgentMachine(machine);
|
|
1955
|
-
* });
|
|
1956
|
-
* ```
|
|
1957
|
-
*/
|
|
1958
|
-
function assertAgentMachine(machine, options = {}) {
|
|
1959
|
-
const diagnostics = lintAgentMachine(machine, options);
|
|
1960
|
-
const failing = options.warnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
|
|
1961
|
-
if (failing.length > 0) throw new AgentLintError(machine.id ?? "(machine)", failing);
|
|
1962
|
-
}
|
|
1963
2099
|
function pendingInvokes(step) {
|
|
1964
2100
|
const out = [];
|
|
1965
2101
|
for (const action of step.actions) {
|
|
@@ -2005,6 +2141,7 @@ async function simulateAgent(machine, options) {
|
|
|
2005
2141
|
decisions: mapValues(options.script.decisions ?? {}, (arr) => [...arr]),
|
|
2006
2142
|
invokes: mapValues(options.script.invokes ?? {}, (arr) => [...arr])
|
|
2007
2143
|
};
|
|
2144
|
+
if (options.script.userInput?.length) script.invokes[USER_INPUT_SRC] = [...options.script.userInput, ...script.invokes[USER_INPUT_SRC] ?? []];
|
|
2008
2145
|
let step = initialAgentStep(machine, options.input);
|
|
2009
2146
|
const trail = [];
|
|
2010
2147
|
for (let i = 0; i < maxSteps; i++) {
|
|
@@ -2071,13 +2208,16 @@ function mapValues(obj, fn) {
|
|
|
2071
2208
|
}
|
|
2072
2209
|
function scriptDryError(kind, src, id, request) {
|
|
2073
2210
|
const events = request?.kind === "decision" ? ` Candidate events: ${request.events.map((e) => e.type).join(", ") || "(none)"}.` : "";
|
|
2074
|
-
|
|
2211
|
+
const key = kind === "text" ? `text['${src}']` : kind === "decision" ? `decisions['${src}']` : src === USER_INPUT_SRC ? "userInput" : `invokes['${src}']`;
|
|
2212
|
+
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}`);
|
|
2075
2213
|
}
|
|
2076
2214
|
const MAX_ADVANCE_STEPS = 1e3;
|
|
2077
2215
|
async function explore(machine, options, stopWhen) {
|
|
2078
2216
|
const maxDepth = options.maxDepth ?? 8;
|
|
2079
2217
|
const maxPaths = options.maxPaths ?? 200;
|
|
2080
|
-
const
|
|
2218
|
+
const textScript = options.text ?? {};
|
|
2219
|
+
const invokeOutputs = { ...options.invokes ?? {} };
|
|
2220
|
+
if ("userInput" in options) invokeOutputs[USER_INPUT_SRC] = options.userInput;
|
|
2081
2221
|
const reachedStates = /* @__PURE__ */ new Set();
|
|
2082
2222
|
const reachedValues = [];
|
|
2083
2223
|
const terminals = [];
|
|
@@ -2102,22 +2242,22 @@ async function explore(machine, options, stopWhen) {
|
|
|
2102
2242
|
if (current.done) return { step: current };
|
|
2103
2243
|
const request = current.requests[0];
|
|
2104
2244
|
if (request && request.kind === "text") {
|
|
2105
|
-
if (!(request.src in
|
|
2245
|
+
if (!(request.src in textScript)) return {
|
|
2106
2246
|
step: current,
|
|
2107
2247
|
blockedSrc: request.src
|
|
2108
2248
|
};
|
|
2109
|
-
current = resolveAgentStep(machine, current, request,
|
|
2249
|
+
current = resolveAgentStep(machine, current, request, textScript[request.src]);
|
|
2110
2250
|
recordState(current.snapshot);
|
|
2111
2251
|
continue;
|
|
2112
2252
|
}
|
|
2113
2253
|
if (request && request.kind === "decision") return { step: current };
|
|
2114
2254
|
const [invoke] = pendingInvokes(current);
|
|
2115
2255
|
if (invoke) {
|
|
2116
|
-
if (!(invoke.src in
|
|
2256
|
+
if (!(invoke.src in invokeOutputs)) return {
|
|
2117
2257
|
step: current,
|
|
2118
2258
|
blockedSrc: invoke.src
|
|
2119
2259
|
};
|
|
2120
|
-
current = resolveAgentStep(machine, current, invoke.id,
|
|
2260
|
+
current = resolveAgentStep(machine, current, invoke.id, invokeOutputs[invoke.src]);
|
|
2121
2261
|
recordState(current.snapshot);
|
|
2122
2262
|
continue;
|
|
2123
2263
|
}
|
|
@@ -2209,9 +2349,10 @@ async function explore(machine, options, stopWhen) {
|
|
|
2209
2349
|
* depth, model-free, and reports which states are reached and how each path
|
|
2210
2350
|
* terminates. At each decision request it forks one branch per candidate event
|
|
2211
2351
|
* (guard-rejected candidates are counted in `prunedByGuard`, not explored); at
|
|
2212
|
-
* an idle wait it forks per externally-accepted event. Text
|
|
2213
|
-
*
|
|
2214
|
-
*
|
|
2352
|
+
* an idle wait it forks per externally-accepted event. Text requests resolve
|
|
2353
|
+
* from `text`, other invokes from `invokes` (or `userInput` for
|
|
2354
|
+
* `agent.userInput`) — all by-src canned-output maps, and a missing src halts
|
|
2355
|
+
* that branch with a `needs-output` terminal rather than throwing.
|
|
2215
2356
|
*
|
|
2216
2357
|
* Combinatorics are bounded by `maxDepth` (default 8) and `maxPaths` (default
|
|
2217
2358
|
* 200, reported via `hitPathCap`).
|
|
@@ -2227,13 +2368,14 @@ async function explorePaths(machine, options = {}) {
|
|
|
2227
2368
|
}
|
|
2228
2369
|
/**
|
|
2229
2370
|
* Answers "can the machine reach `statePath`?" by exploring its branches (a
|
|
2230
|
-
* thin wrapper over {@link explorePaths}). Returns
|
|
2231
|
-
* with the event sequence that reaches it, or
|
|
2371
|
+
* thin wrapper over {@link explorePaths}). Returns
|
|
2372
|
+
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
2373
|
+
* `{ reachable: false }`.
|
|
2232
2374
|
*
|
|
2233
2375
|
* @example
|
|
2234
2376
|
* ```ts
|
|
2235
|
-
* const {
|
|
2236
|
-
* //
|
|
2377
|
+
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
2378
|
+
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
2237
2379
|
* ```
|
|
2238
2380
|
*/
|
|
2239
2381
|
async function canReach(machine, statePath, options = {}) {
|
|
@@ -2245,9 +2387,9 @@ async function canReach(machine, statePath, options = {}) {
|
|
|
2245
2387
|
}
|
|
2246
2388
|
});
|
|
2247
2389
|
return witness !== void 0 ? {
|
|
2248
|
-
|
|
2390
|
+
reachable: true,
|
|
2249
2391
|
witness
|
|
2250
|
-
} : {
|
|
2392
|
+
} : { reachable: false };
|
|
2251
2393
|
}
|
|
2252
2394
|
//#endregion
|
|
2253
2395
|
//#region src/internal/is-record.ts
|
|
@@ -2408,7 +2550,8 @@ function matchesTrajectory(actual, expected, options = {}) {
|
|
|
2408
2550
|
* Scripted executors — a keyless, deterministic stand-in for a model host.
|
|
2409
2551
|
*
|
|
2410
2552
|
* `createScriptedExecutors` builds a full `{ generateText, streamText, decide }`
|
|
2411
|
-
* set that plays back canned answers from FIFO
|
|
2553
|
+
* set (plus a `userInput` handler) that plays back canned answers from FIFO
|
|
2554
|
+
* queues, so `runAgent` (or
|
|
2412
2555
|
* `provideExecutors`, or a bare `TextLogic.execute`) runs with no API key and no
|
|
2413
2556
|
* network. It is the fastest way to see a machine run, and the least ceremonial
|
|
2414
2557
|
* way to test one: same machine, same executor contract, scripted answers.
|
|
@@ -2484,15 +2627,27 @@ function emitScriptedChunk(result, info) {
|
|
|
2484
2627
|
* decisions: [(request) => ({ type: request.events[0]!.type })],
|
|
2485
2628
|
* });
|
|
2486
2629
|
* ```
|
|
2630
|
+
*
|
|
2631
|
+
* @example Scripted human input
|
|
2632
|
+
* ```ts
|
|
2633
|
+
* const scripted = createScriptedExecutors({ userInput: ['ship it'] });
|
|
2634
|
+
* await runAgent(machine, { executors: scripted, userInput: scripted.userInput });
|
|
2635
|
+
* ```
|
|
2487
2636
|
*/
|
|
2488
2637
|
function createScriptedExecutors(script = {}) {
|
|
2489
2638
|
const decisions = [...script.decisions ?? []];
|
|
2490
2639
|
const text = [...script.text ?? []];
|
|
2640
|
+
const userInput = [...script.userInput ?? []];
|
|
2491
2641
|
const nextText = async (request, info) => {
|
|
2492
2642
|
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.`);
|
|
2493
2643
|
return resolveScriptedTextEntry(text.shift(), request, info);
|
|
2494
2644
|
};
|
|
2495
2645
|
return {
|
|
2646
|
+
userInput: async (input) => {
|
|
2647
|
+
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.`);
|
|
2648
|
+
const entry = userInput.shift();
|
|
2649
|
+
return typeof entry === "function" ? await entry(input) : entry;
|
|
2650
|
+
},
|
|
2496
2651
|
generateText: nextText,
|
|
2497
2652
|
streamText: async (request, info) => {
|
|
2498
2653
|
const result = await nextText(request, info);
|
|
@@ -2566,22 +2721,22 @@ async function runSeam(machine, options) {
|
|
|
2566
2721
|
const queueKeyOf = (request) => request.name !== void 0 && queues.has(request.name) ? request.name : request.model;
|
|
2567
2722
|
/**
|
|
2568
2723
|
* Consumes this request's slot in the call plan, or resolves `undefined` when
|
|
2569
|
-
* its queue is dry.
|
|
2570
|
-
*
|
|
2724
|
+
* its queue is dry. With `repeatLast`, the last entry is replayed instead of
|
|
2725
|
+
* running dry.
|
|
2571
2726
|
*/
|
|
2572
2727
|
const takeScriptedSlot = async (request, info) => {
|
|
2573
2728
|
const queue = queues.get(queueKeyOf(request));
|
|
2574
2729
|
if (!queue?.length) return;
|
|
2575
|
-
return resolveScriptedTextEntry(queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2730
|
+
return resolveScriptedTextEntry(options.repeatLast && queue.length === 1 ? queue[0] : queue.shift(), request, info);
|
|
2576
2731
|
};
|
|
2577
2732
|
const scriptedAnswer = async (request, info) => {
|
|
2578
2733
|
const scripted = await takeScriptedSlot(request, info);
|
|
2579
|
-
if (!scripted) throw new AgentError("seam-script-exhausted", `runSeam: no scripted answer left for request ${describeText(request)}. Add an entry to \`scripts.${queueKeyOf(request)}
|
|
2734
|
+
if (!scripted) throw new 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.`);
|
|
2580
2735
|
return scripted;
|
|
2581
2736
|
};
|
|
2582
2737
|
const route = async (request, info) => {
|
|
2583
2738
|
const callIndex = calls++;
|
|
2584
|
-
const isSeam =
|
|
2739
|
+
const isSeam = request.name === seam.request && seamMatches++ === (seam.occurrence ?? 0);
|
|
2585
2740
|
if (isSeam && candidate) {
|
|
2586
2741
|
await takeScriptedSlot(request, info);
|
|
2587
2742
|
seamReached = true;
|
|
@@ -2619,7 +2774,7 @@ async function runSeam(machine, options) {
|
|
|
2619
2774
|
result = await runAgent(machine, {
|
|
2620
2775
|
...snapshot ? { snapshot } : { input: options.input },
|
|
2621
2776
|
...event ? { event } : {},
|
|
2622
|
-
...options.
|
|
2777
|
+
...options.isIdle ? { isIdle: options.isIdle } : {},
|
|
2623
2778
|
...options.actors ? { actors: options.actors } : {},
|
|
2624
2779
|
events,
|
|
2625
2780
|
executors,
|
|
@@ -2672,4 +2827,4 @@ async function runSeam(machine, options) {
|
|
|
2672
2827
|
};
|
|
2673
2828
|
}
|
|
2674
2829
|
//#endregion
|
|
2675
|
-
export { AGENT_EVENT_SCHEMA_VERSION, AGENT_INIT_EVENT_TYPE, AGENT_TRACE_SCHEMA_VERSION, AGENT_USAGE_EVENT_TYPE, AgentDecisionExhaustedError, AgentError, AgentEventLogConflictError, AgentIdleError, AgentIllegalResumeEventError, AgentLintError, AgentReplayDivergenceError, AgentReplayMachineMismatchError, AgentSnapshotVersionMismatchError, NonSerializableAgentEventError, appendMessages, assertAgentLogEntry,
|
|
2830
|
+
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, assertEventLogStoreConformance, assertJsonSerializable, assistantMessage, bindRequestExecutor, buildEnvelopeSchema, canReach, createAgentActor, createAgentRun, createAgentSchemas, 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, runSeam, serializeTraceEvent, setupAgent, simulateAgent, systemMessage, toolMessage, traceTransitions, userMessage };
|