@statelyai/agent 2.0.0-alpha.30 → 2.0.0-alpha.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +1743 -0
  2. package/dist/ai-sdk.cjs +8 -8
  3. package/dist/ai-sdk.d.cts +1 -1
  4. package/dist/ai-sdk.d.mts +1 -1
  5. package/dist/ai-sdk.mjs +3 -3
  6. package/dist/{decision-CIb7ddIv.cjs → decision-BPdEaxPf.cjs} +30 -17
  7. package/dist/{decision-BHkvuImD.mjs → decision-CV7WiV5C.mjs} +22 -9
  8. package/dist/{event-log-BZRfBRd7.cjs → event-log-CKWeC7H-.cjs} +125 -27
  9. package/dist/{event-log-DJCMHek_.mjs → event-log-DAqsalT1.mjs} +108 -28
  10. package/dist/{event-log-store-DsP6GCRh.mjs → event-log-store-BGehvVTb.mjs} +2 -2
  11. package/dist/{event-log-store-CIIJohjj.d.mts → event-log-store-C2NjVlWU.d.mts} +13 -5
  12. package/dist/{event-log-store-DZR4IIWe.cjs → event-log-store-DKyE7JKr.cjs} +2 -2
  13. package/dist/{event-log-store-CLAC9q2Y.d.cts → event-log-store-s73r1Hxf.d.cts} +13 -5
  14. package/dist/index.cjs +129 -21
  15. package/dist/index.d.cts +59 -11
  16. package/dist/index.d.mts +59 -11
  17. package/dist/index.mjs +126 -19
  18. package/dist/log.cjs +40 -5
  19. package/dist/log.d.cts +1 -1
  20. package/dist/log.d.mts +1 -1
  21. package/dist/log.mjs +40 -5
  22. package/dist/machines.cjs +1 -1
  23. package/dist/machines.mjs +1 -1
  24. package/dist/{model-source-BCKg4qJ1.cjs → model-source-BQ97_l1-.cjs} +4 -5
  25. package/dist/{model-source-BaHwJ1wK.mjs → model-source-BilDEyqk.mjs} +1 -2
  26. package/dist/openai.cjs +2 -2
  27. package/dist/openai.d.cts +1 -1
  28. package/dist/openai.d.mts +1 -1
  29. package/dist/openai.mjs +2 -2
  30. package/dist/otel.d.cts +1 -1
  31. package/dist/otel.d.mts +1 -1
  32. package/dist/{run-agent-BpHqkaZw.mjs → run-agent-2UnSUhDb.mjs} +93 -37
  33. package/dist/{run-agent-CAVt5R-r.d.mts → run-agent-Atu-wEk7.d.mts} +17 -6
  34. package/dist/{run-agent-CTlMc3Jb.d.cts → run-agent-B5QhabdY.d.cts} +17 -6
  35. package/dist/{run-agent-ctLCvNgL.cjs → run-agent-Cg0xd3K-.cjs} +93 -37
  36. package/dist/{setup-agent-CLPCkXsu.d.cts → setup-agent-BiF1N3td.d.cts} +11 -3
  37. package/dist/{setup-agent-7rV_cujV.mjs → setup-agent-CmhB-U5P.mjs} +38 -3
  38. package/dist/{setup-agent-DoUk7EdC.cjs → setup-agent-CwUJyqKB.cjs} +38 -3
  39. package/dist/{setup-agent-CGELn1qZ.d.mts → setup-agent-J9f_Wu5T.d.mts} +11 -3
  40. package/dist/tanstack-ai.cjs +3 -3
  41. package/dist/tanstack-ai.d.cts +1 -1
  42. package/dist/tanstack-ai.d.mts +1 -1
  43. package/dist/tanstack-ai.mjs +3 -3
  44. package/dist/testing.cjs +73 -29
  45. package/dist/testing.d.cts +47 -10
  46. package/dist/testing.d.mts +47 -10
  47. package/dist/testing.mjs +65 -22
  48. package/dist/{text-logic-DmciuVUQ.d.cts → text-logic-CYP502VT.d.cts} +9 -3
  49. package/dist/{text-logic-BRYOPWvY.d.mts → text-logic-DuK9HIFM.d.mts} +9 -3
  50. package/dist/{usage-BkM3z02Y.d.mts → usage-D7JKAxLc.d.mts} +1 -1
  51. package/dist/{usage-Cm_ovz9V.d.cts → usage-DVw-646x.d.cts} +1 -1
  52. package/dist/validate.cjs +3 -3
  53. package/dist/validate.d.cts +2 -2
  54. package/dist/validate.d.mts +2 -2
  55. package/dist/validate.mjs +3 -3
  56. package/dist/{verify-Cn86rT8l.d.mts → verify-BVz4cURF.d.mts} +14 -6
  57. package/dist/{verify-COFWyprJ.mjs → verify-CoCvR1eM.mjs} +55 -5
  58. package/dist/{verify-CVlsTZ-V.cjs → verify-D_llRCTr.cjs} +57 -7
  59. package/dist/{verify-Dzwd4Mvk.d.cts → verify-Dk3Vwnha.d.cts} +14 -6
  60. package/package.json +34 -4
  61. package/readme.md +70 -40
  62. package/skills/add-evals/SKILL.md +23 -8
  63. package/skills/add-traces/SKILL.md +23 -11
  64. package/skills/generate-machine/SKILL.md +2 -2
  65. package/skills/graph-engineering/SKILL.md +1 -1
  66. package/skills/improve-machine/SKILL.md +7 -6
  67. package/skills/loop-to-machine/SKILL.md +34 -7
  68. package/skills/loop-to-machine/policy.md +14 -18
@@ -54,6 +54,31 @@ var AgentTruncatedError = class extends AgentError {
54
54
  }
55
55
  };
56
56
  //#endregion
57
+ //#region src/internal/is-record.ts
58
+ /** True for a plain object-like value (arrays excluded). @internal */
59
+ function isRecord(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ /**
63
+ * True for an object whose prototype is `null` or some realm's
64
+ * `Object.prototype`. Comparing against this realm's `Object.prototype` alone
65
+ * rejects plain objects built in another realm: a jest test file's vm
66
+ * context, or `structuredClone`'s output inside one. @internal
67
+ */
68
+ function isPlainObject(value) {
69
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
70
+ const prototype = Object.getPrototypeOf(value);
71
+ return prototype === null || Object.getPrototypeOf(prototype) === null;
72
+ }
73
+ /** True for an `Error` from any realm, where `instanceof Error` sees only this one. @internal */
74
+ function isError(value) {
75
+ return value instanceof Error || Object.prototype.toString.call(value) === "[object Error]";
76
+ }
77
+ /** JSON text with object keys sorted and `undefined` fields dropped, for comparing JSON-like values. @internal */
78
+ function canonicalJson(value) {
79
+ return JSON.stringify(value, (_key, item) => isPlainObject(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item) ?? "undefined";
80
+ }
81
+ //#endregion
57
82
  //#region src/utils.ts
58
83
  /** Returns the human-readable text carried by a message, ignoring non-text parts. */
59
84
  function getMessageText(message) {
@@ -103,8 +128,7 @@ function findNonSerializableContextPaths(context, limit = 5) {
103
128
  seen.delete(obj);
104
129
  return;
105
130
  }
106
- const proto = Object.getPrototypeOf(value);
107
- if (proto === Object.prototype || proto === null) {
131
+ if (isPlainObject(value)) {
108
132
  seen.add(obj);
109
133
  for (const [key, item] of Object.entries(value)) walk(item, `${path}.${key}`);
110
134
  seen.delete(obj);
@@ -456,7 +480,7 @@ function responseMessagesOf(raw) {
456
480
  }
457
481
  function dropUndefined(value) {
458
482
  if (Array.isArray(value)) return value.map(dropUndefined);
459
- if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
483
+ if (isPlainObject(value)) {
460
484
  const out = {};
461
485
  for (const [key, entry] of Object.entries(value)) if (entry !== void 0) out[key] = dropUndefined(entry);
462
486
  return out;
@@ -932,8 +956,7 @@ function assertJsonSerializable(value, path = "entry", ancestors = /* @__PURE__
932
956
  assertNoHiddenKeys(value, path, (key) => key === "length" || arrayIndexPattern.test(key));
933
957
  return;
934
958
  }
935
- const prototype = Object.getPrototypeOf(value);
936
- if (prototype !== Object.prototype && prototype !== null) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
959
+ if (!isPlainObject(value)) throw new NonSerializableAgentEventError(path, value.constructor?.name ?? "object");
937
960
  assertNoHiddenKeys(value, path);
938
961
  for (const [key, child] of Object.entries(value)) assertJsonSerializable(child, `${path}.${key}`, ancestors);
939
962
  } finally {
@@ -990,22 +1013,32 @@ function exhaustedAttemptsOf(error) {
990
1013
  return Array.isArray(attempts) && isJsonSerializable(attempts) ? attempts : void 0;
991
1014
  }
992
1015
  /**
993
- * Replaces `Error` instances with plain `{ name, message, cause? }` objects so
994
- * an `xstate.error.actor` payload survives the log's JSON round-trip. A
995
- * decision-exhausted error also keeps its `code` and `attempts`. Cycles
1016
+ * Marks an object {@link normalizeEventErrors} made from an `Error`, so replay
1017
+ * restores that object, and only that one, as an `Error`: a thrown plain
1018
+ * `{ name, message }` stays a plain object.
1019
+ */
1020
+ const NORMALIZED_ERROR_KEY = "@error";
1021
+ /**
1022
+ * Replaces `Error` instances with plain `{ name, message, code?, cause?,
1023
+ * "@error": true }` objects so an `xstate.error.actor` payload survives the
1024
+ * log's JSON round-trip. `code` is kept when it is a string or a number; a
1025
+ * decision-exhausted error also keeps its `attempts`. Replay turns the
1026
+ * object back into an `Error` (see `restoreJournaledEvent`). Cycles
996
1027
  * are preserved by identity; non-plain objects are left alone (and are then
997
1028
  * rejected by {@link assertJsonSerializable}).
998
1029
  */
999
1030
  function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
1000
- if (value instanceof Error) {
1031
+ if (isError(value)) {
1001
1032
  const normalized = {
1002
1033
  name: value.name,
1003
- message: value.message
1034
+ message: value.message,
1035
+ [NORMALIZED_ERROR_KEY]: true
1004
1036
  };
1005
1037
  seen.set(value, normalized);
1006
1038
  if (value.cause !== void 0) normalized.cause = normalizeEventErrors(value.cause, seen);
1007
- if (value.code === DECISION_EXHAUSTED_CODE) {
1008
- normalized.code = DECISION_EXHAUSTED_CODE;
1039
+ const code = value.code;
1040
+ if (typeof code === "string" || typeof code === "number" && Number.isFinite(code)) normalized.code = code;
1041
+ if (code === DECISION_EXHAUSTED_CODE) {
1009
1042
  const attempts = exhaustedAttemptsOf(value);
1010
1043
  if (attempts !== void 0) normalized.attempts = structuredClone(attempts);
1011
1044
  }
@@ -1020,8 +1053,7 @@ function normalizeEventErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
1020
1053
  for (const item of value) result.push(normalizeEventErrors(item, seen));
1021
1054
  return result;
1022
1055
  }
1023
- const prototype = Object.getPrototypeOf(value);
1024
- if (prototype !== Object.prototype && prototype !== null) return value;
1056
+ if (!isPlainObject(value)) return value;
1025
1057
  const result = {};
1026
1058
  seen.set(value, result);
1027
1059
  for (const [key, item] of Object.entries(value)) result[key] = normalizeEventErrors(item, seen);
@@ -1046,7 +1078,7 @@ function canonicalizeForHash(value, seen = /* @__PURE__ */ new WeakSet(), positi
1046
1078
  if (typeof value === "symbol") return `[symbol:${String(value)}]`;
1047
1079
  if (value === null || typeof value !== "object") return value;
1048
1080
  if (value instanceof Date) return `[Date:${value.toISOString()}]`;
1049
- if (value instanceof Error) {
1081
+ if (isError(value)) {
1050
1082
  if (seen.has(value)) return "[circular]";
1051
1083
  seen.add(value);
1052
1084
  try {
@@ -1141,7 +1173,7 @@ function createReplayEntry(machine, entries, event, options = {}) {
1141
1173
  recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1142
1174
  machineId: machineIdOf(machine),
1143
1175
  machineVersion,
1144
- event: normalizeDoneActorEvent(normalizeEventErrors(event)),
1176
+ event: normalizeDoneActorEvent(pruneUndefined(normalizeEventErrors(event))),
1145
1177
  ...options.causationId !== void 0 ? { causationId: options.causationId } : {},
1146
1178
  ...options.metadata !== void 0 ? { metadata: options.metadata } : {},
1147
1179
  ...options.attempts !== void 0 && options.attempts.length > 0 ? { attempts: options.attempts } : {}
@@ -1154,15 +1186,23 @@ function createReplayEntry(machine, entries, event, options = {}) {
1154
1186
  }).persistedSnapshot) };
1155
1187
  return entry;
1156
1188
  }
1157
- function pruneUndefined(value) {
1158
- if (Array.isArray(value)) return value.map(pruneUndefined);
1189
+ function pruneUndefined(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1159
1190
  if (value === null || typeof value !== "object") return value;
1160
- if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
1191
+ if (ancestors.has(value)) return value;
1192
+ if (Array.isArray(value)) {
1193
+ ancestors.add(value);
1194
+ const result = value.map((item) => pruneUndefined(item, ancestors));
1195
+ ancestors.delete(value);
1196
+ return result;
1197
+ }
1198
+ if (!isPlainObject(value)) return value;
1199
+ ancestors.add(value);
1161
1200
  const result = {};
1162
1201
  for (const [key, child] of Object.entries(value)) {
1163
1202
  if (child === void 0) continue;
1164
- result[key] = pruneUndefined(child);
1203
+ result[key] = pruneUndefined(child, ancestors);
1165
1204
  }
1205
+ ancestors.delete(value);
1166
1206
  return result;
1167
1207
  }
1168
1208
  /**
@@ -1199,8 +1239,8 @@ function isSnapshotInitEntry(entry) {
1199
1239
  * Minted once per run and inherited verbatim by every resume of that log, so
1200
1240
  * it names one lineage rather than one process. Entry ids cannot serve this
1201
1241
  * role — the built-in ids are index-derived, so every log's init entry is
1202
- * `evt_00000000`. A fork copies the init entry, so it shares its parent's
1203
- * execution id by construction.
1242
+ * `evt_00000000`. A fork ({@link forkEventLog}, `store.fork`) mints its own
1243
+ * and records the parent's in `metadata.forkedFrom`.
1204
1244
  *
1205
1245
  * Returns `undefined` for a log with no init entry and for one written without
1206
1246
  * the field — callers must treat the id as optional.
@@ -1248,12 +1288,31 @@ function validateReplayEntries(entries, options = {}) {
1248
1288
  /**
1249
1289
  * The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
1250
1290
  * and must leave the init entry in place, so the fork is still self-contained.
1291
+ *
1292
+ * The fork is its own lineage: its init entry gets a fresh
1293
+ * `metadata.executionId` and `metadata.forkedFrom: { executionId?, logIndex }`:
1294
+ * the cutoff, and the parent's id when it had one. Call keys derive
1295
+ * from the execution id, so a fork's calls never collide with its parent's.
1296
+ * Every other entry is copied as is.
1251
1297
  */
1252
1298
  function forkEventLog(entries, upToIndex) {
1253
1299
  if (!Number.isInteger(upToIndex) || upToIndex < 1 || upToIndex > entries.length) throw new AgentEventLogError(`forkEventLog: upToIndex must be an integer in [1, ${entries.length}]; got ${String(upToIndex)}.`);
1254
1300
  const forked = entries.slice(0, upToIndex);
1255
1301
  if (!isInitEntry(forked[0])) throw new AgentEventLogError(`forkEventLog: the forked prefix must start with the reserved '${AGENT_INIT_EVENT_TYPE}' entry.`);
1256
- return forked;
1302
+ const [init, ...rest] = forked;
1303
+ const parentExecutionId = getLogExecutionId(forked);
1304
+ const forkedFrom = {
1305
+ ...parentExecutionId !== void 0 ? { executionId: parentExecutionId } : {},
1306
+ logIndex: upToIndex
1307
+ };
1308
+ return [{
1309
+ ...init,
1310
+ metadata: {
1311
+ ...init.metadata,
1312
+ executionId: crypto.randomUUID(),
1313
+ forkedFrom
1314
+ }
1315
+ }, ...rest];
1257
1316
  }
1258
1317
  const DONE_ACTOR_EVENT_TYPE = "xstate.done.actor";
1259
1318
  const ERROR_ACTOR_EVENT_TYPE = "xstate.error.actor";
@@ -1359,12 +1418,33 @@ function rebindActorSession(event, snapshot, sessions) {
1359
1418
  sessionId
1360
1419
  };
1361
1420
  }
1362
- function restoreDoneActorOutput(event) {
1363
- if (event.type !== DONE_ACTOR_EVENT_TYPE || Object.hasOwn(event, "output")) return event;
1364
- return {
1421
+ /**
1422
+ * The event as the machine received it live: a done event's dropped
1423
+ * `undefined` output restored, and an error event's normalized error turned
1424
+ * back into an `Error`, so `error instanceof Error` holds on replay too.
1425
+ */
1426
+ function restoreJournaledEvent(event) {
1427
+ if (event.type === DONE_ACTOR_EVENT_TYPE && !Object.hasOwn(event, "output")) return {
1365
1428
  ...event,
1366
1429
  output: void 0
1367
1430
  };
1431
+ if (event.type.startsWith(ERROR_ACTOR_EVENT_TYPE)) {
1432
+ const error = event.error;
1433
+ if (isNormalizedError(error)) return {
1434
+ ...event,
1435
+ error: toError(error)
1436
+ };
1437
+ }
1438
+ return event;
1439
+ }
1440
+ function isNormalizedError(value) {
1441
+ return isPlainObject(value) && value[NORMALIZED_ERROR_KEY] === true && typeof value.name === "string" && typeof value.message === "string";
1442
+ }
1443
+ function toError(normalized) {
1444
+ const { name, message, cause, [NORMALIZED_ERROR_KEY]: _marker, ...rest } = normalized;
1445
+ const error = new Error(message, cause === void 0 ? void 0 : { cause: isNormalizedError(cause) ? toError(cause) : cause });
1446
+ error.name = name;
1447
+ return Object.assign(error, rest);
1368
1448
  }
1369
1449
  function verifyEntry(entry, persisted, mode) {
1370
1450
  if (mode === false) return;
@@ -1412,7 +1492,7 @@ function foldLog(machine, entries, options = {}) {
1412
1492
  verifyEntry(entries[0], persisted, options.verify);
1413
1493
  const sessions = /* @__PURE__ */ new Map();
1414
1494
  const apply = (entry) => {
1415
- const event = restoreDoneActorOutput(rebindActorSession(entry.event, snapshot, sessions));
1495
+ const event = restoreJournaledEvent(rebindActorSession(entry.event, snapshot, sessions));
1416
1496
  [snapshot] = transition(machine, snapshot, event);
1417
1497
  persisted = machine.getPersistedSnapshot(snapshot);
1418
1498
  return persisted;
@@ -1490,4 +1570,4 @@ function recordLog(actor) {
1490
1570
  };
1491
1571
  }
1492
1572
  //#endregion
1493
- export { getStateMeta as $, createTextLogic as A, responseMessagesOf as B, AGENT_USAGE_EVENT_TYPE as C, STREAM_TEXT_ACTOR as D, GENERATE_TEXT_ACTOR as E, normalizeGeneratorResult as F, missingActor as G, getAgentExecutionOptions as H, parseOutput as I, findNonSerializableContextPaths as J, setAgentExecutionOptions as K, parseProviderOutput as L, getCallFinishReason as M, getCallUsage as N, bindRequestExecutor as O, isTextLogic as P, getMessageText as Q, providerOutputSchema as R, validateReplayEntries as S, DECIDE_ACTOR as T, getRegisteredAgentExecutionOptions as U, executorBoundLogics as V, isUnboundPlaceholder as W, getJsonSchemaSync as X, getJsonSchema as Y, getMachineStructuralHash as Z, getUsageFromEvents as _, AgentReplayDivergenceError as a, userMessage as at, recordLog as b, assertAgentLogEntry as c, AgentTruncatedError as ct, createReplayEntry as d, getStatePath as et, foldLog as f, getSnapshotStateHash as g, getLogExecutionId as h, AgentMachineVersionMismatchError as i, toolMessage as it, getAgentOutputMode as j, builtinTextActors as k, assertJsonSerializable as l, getDecisionAttempts as m, AGENT_INIT_EVENT_TYPE as n, resolveMachineVersion as nt, NonSerializableAgentEventError as o, validateSchemaSync as ot, forkEventLog as p, assistantMessage as q, AgentEventLogError as r, systemMessage as rt, agentCallOccurrence as s, AgentError as st, AGENT_EVENT_SCHEMA_VERSION as t, isStandardSchema as tt, canonicalEventJson as u, initEntry as v, AGENT_USAGE_TOKEN_FIELDS as w, replay as x, rebindActorSession as y, resolveTextLogicValue as z };
1573
+ export { getStateMeta as $, createTextLogic as A, responseMessagesOf as B, AGENT_USAGE_EVENT_TYPE as C, STREAM_TEXT_ACTOR as D, GENERATE_TEXT_ACTOR as E, normalizeGeneratorResult as F, missingActor as G, getAgentExecutionOptions as H, parseOutput as I, findNonSerializableContextPaths as J, setAgentExecutionOptions as K, parseProviderOutput as L, getCallFinishReason as M, getCallUsage as N, bindRequestExecutor as O, isTextLogic as P, getMessageText as Q, providerOutputSchema as R, validateReplayEntries as S, DECIDE_ACTOR as T, getRegisteredAgentExecutionOptions as U, executorBoundLogics as V, isUnboundPlaceholder as W, getJsonSchemaSync as X, getJsonSchema as Y, getMachineStructuralHash as Z, getUsageFromEvents as _, AgentReplayDivergenceError as a, userMessage as at, recordLog as b, assertAgentLogEntry as c, isPlainObject as ct, createReplayEntry as d, AgentTruncatedError as dt, getStatePath as et, foldLog as f, getSnapshotStateHash as g, getLogExecutionId as h, AgentMachineVersionMismatchError as i, toolMessage as it, getAgentOutputMode as j, builtinTextActors as k, assertJsonSerializable as l, isRecord as lt, getDecisionAttempts as m, AGENT_INIT_EVENT_TYPE as n, resolveMachineVersion as nt, NonSerializableAgentEventError as o, validateSchemaSync as ot, forkEventLog as p, assistantMessage as q, AgentEventLogError as r, systemMessage as rt, agentCallOccurrence as s, canonicalJson as st, AGENT_EVENT_SCHEMA_VERSION as t, isStandardSchema as tt, canonicalEventJson as u, AgentError as ut, initEntry as v, AGENT_USAGE_TOKEN_FIELDS as w, replay as x, rebindActorSession as y, resolveTextLogicValue as z };
@@ -1,4 +1,4 @@
1
- import { c as assertAgentLogEntry, st as AgentError } from "./event-log-DJCMHek_.mjs";
1
+ import { c as assertAgentLogEntry, p as forkEventLog, ut as AgentError } from "./event-log-DAqsalT1.mjs";
2
2
  //#region src/event-log-store.ts
3
3
  /**
4
4
  * Durable storage for {@link AgentLogEntry} logs: the one interface a host
@@ -65,7 +65,7 @@ function createInMemoryEventLogStore() {
65
65
  if (!source) throw new Error(`AgentEventLogStore.fork: unknown source thread "${threadId}".`);
66
66
  const upTo = upToIndex ?? source.length;
67
67
  if (!Number.isInteger(upTo) || upTo < 1 || upTo > source.length) throw new Error(`AgentEventLogStore.fork: upToIndex must be an integer in [1, ${source.length}] on thread "${threadId}"; got ${String(upTo)}.`);
68
- threads.set(newThreadId, source.slice(0, upTo).map((entry) => clone(entry)));
68
+ threads.set(newThreadId, forkEventLog(source, upTo).map((entry) => clone(entry)));
69
69
  },
70
70
  async listThreads(options = {}) {
71
71
  const { prefix = "", limit, cursor } = options;
@@ -1,4 +1,4 @@
1
- import { I as DecisionAttempt, Q as AgentError, t as AgentCallUsage } from "./text-logic-BRYOPWvY.mjs";
1
+ import { I as DecisionAttempt, Q as AgentError, t as AgentCallUsage } from "./text-logic-DuK9HIFM.mjs";
2
2
  import { Actor, AnyMachineSnapshot, AnyStateMachine, EventObject, Snapshot, SnapshotFrom } from "xstate";
3
3
  //#region src/event-log.d.ts
4
4
  /** The durable entry envelope version. */
@@ -186,8 +186,8 @@ declare function initEntry(machine: AnyStateMachine, init?: AgentLogInit, option
186
186
  * Minted once per run and inherited verbatim by every resume of that log, so
187
187
  * it names one lineage rather than one process. Entry ids cannot serve this
188
188
  * role — the built-in ids are index-derived, so every log's init entry is
189
- * `evt_00000000`. A fork copies the init entry, so it shares its parent's
190
- * execution id by construction.
189
+ * `evt_00000000`. A fork ({@link forkEventLog}, `store.fork`) mints its own
190
+ * and records the parent's in `metadata.forkedFrom`.
191
191
  *
192
192
  * Returns `undefined` for a log with no init entry and for one written without
193
193
  * the field — callers must treat the id as optional.
@@ -210,6 +210,12 @@ declare function validateReplayEntries(entries: readonly AgentLogEntry[], option
210
210
  /**
211
211
  * The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
212
212
  * and must leave the init entry in place, so the fork is still self-contained.
213
+ *
214
+ * The fork is its own lineage: its init entry gets a fresh
215
+ * `metadata.executionId` and `metadata.forkedFrom: { executionId?, logIndex }`:
216
+ * the cutoff, and the parent's id when it had one. Call keys derive
217
+ * from the execution id, so a fork's calls never collide with its parent's.
218
+ * Every other entry is copied as is.
213
219
  */
214
220
  declare function forkEventLog(entries: readonly AgentLogEntry[], upToIndex: number): AgentLogEntry[];
215
221
  /**
@@ -380,8 +386,10 @@ interface AgentEventLogStore {
380
386
  * init entry and stays a self-contained log. The fork then appends
381
387
  * independently. Rejects (plain `Error`) if `newThreadId` already has
382
388
  * entries, the source is unknown, or `upToIndex` is out of range.
383
- * Implementations may copy-on-write or physically copy; observable behavior
384
- * must match a full copy.
389
+ *
390
+ * The fork is its own lineage: store what {@link forkEventLog} returns for
391
+ * the prefix, whose init entry carries a fresh `metadata.executionId` and
392
+ * `metadata.forkedFrom`. Every other entry must match the source.
385
393
  */
386
394
  fork(input: {
387
395
  threadId: string;
@@ -1,4 +1,4 @@
1
- const require_event_log = require("./event-log-BZRfBRd7.cjs");
1
+ const require_event_log = require("./event-log-CKWeC7H-.cjs");
2
2
  //#region src/event-log-store.ts
3
3
  /**
4
4
  * Durable storage for {@link AgentLogEntry} logs: the one interface a host
@@ -65,7 +65,7 @@ function createInMemoryEventLogStore() {
65
65
  if (!source) throw new Error(`AgentEventLogStore.fork: unknown source thread "${threadId}".`);
66
66
  const upTo = upToIndex ?? source.length;
67
67
  if (!Number.isInteger(upTo) || upTo < 1 || upTo > source.length) throw new Error(`AgentEventLogStore.fork: upToIndex must be an integer in [1, ${source.length}] on thread "${threadId}"; got ${String(upTo)}.`);
68
- threads.set(newThreadId, source.slice(0, upTo).map((entry) => clone(entry)));
68
+ threads.set(newThreadId, require_event_log.forkEventLog(source, upTo).map((entry) => clone(entry)));
69
69
  },
70
70
  async listThreads(options = {}) {
71
71
  const { prefix = "", limit, cursor } = options;
@@ -1,4 +1,4 @@
1
- import { I as DecisionAttempt, Q as AgentError, t as AgentCallUsage } from "./text-logic-DmciuVUQ.cjs";
1
+ import { I as DecisionAttempt, Q as AgentError, t as AgentCallUsage } from "./text-logic-CYP502VT.cjs";
2
2
  import { Actor, AnyMachineSnapshot, AnyStateMachine, EventObject, Snapshot, SnapshotFrom } from "xstate";
3
3
  //#region src/event-log.d.ts
4
4
  /** The durable entry envelope version. */
@@ -186,8 +186,8 @@ declare function initEntry(machine: AnyStateMachine, init?: AgentLogInit, option
186
186
  * Minted once per run and inherited verbatim by every resume of that log, so
187
187
  * it names one lineage rather than one process. Entry ids cannot serve this
188
188
  * role — the built-in ids are index-derived, so every log's init entry is
189
- * `evt_00000000`. A fork copies the init entry, so it shares its parent's
190
- * execution id by construction.
189
+ * `evt_00000000`. A fork ({@link forkEventLog}, `store.fork`) mints its own
190
+ * and records the parent's in `metadata.forkedFrom`.
191
191
  *
192
192
  * Returns `undefined` for a log with no init entry and for one written without
193
193
  * the field — callers must treat the id as optional.
@@ -210,6 +210,12 @@ declare function validateReplayEntries(entries: readonly AgentLogEntry[], option
210
210
  /**
211
211
  * The prefix `[0, upToIndex)` of a log, as a new log. `upToIndex` is exclusive
212
212
  * and must leave the init entry in place, so the fork is still self-contained.
213
+ *
214
+ * The fork is its own lineage: its init entry gets a fresh
215
+ * `metadata.executionId` and `metadata.forkedFrom: { executionId?, logIndex }`:
216
+ * the cutoff, and the parent's id when it had one. Call keys derive
217
+ * from the execution id, so a fork's calls never collide with its parent's.
218
+ * Every other entry is copied as is.
213
219
  */
214
220
  declare function forkEventLog(entries: readonly AgentLogEntry[], upToIndex: number): AgentLogEntry[];
215
221
  /**
@@ -380,8 +386,10 @@ interface AgentEventLogStore {
380
386
  * init entry and stays a self-contained log. The fork then appends
381
387
  * independently. Rejects (plain `Error`) if `newThreadId` already has
382
388
  * entries, the source is unknown, or `upToIndex` is out of range.
383
- * Implementations may copy-on-write or physically copy; observable behavior
384
- * must match a full copy.
389
+ *
390
+ * The fork is its own lineage: store what {@link forkEventLog} returns for
391
+ * the prefix, whose init entry carries a fresh `metadata.executionId` and
392
+ * `metadata.forkedFrom`. Every other entry must match the source.
385
393
  */
386
394
  fork(input: {
387
395
  threadId: string;
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_event_log = require("./event-log-BZRfBRd7.cjs");
3
- const require_decision = require("./decision-CIb7ddIv.cjs");
4
- const require_setup_agent = require("./setup-agent-DoUk7EdC.cjs");
5
- const require_event_log_store = require("./event-log-store-DZR4IIWe.cjs");
6
- const require_run_agent = require("./run-agent-ctLCvNgL.cjs");
2
+ const require_event_log = require("./event-log-CKWeC7H-.cjs");
3
+ const require_decision = require("./decision-BPdEaxPf.cjs");
4
+ const require_setup_agent = require("./setup-agent-CwUJyqKB.cjs");
5
+ const require_event_log_store = require("./event-log-store-DKyE7JKr.cjs");
6
+ const require_run_agent = require("./run-agent-Cg0xd3K-.cjs");
7
7
  let xstate = require("xstate");
8
8
  //#region src/messages.ts
9
9
  const KNOWN_PART_TYPES = /* @__PURE__ */ new Set([
@@ -168,7 +168,8 @@ function validateChoice(value, path) {
168
168
  * reads: an object whose optional `interaction` declares a `label` (a string
169
169
  * with `{context.path}` interpolation, or a function of the context), an
170
170
  * `events` map of choices, payload `choices` (an array, or a context path to
171
- * one), and a `textEvent` (a type, or `{ type, field }`) for free-text answers. This is
171
+ * one), and a `textEvent` (a type, or `{ type, field }`) for free-text answers; and
172
+ * an optional `actor: "human"`, which tags a transition as sent by a person. This is
172
173
  * the default `meta` type `setupAgent` gives a machine; pass it to
173
174
  * `createAgentSchemas({ meta: interactionMetaSchema })` only when building a
174
175
  * schema pack by hand.
@@ -178,6 +179,8 @@ const interactionMetaSchema = { "~standard": {
178
179
  vendor: "statelyai-agent",
179
180
  validate(value) {
180
181
  if (!value || typeof value !== "object" || Array.isArray(value)) return { issues: [{ message: "Expected a state meta object" }] };
182
+ const actor = value["actor"];
183
+ if (actor !== void 0 && actor !== "human") return { issues: [issue("Expected \"human\"", ["actor"])] };
181
184
  const interaction = value["interaction"];
182
185
  if (interaction === void 0) return { value };
183
186
  if (!interaction || typeof interaction !== "object" || Array.isArray(interaction)) return { issues: [issue("Expected an interaction descriptor object", ["interaction"])] };
@@ -460,10 +463,36 @@ function guardText(guard) {
460
463
  */
461
464
  const mayDecline = (code) => /\bundefined\b|\bnull\b|\bvoid 0\b|\breturn\s*;|\bif\s*\(|(?<!\?)\?(?![.?])|&&/.test(code.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, "\"\""));
462
465
  /**
466
+ * True when the function returns a call's result (`=> next(ctx)`, `return
467
+ * pick(x)`), so its target is not in its own source. Reads only the
468
+ * function's own returned expressions, not those of callbacks nested in it.
469
+ */
470
+ function returnsCall(code) {
471
+ const text = code.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, "\"\"");
472
+ const startsWithCall = (at) => /^[\s(]*[A-Za-z_$][\w$.]*\s*\(/.test(text.slice(at));
473
+ let parens = 0;
474
+ let body = -1;
475
+ for (let i = 0; i < text.length && body < 0; i++) {
476
+ const char = text[i];
477
+ if (char === "(") parens++;
478
+ else if (char === ")") parens--;
479
+ else if (parens === 0 && char === "=" && text[i + 1] === ">") body = i + 2;
480
+ else if (parens === 0 && char === "{") body = i;
481
+ }
482
+ if (body < 0) return false;
483
+ while (/\s/.test(text[body] ?? "")) body++;
484
+ if (text[body] !== "{") return startsWithCall(body);
485
+ let depth = 0;
486
+ for (let i = body; i < text.length; i++) if (text[i] === "{") depth++;
487
+ else if (text[i] === "}" && --depth === 0) return false;
488
+ else if (depth === 1 && text.startsWith("return", i) && !/[\w$]/.test(text[i - 1] ?? "") && !/[\w$]/.test(text[i + 6] ?? "") && startsWithCall(i + 6)) return true;
489
+ return false;
490
+ }
491
+ /**
463
492
  * Where a transition written as a function goes: every string literal after a
464
493
  * `target:` that names a real state node, `(conditional)` when it may decline
465
494
  * or pick among several, `(stays)` when its source has no `target`, `?` when
466
- * it names none that resolves.
495
+ * it names none that resolves or returns a helper call's result.
467
496
  */
468
497
  function targetsInSource(source, code, choice) {
469
498
  const found = /* @__PURE__ */ new Set();
@@ -476,14 +505,15 @@ function targetsInSource(source, code, choice) {
476
505
  const conditional = names.length > 1 || !choice && mayDecline(code);
477
506
  return `${names.join(", ")}${conditional ? " (conditional)" : ""}`;
478
507
  }
479
- return /\btarget\s*:/.test(code) ? "?" : "(stays)";
508
+ return /\btarget\s*:/.test(code) || returnsCall(code) ? "?" : "(stays)";
480
509
  }
481
- /** One transition, as a line: `on GO if context.ready → review; sets draft — description`. */
510
+ /** One transition, as a line: `on GO (human) if context.ready → review; sets draft — description`. */
482
511
  function transitionLine(source, head, transition, options = {}) {
483
512
  const code = codeOf(transition);
484
513
  let to;
485
514
  let guard;
486
515
  let description;
516
+ let human = false;
487
517
  let sets;
488
518
  if (typeof transition === "string") {
489
519
  const node = resolveTarget(source, transition);
@@ -500,12 +530,13 @@ function transitionLine(source, head, transition, options = {}) {
500
530
  }).join(", ") : "(stays)";
501
531
  guard = guardText(transition[options.guardKey ?? "guard"]);
502
532
  description = typeof transition.description === "string" ? transition.description : void 0;
533
+ human = isObject(transition.meta) && transition.meta.actor === "human";
503
534
  sets = contextKeys(transition, void 0);
504
535
  } else {
505
536
  to = "?";
506
537
  sets = [];
507
538
  }
508
- return `${head}${guard ? ` if ${guard}` : ""} → ${to}${sets.length ? `; sets ${sets.join(", ")}` : ""}${description ? ` — ${oneLine(description)}` : ""}`;
539
+ return `${head}${human ? " (human)" : ""}${guard ? ` if ${guard}` : ""} → ${to}${sets.length ? `; sets ${sets.join(", ")}` : ""}${description ? ` — ${oneLine(description)}` : ""}`;
509
540
  }
510
541
  function formatMs(ms) {
511
542
  if (ms < 1e3) return `${ms}ms`;
@@ -552,7 +583,7 @@ function stateLine(node, json) {
552
583
  else if (node.type === "compound" && typeof json.initial === "string") marker = ` (initial: ${json.initial})`;
553
584
  const tags = node.tags.length ? ` [tags: ${node.tags.join(", ")}]` : "";
554
585
  const interaction = node.meta?.interaction;
555
- const label = typeof interaction?.label === "string" ? ` [interaction: "${oneLine(interaction.label)}"]` : "";
586
+ const label = !interaction ? "" : typeof interaction.label === "string" ? ` [interaction: "${oneLine(interaction.label)}"]` : typeof interaction.label === "function" ? " [interaction: computed label]" : " [interaction]";
556
587
  const description = node.description ? ` — ${oneLine(node.description)}` : "";
557
588
  return `${node.key}${marker}${tags}${label}${description}`;
558
589
  }
@@ -589,9 +620,10 @@ function eventLines(machine) {
589
620
  * label and description (`*` marks the active ones when `snapshot` is given),
590
621
  * and under it every transition leaving it, one per line: `on EVENT`,
591
622
  * `always`, `after <delay>`, invoke `done`/`error`, and `choice` branches,
592
- * each with its guard (`if …`), where it goes (`→ review`), and what it sets
593
- * in context (`sets draft, attempts`). An `Events` section lists the
594
- * machine's event schemas with their descriptions and fields.
623
+ * each with `(human)` when its `meta.actor` is `"human"`, its guard (`if …`),
624
+ * where it goes (`→ review`), and what it sets in context (`sets draft,
625
+ * attempts`). An `Events` section lists the machine's event schemas with their
626
+ * descriptions and fields.
595
627
  *
596
628
  * A transition written as a function shows the targets its source names
597
629
  * (`→ review (conditional)` when it may decline or pick among several),
@@ -635,7 +667,8 @@ function describeMachine(machine, options = {}) {
635
667
  * Asks a model to choose the next event for ANY XState snapshot, including a
636
668
  * machine built with plain `createMachine`/`setup` that knows nothing about AI.
637
669
  * The candidates are the `allowedEvents` an active state handles, one option per
638
- * payload, kept only where `snapshot.can(event)` is true; the rest come back as
670
+ * payload, kept only where `snapshot.can(event)` is true and no transition
671
+ * tagged `meta: { actor: "human" }` handles the type; the rest come back as
639
672
  * `withheld`. Each option is
640
673
  * described from the transition's `description`, else the event schema's,
641
674
  * plus (with `lookahead`) what it changes. The chosen event must equal one
@@ -670,19 +703,21 @@ function describeMachine(machine, options = {}) {
670
703
  async function decideNext(snapshot, options) {
671
704
  const patterns = typeof options.allowedEvents === "string" ? [options.allowedEvents] : options.allowedEvents;
672
705
  const schemas = snapshot.machine.schemas?.events;
673
- const events = require_decision.getAcceptedEvents(snapshot, {
706
+ const candidates = require_decision.getAcceptedEvents(snapshot, {
674
707
  events: schemas,
675
708
  eventTypes: patterns
676
709
  }).filter((event) => !require_decision.isEventPattern(event.type));
710
+ const humanEventTypes = require_decision.getHumanEventTypes(snapshot);
711
+ const events = candidates.filter((event) => !humanEventTypes.has(event.type));
677
712
  const payloads = options.payloads?.(snapshot) ?? {};
678
713
  const accepted = [];
679
714
  const withheld = [];
680
- for (const { type } of events) for (const payload of payloads[type] ?? [{}]) {
715
+ for (const { type } of candidates) for (const payload of payloads[type] ?? [{}]) {
681
716
  const event = {
682
717
  ...payload,
683
718
  type
684
719
  };
685
- (safeCan(snapshot, event) ? accepted : withheld).push(event);
720
+ (!humanEventTypes.has(type) && safeCan(snapshot, event) ? accepted : withheld).push(event);
686
721
  }
687
722
  const offered = accepted.map((event) => ({
688
723
  event,
@@ -742,7 +777,7 @@ function schemaDescription(schema) {
742
777
  if (typeof direct === "string") return direct;
743
778
  try {
744
779
  const json = schema["~standard"].jsonSchema?.input?.({ target: "draft-2020-12" });
745
- return require_decision.isRecord(json) && typeof json.description === "string" ? json.description : void 0;
780
+ return require_event_log.isRecord(json) && typeof json.description === "string" ? json.description : void 0;
746
781
  } catch {
747
782
  return;
748
783
  }
@@ -757,8 +792,8 @@ function describeEffect(snapshot, event, keys) {
757
792
  }
758
793
  const changes = [];
759
794
  if (require_event_log.canonicalEventJson(snapshot.value) !== require_event_log.canonicalEventJson(next.value)) changes.push(`state: ${short(snapshot.value, maxLength)} → ${short(next.value, maxLength)}`);
760
- const before = require_decision.isRecord(snapshot.context) ? snapshot.context : {};
761
- const after = require_decision.isRecord(next.context) ? next.context : {};
795
+ const before = require_event_log.isRecord(snapshot.context) ? snapshot.context : {};
796
+ const after = require_event_log.isRecord(next.context) ? next.context : {};
762
797
  for (const key of keys ?? /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) if (require_event_log.canonicalEventJson(before[key]) !== require_event_log.canonicalEventJson(after[key])) changes.push(`${key}: ${short(before[key], maxLength)} → ${short(after[key], maxLength)}`);
763
798
  return changes.length ? changes.join("; ") : "nothing changes";
764
799
  }
@@ -772,6 +807,78 @@ function short(value, max = 60) {
772
807
  return text.length > max ? `${text.slice(0, max - 1)}…` : text;
773
808
  }
774
809
  //#endregion
810
+ //#region src/policy-decider.ts
811
+ /**
812
+ * Runs a policy machine as a decide executor: each decision request starts one
813
+ * run of `policy`, and its output is the chosen event. The policy can answer
814
+ * with a rule (a `choice` over the facts, no model call) and invoke
815
+ * `agent.decide` for the rest, answered by `options.executors`.
816
+ *
817
+ * The environment still judges the event: one it does not accept is retried
818
+ * like any other decider's choice. The policy's input must be JSON (it opens
819
+ * the policy run's event log): the default drops keys whose value is
820
+ * `undefined`, and any other value that is not JSON throws before the run. The policy's token usage is reported on the
821
+ * result, and left out when the policy made no model call. A run that does not
822
+ * end `done` with an event throws `AgentError` with code `policy-not-done`.
823
+ *
824
+ * @example
825
+ * ```ts no-check
826
+ * const decide = createPolicyDecider(policyMachine, { executors: modelExecutors });
827
+ * await runAgent(deskMachine, { input, executors: { ...modelExecutors, decide } });
828
+ * ```
829
+ */
830
+ function createPolicyDecider(policy, options = {}) {
831
+ const { input = defaultInput, threadId, ...runOptions } = options;
832
+ const id = policy.config.id ?? policy.id;
833
+ return async (request, info) => {
834
+ const policyInput = input(request);
835
+ require_event_log.assertJsonSerializable(policyInput, "policy input");
836
+ const thread = runOptions.store === void 0 ? void 0 : await policyThread(request, policyInput, info);
837
+ const result = await require_run_agent.runAgent(policy, {
838
+ ...runOptions,
839
+ input: policyInput,
840
+ ...thread === void 0 ? {} : { threadId: thread },
841
+ ...info?.signal ? { signal: info.signal } : {}
842
+ });
843
+ if (result.status !== "done") {
844
+ const why = result.status === "error" ? `: ${String(result.error)}` : "";
845
+ throw new require_event_log.AgentError("policy-not-done", `policy '${id}' settled '${result.status}'${why}`, { cause: result.status === "error" ? result.error : void 0 });
846
+ }
847
+ const event = result.output;
848
+ if (!require_event_log.isRecord(event) || typeof event.type !== "string") throw new require_event_log.AgentError("policy-not-done", `policy '${id}' output is not an event: ${JSON.stringify(event)}`);
849
+ const { modelCalls, ...usage } = result.usage;
850
+ return {
851
+ event,
852
+ ...modelCalls > 0 ? { usage } : {}
853
+ };
854
+ };
855
+ async function policyThread(request, policyInput, info) {
856
+ if (threadId) return threadId(request, info);
857
+ if (info?.callKey) return `policy:${info.callKey}:${await sha256(require_event_log.canonicalEventJson(policyInput))}`;
858
+ throw new require_event_log.AgentError("missing-thread-id", `policy '${id}' has a store but no threadId, and the decision came with no callKey to derive one from`);
859
+ }
860
+ }
861
+ function defaultInput(request) {
862
+ const facts = request.metadata?.facts;
863
+ return withoutUndefinedKeys({
864
+ facts,
865
+ options: (request.options ?? []).map((option) => option.event),
866
+ system: request.system,
867
+ prompt: request.prompt
868
+ });
869
+ }
870
+ /** A copy of `value` with every object key whose value is `undefined` removed, at any depth. */
871
+ function withoutUndefinedKeys(value) {
872
+ if (Array.isArray(value)) return value.map(withoutUndefinedKeys);
873
+ if (!require_event_log.isRecord(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return value;
874
+ return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, withoutUndefinedKeys(child)]));
875
+ }
876
+ /** Hex SHA-256 of `text`, with Web Crypto (Node, browsers, workers). */
877
+ async function sha256(text) {
878
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
879
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
880
+ }
881
+ //#endregion
775
882
  //#region src/provide-executors.ts
776
883
  /**
777
884
  * Binds a machine's agent actor sources to a set of host `executors` in one
@@ -901,6 +1008,7 @@ exports.createAgentRuntime = require_run_agent.createAgentRuntime;
901
1008
  exports.createAgentSchemas = require_setup_agent.createAgentSchemas;
902
1009
  exports.createDecisionLogic = require_decision.createDecisionLogic;
903
1010
  exports.createDecisionRequest = require_decision.createDecisionRequest;
1011
+ exports.createPolicyDecider = createPolicyDecider;
904
1012
  exports.createTextLogic = require_event_log.createTextLogic;
905
1013
  exports.decideNext = decideNext;
906
1014
  exports.describeMachine = describeMachine;