@arnilo/prism 0.0.96 → 0.1.1

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 (203) hide show
  1. package/CHANGELOG.md +290 -2
  2. package/README.md +17 -3
  3. package/dist/agent-definitions.js +2 -3
  4. package/dist/agent-event-source.d.ts +11 -0
  5. package/dist/agent-event-source.js +512 -0
  6. package/dist/agent-loops.d.ts +5 -0
  7. package/dist/agent-loops.js +99 -14
  8. package/dist/agent-run-lifecycle.d.ts +5 -2
  9. package/dist/agent-run-lifecycle.js +18 -2
  10. package/dist/agent-run-state.d.ts +27 -1
  11. package/dist/agent-run-state.js +113 -7
  12. package/dist/agents.d.ts +3 -1
  13. package/dist/agents.js +1255 -129
  14. package/dist/artifacts.d.ts +132 -0
  15. package/dist/artifacts.js +44 -0
  16. package/dist/cache-helpers.js +18 -9
  17. package/dist/checkpoints.d.ts +4 -0
  18. package/dist/checkpoints.js +17 -9
  19. package/dist/cli-init.js +3 -7
  20. package/dist/cli-runner.d.ts +2 -6
  21. package/dist/cli-runner.js +71 -33
  22. package/dist/compaction.js +5 -4
  23. package/dist/config.js +7 -4
  24. package/dist/content.js +26 -24
  25. package/dist/context-budget.d.ts +67 -0
  26. package/dist/context-budget.js +288 -0
  27. package/dist/contracts.d.ts +590 -8
  28. package/dist/contracts.js +142 -1
  29. package/dist/contribution-parsing.js +6 -2
  30. package/dist/contributions.d.ts +2 -0
  31. package/dist/contributions.js +3 -0
  32. package/dist/conversations.d.ts +50 -0
  33. package/dist/conversations.js +98 -0
  34. package/dist/credentials.d.ts +22 -2
  35. package/dist/credentials.js +18 -3
  36. package/dist/devices.d.ts +94 -0
  37. package/dist/devices.js +138 -0
  38. package/dist/event-multiplexer.js +18 -4
  39. package/dist/extensions.d.ts +18 -1
  40. package/dist/extensions.js +79 -6
  41. package/dist/feedback.js +12 -10
  42. package/dist/guardrails.d.ts +1 -1
  43. package/dist/guardrails.js +26 -17
  44. package/dist/identity.d.ts +92 -0
  45. package/dist/identity.js +265 -0
  46. package/dist/index.d.ts +94 -72
  47. package/dist/index.js +48 -36
  48. package/dist/input.d.ts +10 -1
  49. package/dist/input.js +152 -52
  50. package/dist/instruction-injection.d.ts +1 -1
  51. package/dist/middleware.js +9 -1
  52. package/dist/models.d.ts +2 -0
  53. package/dist/models.js +3 -0
  54. package/dist/node/agent-definitions.js +16 -8
  55. package/dist/node/contribution-discovery.d.ts +1 -2
  56. package/dist/node/contribution-discovery.js +3 -3
  57. package/dist/node/session-store-jsonl.js +13 -7
  58. package/dist/node/settings.d.ts +1 -1
  59. package/dist/node/settings.js +1 -1
  60. package/dist/node/system-project-prompts.js +2 -4
  61. package/dist/node/trust.js +1 -1
  62. package/dist/persistence-lifecycle.d.ts +103 -0
  63. package/dist/persistence-lifecycle.js +202 -0
  64. package/dist/provider-events.d.ts +1 -0
  65. package/dist/provider-events.js +6 -1
  66. package/dist/provider-request-policy.js +3 -4
  67. package/dist/providers/media.d.ts +1 -1
  68. package/dist/providers/openai-compatible.d.ts +46 -1
  69. package/dist/providers/openai-compatible.js +123 -53
  70. package/dist/providers/openai-primitives.js +10 -7
  71. package/dist/providers/transport.d.ts +6 -0
  72. package/dist/providers/transport.js +21 -0
  73. package/dist/providers.d.ts +2 -0
  74. package/dist/providers.js +3 -0
  75. package/dist/redaction.d.ts +1 -0
  76. package/dist/redaction.js +26 -9
  77. package/dist/resources.d.ts +2 -2
  78. package/dist/resources.js +2 -2
  79. package/dist/retry.d.ts +5 -0
  80. package/dist/retry.js +8 -1
  81. package/dist/rpc.js +55 -11
  82. package/dist/run-ledger.d.ts +6 -0
  83. package/dist/run-ledger.js +16 -13
  84. package/dist/run-limits.js +49 -10
  85. package/dist/secure-agent.js +8 -2
  86. package/dist/security.js +7 -2
  87. package/dist/session-stores.d.ts +7 -2
  88. package/dist/session-stores.js +195 -21
  89. package/dist/skill-disclosure.d.ts +35 -0
  90. package/dist/skill-disclosure.js +101 -0
  91. package/dist/skill-load.d.ts +25 -0
  92. package/dist/skill-load.js +112 -0
  93. package/dist/structured-output.d.ts +5 -1
  94. package/dist/structured-output.js +20 -2
  95. package/dist/system-prompts.js +7 -2
  96. package/dist/testing/agent-event-source-conformance.d.ts +4 -0
  97. package/dist/testing/agent-event-source-conformance.js +54 -0
  98. package/dist/testing/compaction-conformance.js +5 -1
  99. package/dist/testing/extension-conformance.js +15 -3
  100. package/dist/testing/feedback.d.ts +1 -3
  101. package/dist/testing/feedback.js +1 -1
  102. package/dist/testing/persistence-schema.d.ts +2 -2
  103. package/dist/testing/persistence-schema.js +280 -35
  104. package/dist/testing/provider-conformance.js +3 -3
  105. package/dist/testing/run-ledger-conformance.js +1 -1
  106. package/dist/testing/session-store-conformance.d.ts +6 -0
  107. package/dist/testing/session-store-conformance.js +37 -2
  108. package/dist/testing/tool-conformance.js +30 -5
  109. package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
  110. package/dist/testing/tool-effect-store-conformance.js +85 -0
  111. package/dist/thinking.js +4 -1
  112. package/dist/tool-effects.d.ts +15 -0
  113. package/dist/tool-effects.js +352 -0
  114. package/dist/tool-result-fold.d.ts +40 -0
  115. package/dist/tool-result-fold.js +176 -0
  116. package/dist/tools.d.ts +8 -3
  117. package/dist/tools.js +248 -13
  118. package/docs/0.1.0-readiness.md +215 -0
  119. package/docs/a2a.md +33 -2
  120. package/docs/acp.md +152 -0
  121. package/docs/ag-ui-adoption.md +77 -0
  122. package/docs/ag-ui.md +225 -0
  123. package/docs/agent-events.md +34 -3
  124. package/docs/agent-identity.md +144 -0
  125. package/docs/agent-loops.md +17 -2
  126. package/docs/agent-session-runtime.md +21 -4
  127. package/docs/browser-automation.md +5 -0
  128. package/docs/caveman.md +129 -0
  129. package/docs/cli-rpc.md +3 -6
  130. package/docs/coding-agent-tools.md +229 -25
  131. package/docs/coding-security.md +77 -11
  132. package/docs/compaction-and-retry.md +5 -2
  133. package/docs/compaction-llm.md +20 -1
  134. package/docs/compaction-observational-memory.md +52 -8
  135. package/docs/context-and-skills.md +94 -7
  136. package/docs/contribution-registries.md +1 -0
  137. package/docs/conversations.md +135 -0
  138. package/docs/credential-storage.md +34 -1
  139. package/docs/credentials-and-redaction.md +11 -1
  140. package/docs/database-persistence.md +27 -7
  141. package/docs/device-adapters.md +97 -0
  142. package/docs/enterprise-postgres-state.md +178 -0
  143. package/docs/evaluations.md +14 -1
  144. package/docs/extensions.md +4 -1
  145. package/docs/forge-integration.md +113 -0
  146. package/docs/guardrails.md +16 -2
  147. package/docs/host-security.md +35 -4
  148. package/docs/index.md +69 -37
  149. package/docs/input-and-prompt-assembly.md +8 -7
  150. package/docs/language-intelligence.md +162 -0
  151. package/docs/mcp-tools.md +62 -5
  152. package/docs/middleware-hooks.md +2 -2
  153. package/docs/migration.md +427 -2
  154. package/docs/model-routing.md +111 -0
  155. package/docs/multimodal-content.md +8 -5
  156. package/docs/node-jsonl-session-store.md +1 -1
  157. package/docs/observability.md +2 -0
  158. package/docs/openapi-tools.md +56 -0
  159. package/docs/performance.md +282 -0
  160. package/docs/policy-and-audit.md +171 -0
  161. package/docs/ponytail.md +127 -0
  162. package/docs/postgres-persistence.md +8 -4
  163. package/docs/process-sessions.md +147 -0
  164. package/docs/provider-caching.md +13 -1
  165. package/docs/provider-conformance.md +29 -5
  166. package/docs/provider-packages.md +43 -2
  167. package/docs/provider-request-policies.md +2 -0
  168. package/docs/providers/ai-sdk.md +24 -7
  169. package/docs/providers/alibaba.md +179 -0
  170. package/docs/providers/anthropic.md +93 -0
  171. package/docs/providers/azure.md +74 -0
  172. package/docs/providers/bedrock.md +72 -0
  173. package/docs/providers/google.md +89 -0
  174. package/docs/providers/ollama.md +166 -0
  175. package/docs/providers/openai-compatible.md +31 -2
  176. package/docs/providers/openai.md +24 -5
  177. package/docs/providers/openrouter.md +2 -0
  178. package/docs/providers/vertex.md +71 -0
  179. package/docs/public-contracts.md +68 -4
  180. package/docs/rag.md +41 -12
  181. package/docs/release-and-install.md +362 -208
  182. package/docs/resource-loading.md +3 -0
  183. package/docs/runs-and-usage.md +3 -0
  184. package/docs/server.md +44 -6
  185. package/docs/session-store-conformance.md +2 -0
  186. package/docs/session-stores.md +41 -2
  187. package/docs/sqlite-persistence.md +11 -3
  188. package/docs/structured-output.md +7 -1
  189. package/docs/supervisors.md +8 -0
  190. package/docs/tool-effects.md +95 -0
  191. package/docs/tools.md +5 -0
  192. package/docs/work-artifacts-and-review.md +102 -0
  193. package/docs/work-connectors.md +32 -0
  194. package/docs/work-tools.md +137 -0
  195. package/docs/workflows.md +6 -0
  196. package/docs/working-and-semantic-memory.md +40 -7
  197. package/package.json +30 -7
  198. package/templates/init/providers.json +22 -0
  199. package/docs/review-coverage-2026-07-14.md +0 -260
  200. package/docs/review-coverage-2026-07-15.md +0 -193
  201. package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
  202. package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
  203. package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
@@ -1,5 +1,7 @@
1
- import { inputMessages } from "./input.js";
1
+ import { AgentLoopStateError } from "./contracts.js";
2
2
  import { createId } from "./ids.js";
3
+ import { inputMessages } from "./input.js";
4
+ import { artifactStructuredOutputRequest, withoutStructuredOutput } from "./structured-output.js";
3
5
  function throwIfAborted(signal) {
4
6
  if (signal.aborted)
5
7
  throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
@@ -7,7 +9,10 @@ function throwIfAborted(signal) {
7
9
  function toolResultMessage(result) {
8
10
  return {
9
11
  role: "tool",
10
- content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
12
+ content: [
13
+ { type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
14
+ ...(result.content ?? []),
15
+ ],
11
16
  metadata: result.metadata,
12
17
  };
13
18
  }
@@ -17,12 +22,15 @@ function toolResultMessage(result) {
17
22
  // fires artifact_* events here as a noop seam — single-shot emits zero.
18
23
  export const singleShotLoop = {
19
24
  name: "single-shot",
25
+ // Durable via the runtime's pending-call mechanism; no loop-local state to snapshot.
26
+ revision: "1",
20
27
  async run(ctx) {
21
28
  let usage;
22
29
  let toolRounds = 0;
23
30
  let nextInput = ctx.input;
24
31
  for (let turn = 1;; turn += 1) {
25
32
  throwIfAborted(ctx.signal);
33
+ await ctx.applyPendingSteers?.();
26
34
  ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
27
35
  const request = await ctx.assemble(nextInput, undefined, turn);
28
36
  throwIfAborted(ctx.signal);
@@ -37,10 +45,17 @@ export const singleShotLoop = {
37
45
  ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
38
46
  }
39
47
  ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
40
- if (calls.length === 0 || toolRounds >= ctx.maxToolRounds)
48
+ const dispatchable = dispatchableToolCalls(calls);
49
+ if (dispatchable.length === 0 || toolRounds >= ctx.maxToolRounds) {
50
+ // Soft-interrupt / late steer: keep same run going when queue still has text.
51
+ if (await ctx.applyPendingSteers?.()) {
52
+ nextInput = [];
53
+ continue;
54
+ }
41
55
  break;
56
+ }
42
57
  toolRounds += 1;
43
- await dispatchToolCallsInOrder(calls, ctx);
58
+ await dispatchToolCallsInOrder(dispatchable, ctx);
44
59
  nextInput = [];
45
60
  }
46
61
  return usage;
@@ -61,18 +76,56 @@ function defaultRepairer() {
61
76
  export function generateValidateReviseLoop(opts) {
62
77
  const max = opts.maxRevisions ?? 3;
63
78
  const repairer = opts.repairer ?? defaultRepairer();
79
+ const finalOnly = opts.structuredOutputTiming === "final-turn-only" && opts.toolCalls === "bounded";
80
+ // ponytail: per-run state hoisted to factory scope so snapshot/restore can capture it;
81
+ // resolveLoop invokes this factory once per run, so there is no cross-run leakage.
82
+ let attempts = 0;
83
+ let artifactPhase = !finalOnly;
84
+ let savedSchema;
85
+ let pendingHistory = [];
64
86
  return {
65
87
  name: "generate-validate-revise",
88
+ revision: "1",
89
+ snapshot() {
90
+ return {
91
+ attempts,
92
+ artifactPhase,
93
+ savedSchema: savedSchema ?? null,
94
+ pendingHistory,
95
+ };
96
+ },
97
+ restore(snapshot) {
98
+ const state = snapshot;
99
+ if (typeof state.attempts !== "number" || !Number.isInteger(state.attempts) || typeof state.artifactPhase !== "boolean") {
100
+ throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "generate-validate-revise snapshot drift");
101
+ }
102
+ attempts = state.attempts;
103
+ artifactPhase = state.artifactPhase;
104
+ savedSchema = (state.savedSchema ?? undefined);
105
+ // Repair messages were appended to the session before suspension, so the rebuilt
106
+ // history already carries them; re-applying pendingHistory would duplicate them.
107
+ pendingHistory = [];
108
+ },
66
109
  async run(ctx) {
67
110
  let usage;
68
111
  let nextInput = ctx.input;
69
- let pendingHistory = [];
70
112
  let toolRounds = 0;
71
- let attempts = 0;
72
113
  for (let turn = 1; attempts <= max; turn += 1) {
73
114
  throwIfAborted(ctx.signal);
115
+ await ctx.applyPendingSteers?.();
74
116
  ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
75
- const request = await ctx.assemble(nextInput, undefined, turn);
117
+ let request = await ctx.assemble(nextInput, undefined, turn);
118
+ if (request.options?.structuredOutput)
119
+ savedSchema ??= request.options.structuredOutput;
120
+ if (finalOnly) {
121
+ if (!artifactPhase && toolRounds < ctx.maxToolRounds) {
122
+ request = withoutStructuredOutput(request);
123
+ }
124
+ else {
125
+ artifactPhase = true;
126
+ request = artifactStructuredOutputRequest(request, savedSchema);
127
+ }
128
+ }
76
129
  throwIfAborted(ctx.signal);
77
130
  const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
78
131
  usage = turnUsage ?? usage;
@@ -90,13 +143,34 @@ export function generateValidateReviseLoop(opts) {
90
143
  }
91
144
  ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
92
145
  if (opts.toolCalls === "bounded" && calls.length > 0) {
146
+ const dispatchable = dispatchableToolCalls(calls);
147
+ if (dispatchable.length === 0) {
148
+ // Only provider-hosted calls; no host tool to run. Continue without charging a round.
149
+ nextInput = [];
150
+ continue;
151
+ }
93
152
  if (toolRounds >= ctx.maxToolRounds) {
94
- const result = { ok: false, errors: [{ message: "maximum tool rounds exceeded" }], metadata: { reason: "tool_round_limit" } };
153
+ const result = {
154
+ ok: false,
155
+ errors: [{ message: "maximum tool rounds exceeded" }],
156
+ metadata: { reason: "tool_round_limit" },
157
+ };
95
158
  ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
96
159
  return usage;
97
160
  }
98
161
  toolRounds += 1;
99
- await dispatchToolCallsInOrder(calls, { ...ctx, toolConcurrency: 1 });
162
+ await dispatchToolCallsInOrder(dispatchable, { ...ctx, toolConcurrency: 1 });
163
+ nextInput = [];
164
+ continue;
165
+ }
166
+ // Soft-interrupt / late steer before treating empty/final output as artifact work.
167
+ if (await ctx.applyPendingSteers?.()) {
168
+ nextInput = [];
169
+ continue;
170
+ }
171
+ // Call-free during tool phase → one more turn with schema on / tools off.
172
+ if (finalOnly && !artifactPhase) {
173
+ artifactPhase = true;
100
174
  nextInput = [];
101
175
  continue;
102
176
  }
@@ -125,7 +199,7 @@ export function generateValidateReviseLoop(opts) {
125
199
  : undefined;
126
200
  const attempt = ++attempts;
127
201
  ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
128
- const result = parseFailure ?? await opts.validator(parsed.value, artifactCtx);
202
+ const result = parseFailure ?? (await opts.validator(parsed.value, artifactCtx));
129
203
  ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
130
204
  if (result.ok) {
131
205
  ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
@@ -161,14 +235,18 @@ export function resolveToolConcurrency(options, config) {
161
235
  return 1;
162
236
  return Math.floor(value);
163
237
  }
238
+ /** Calls the host must dispatch. Provider-hosted calls (`authority: "provider-hosted"`)
239
+ * were already executed server-side; the assistant response text carries their effect, so
240
+ * the host neither dispatches them nor appends a `tool_result`. */
241
+ export function dispatchableToolCalls(calls) {
242
+ return calls.filter((call) => call.authority !== "provider-hosted");
243
+ }
164
244
  /** Dispatch tool calls with bounded concurrency; append transcript rows in call order. */
165
245
  export async function dispatchToolCallsInOrder(calls, ctx) {
166
246
  if (calls.length === 0)
167
247
  return;
168
- ctx.chargeToolRound?.(calls);
169
- const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
170
- ? 1
171
- : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
248
+ await ctx.chargeToolRound?.(calls);
249
+ const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call)) ? 1 : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
172
250
  if (concurrency === 1) {
173
251
  for (const call of calls) {
174
252
  const result = await ctx.dispatchToolCall(call);
@@ -191,10 +269,16 @@ export async function dispatchToolCallsInOrder(calls, ctx) {
191
269
  await Promise.all(workers);
192
270
  for (const result of results) {
193
271
  throwIfAborted(ctx.signal);
272
+ if (!result)
273
+ continue;
194
274
  await appendToolResultMessage(result, ctx);
195
275
  }
196
276
  }
197
277
  async function appendToolResultMessage(result, ctx) {
278
+ // Approval-gated calls return a marker instead of a real result; the transcript must not
279
+ // record a phantom tool_result for a call that never dispatched.
280
+ if (result.metadata?.approvalPending === true)
281
+ return;
198
282
  const message = toolResultMessage(result);
199
283
  ctx.history.push(message);
200
284
  await ctx.appendMessage(message);
@@ -214,6 +298,7 @@ export function resolveLoop(options, config) {
214
298
  repairer: loop.repairer,
215
299
  maxRevisions: loop.maxRevisions,
216
300
  toolCalls: loop.toolCalls,
301
+ structuredOutputTiming: loop.structuredOutputTiming,
217
302
  });
218
303
  }
219
304
  throw new Error(`Unknown agent loop strategy: ${strategy}`);
@@ -1,5 +1,4 @@
1
- import type { Agent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope } from "./contracts.js";
2
- import type { CheckpointStore } from "./contracts.js";
1
+ import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
3
2
  export interface AgentRunLifecycleAgent {
4
3
  readonly agent: Agent;
5
4
  /** Current host-authored revision; it must match the stored revision. */
@@ -20,9 +19,13 @@ export interface AgentRunLifecycleRequest {
20
19
  /** Adapter-selected capability; stored runs for another agent are non-enumerable. */
21
20
  readonly agentId?: string;
22
21
  }
22
+ /** Bounded live-event options for a durable lifecycle resume. */
23
+ export interface AgentRunLifecycleStreamRequest extends AgentRunLifecycleRequest, SubscribeOptions {
24
+ }
23
25
  export interface AgentRunLifecycle {
24
26
  status(ref: AgentRunRef, options?: AgentRunLifecycleRequest): Promise<AgentRunStatusResult>;
25
27
  resume(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleRequest): Promise<AgentRunResult>;
28
+ resumeStream(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleStreamRequest): AsyncIterable<AgentEvent>;
26
29
  }
27
30
  /** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
28
31
  export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
@@ -1,6 +1,6 @@
1
- import { AgentRunStateError } from "./contracts.js";
2
1
  import { loadAgentRunState, publicState } from "./agent-run-state.js";
3
- import { resumeAgentRun } from "./agents.js";
2
+ import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
3
+ import { AgentRunStateError } from "./contracts.js";
4
4
  function assertAgentId(actual, expected) {
5
5
  if (expected !== undefined && actual !== expected)
6
6
  throw new AgentRunStateError("Agent run capability mismatch");
@@ -28,6 +28,22 @@ export function createAgentRunLifecycle(options) {
28
28
  definitionRevision: resolved.definitionRevision,
29
29
  });
30
30
  },
31
+ async *resumeStream(ref, resume, request = {}) {
32
+ request.signal?.throwIfAborted();
33
+ const { state } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
34
+ assertAgentId(state.agentId, request.agentId);
35
+ const resolved = await options.resolveAgent({ agentId: state.agentId, ownership: request.ownership, signal: request.signal });
36
+ request.signal?.throwIfAborted();
37
+ yield* resumeAgentRunStream(resolved.agent, ref, resume, {
38
+ checkpoints: options.checkpoints,
39
+ ownership: request.ownership,
40
+ fencingToken: options.fencingToken,
41
+ definitionRevision: resolved.definitionRevision,
42
+ signal: request.signal,
43
+ maxQueuedEvents: request.maxQueuedEvents,
44
+ overflow: request.overflow,
45
+ });
46
+ },
31
47
  };
32
48
  }
33
49
  //# sourceMappingURL=agent-run-lifecycle.js.map
@@ -1,19 +1,44 @@
1
- import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, Message, ModelConfig, OwnershipScope, RunLimitCounters, ToolCallContent } from "./contracts.js";
1
+ import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
2
2
  import type { SecretRedactor } from "./redaction.js";
3
3
  export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
4
  export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
5
5
  export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
6
6
  export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
7
+ /** One gated tool call awaiting or holding a decision inside a suspended durable run. */
8
+ export interface PendingToolCall {
9
+ readonly call: ToolCallContent;
10
+ readonly status: "ready" | "dispatched";
11
+ readonly approvalId: string;
12
+ /** Decision persisted by a partial batch; applied when the run finally resumes. */
13
+ readonly decision?: RunDecision;
14
+ }
7
15
  export interface StoredAgentRunState extends AgentRunState {
8
16
  readonly input?: readonly Message[];
17
+ /** Legacy single gated call (pre-0.0.25 checkpoints). New states write `pendingCalls`. */
9
18
  readonly pending?: {
10
19
  readonly call: ToolCallContent;
11
20
  readonly status: "ready" | "dispatched";
12
21
  };
22
+ /** Gated calls of the current suspension, in provider-turn order. */
23
+ readonly pendingCalls?: readonly PendingToolCall[];
24
+ /** Suspended nested runs (supervisor children) whose pending decisions surface at this root. */
25
+ readonly nestedRuns?: readonly NestedRunRef[];
26
+ /** Run-scoped sticky decisions; exact scope match, dropped at any terminal status. */
27
+ readonly stickyDecisions?: readonly StickyDecision[];
13
28
  readonly interruptBeforeTool?: boolean;
14
29
  readonly counters: RunLimitCounters;
15
30
  readonly deadlineAt: string;
31
+ /** Loop-local durable state captured by the strategy's snapshot hook at suspension. */
32
+ readonly loopState?: {
33
+ readonly name: string;
34
+ readonly revision: string;
35
+ readonly snapshot: JsonValue;
36
+ };
16
37
  }
38
+ /** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
39
+ export declare const BUILT_IN_LOOP_REVISIONS: Readonly<Record<string, string>>;
40
+ /** Validate a strategy snapshot as JSON-compatible and package it for the durable envelope. */
41
+ export declare function boundedLoopSnapshot(name: string, revision: string, snapshot: JsonValue): StoredAgentRunState["loopState"];
17
42
  export declare function agentFingerprint(agent: Agent, revision: string): string;
18
43
  export declare function agentId(agent: Agent): string;
19
44
  export declare function validateRunStateOptions(options: AgentRunStateOptions): void;
@@ -48,6 +73,7 @@ export declare function initialAgentRunState(input: {
48
73
  readonly interruption?: AgentRunInterruption;
49
74
  readonly messages?: readonly Message[];
50
75
  readonly pending?: StoredAgentRunState["pending"];
76
+ readonly pendingCalls?: StoredAgentRunState["pendingCalls"];
51
77
  readonly interruptBeforeTool?: boolean;
52
78
  }): StoredAgentRunState;
53
79
  export declare function parseAgentRunState(value: unknown, version?: number): StoredAgentRunState;
@@ -1,27 +1,83 @@
1
1
  import { createHash } from "node:crypto";
2
- import { AgentRunStateError } from "./contracts.js";
2
+ import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
3
3
  export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
4
  export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
5
5
  export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
6
6
  export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
7
7
  const MAX_DEPTH = 32;
8
8
  const MAX_PROPERTIES = 256;
9
+ /** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
10
+ export const BUILT_IN_LOOP_REVISIONS = {
11
+ "single-shot": "1",
12
+ "generate-validate-revise": "1",
13
+ };
14
+ /** Validate a strategy snapshot as JSON-compatible and package it for the durable envelope. */
15
+ export function boundedLoopSnapshot(name, revision, snapshot) {
16
+ try {
17
+ assertJsonValue(snapshot, 0);
18
+ }
19
+ catch (error) {
20
+ if (error instanceof AgentLoopStateError)
21
+ throw error;
22
+ throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot must be JSON-compatible", { cause: error });
23
+ }
24
+ return { name, revision, snapshot };
25
+ }
26
+ function assertJsonValue(value, depth) {
27
+ if (depth > MAX_DEPTH)
28
+ throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", `Loop snapshot exceeds depth ${MAX_DEPTH}`);
29
+ switch (typeof value) {
30
+ case "string":
31
+ case "boolean":
32
+ return;
33
+ case "number":
34
+ if (!Number.isFinite(value))
35
+ throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot numbers must be finite");
36
+ return;
37
+ case "object": {
38
+ if (value === null)
39
+ return;
40
+ for (const item of Object.values(value))
41
+ assertJsonValue(item, depth + 1);
42
+ return;
43
+ }
44
+ default:
45
+ throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot must be JSON-compatible");
46
+ }
47
+ }
9
48
  export function agentFingerprint(agent, revision) {
10
49
  const config = agent.config;
11
50
  const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
51
+ const skills = !config.skills ? [] : "list" in config.skills ? config.skills.list() : config.skills;
12
52
  const guardrails = [
13
53
  ...(config.guardrails?.input ?? []),
14
54
  ...(config.guardrails?.output ?? []),
15
55
  ...(config.guardrails?.toolInput ?? []),
16
56
  ...(config.guardrails?.toolOutput ?? []),
17
57
  ];
58
+ const systemPrompt = config.systemPrompt === false || config.systemPrompt === undefined
59
+ ? (config.systemPrompt ?? null)
60
+ : (Array.isArray(config.systemPrompt) ? config.systemPrompt : [config.systemPrompt]).map((c) => ({ id: c.id, text: c.text }));
18
61
  const value = JSON.stringify({
19
62
  id: config.id ?? config.name ?? "agent",
20
63
  revision,
21
64
  model: config.model,
22
- tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
65
+ // Instructions/prompt text shapes agent behavior as much as the tool set; a change
66
+ // without a definitionRevision bump must not resume stale durable runs silently.
67
+ instructions: config.instructions ?? null,
68
+ systemPrompt,
69
+ skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
70
+ tools: tools.map((tool) => ({
71
+ name: tool.name,
72
+ parameters: tool.parameters,
73
+ exclusive: tool.exclusive,
74
+ effect: typeof tool.effect === "function" ? "classifier" : tool.effect,
75
+ })),
23
76
  guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
24
- loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop ? config.loop.strategy : config.loop?.name ?? "single-shot",
77
+ // Loop revision participates so a loop change without a definitionRevision bump fails closed.
78
+ loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
79
+ ? { name: config.loop.strategy, revision: BUILT_IN_LOOP_REVISIONS[config.loop.strategy] ?? null }
80
+ : { name: config.loop?.name ?? "single-shot", revision: config.loop?.revision ?? BUILT_IN_LOOP_REVISIONS["single-shot"] },
25
81
  });
26
82
  return createHash("sha256").update(value).digest("hex");
27
83
  }
@@ -43,7 +99,10 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
43
99
  const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
44
100
  if (!record)
45
101
  throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
46
- if (ref.sessionId && record.value && typeof record.value === "object" && record.value.sessionId !== ref.sessionId) {
102
+ if (ref.sessionId &&
103
+ record.value &&
104
+ typeof record.value === "object" &&
105
+ record.value.sessionId !== ref.sessionId) {
47
106
  throw new AgentRunStateError("Agent run session mismatch");
48
107
  }
49
108
  return { record, state: parseAgentRunState(record.value, record.version) };
@@ -66,7 +125,7 @@ export function statusFromState(state, version) {
66
125
  return { state: publicState({ ...state, version }), version };
67
126
  }
68
127
  export function publicState(state) {
69
- const { input: _input, pending: _pending, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
128
+ const { input: _input, pending: _pending, pendingCalls: _pendingCalls, nestedRuns: _nestedRuns, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
70
129
  return publicValue;
71
130
  }
72
131
  export function initialAgentRunState(input) {
@@ -84,6 +143,7 @@ export function initialAgentRunState(input) {
84
143
  interruption: input.interruption,
85
144
  input: input.messages,
86
145
  pending: input.pending,
146
+ pendingCalls: input.pendingCalls,
87
147
  interruptBeforeTool: input.interruptBeforeTool,
88
148
  counters: input.counters,
89
149
  deadlineAt: input.deadlineAt,
@@ -95,10 +155,56 @@ export function parseAgentRunState(value, version) {
95
155
  const state = value;
96
156
  if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
97
157
  throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
98
- if (!state.agentId || !state.definitionRevision || !state.fingerprint || !state.runId || !state.sessionId || !state.model || !state.status || !state.counters || !state.deadlineAt) {
158
+ if (!state.agentId ||
159
+ !state.definitionRevision ||
160
+ !state.fingerprint ||
161
+ !state.runId ||
162
+ !state.sessionId ||
163
+ !state.model ||
164
+ !state.status ||
165
+ !state.counters ||
166
+ !state.deadlineAt) {
99
167
  throw new AgentRunStateError("Malformed agent run state");
100
168
  }
101
- return boundState({ ...state, version }, DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
169
+ if (state.pendingCalls !== undefined &&
170
+ (!Array.isArray(state.pendingCalls) ||
171
+ state.pendingCalls.some((entry) => !entry ||
172
+ typeof entry !== "object" ||
173
+ !entry.call ||
174
+ typeof entry.approvalId !== "string" ||
175
+ (entry.status !== "ready" && entry.status !== "dispatched")))) {
176
+ throw new AgentRunStateError("Malformed agent run pending calls");
177
+ }
178
+ if (state.stickyDecisions !== undefined &&
179
+ (!Array.isArray(state.stickyDecisions) ||
180
+ state.stickyDecisions.some((entry) => !entry ||
181
+ typeof entry !== "object" ||
182
+ !entry.scope ||
183
+ (entry.outcome !== "allow_for_run" && entry.outcome !== "reject_for_run")))) {
184
+ throw new AgentRunStateError("Malformed agent run sticky decisions");
185
+ }
186
+ if (state.nestedRuns !== undefined &&
187
+ (!Array.isArray(state.nestedRuns) ||
188
+ state.nestedRuns.some((entry) => !entry ||
189
+ typeof entry !== "object" ||
190
+ typeof entry.runId !== "string" ||
191
+ typeof entry.toolCallId !== "string" ||
192
+ !Array.isArray(entry.path) ||
193
+ !Array.isArray(entry.approvals) ||
194
+ entry.approvals.some((approval) => typeof approval?.id !== "string" || typeof approval?.childApprovalId !== "string")))) {
195
+ throw new AgentRunStateError("Malformed agent run nested runs");
196
+ }
197
+ if (state.loopState !== undefined &&
198
+ (typeof state.loopState !== "object" ||
199
+ typeof state.loopState.name !== "string" ||
200
+ typeof state.loopState.revision !== "string" ||
201
+ !("snapshot" in state.loopState))) {
202
+ throw new AgentRunStateError("Malformed agent run loop state");
203
+ }
204
+ // Load bounds against the hard cap, not the default: the configured maxStateBytes is a
205
+ // save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
206
+ // with a raised maxStateBytes must remain resumable.
207
+ return boundState({ ...state, version }, HARD_MAX_AGENT_RUN_STATE_BYTES);
102
208
  }
103
209
  function boundState(state, maxBytes) {
104
210
  checkShape(state, 0);
package/dist/agents.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { Agent, AgentConfig, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunRef, AgentSession, AgentSessionConfig } from "./contracts.js";
1
+ import type { Agent, AgentConfig, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentSession, AgentSessionConfig } from "./contracts.js";
2
2
  export declare function createAgent(config: AgentConfig): Agent;
3
3
  export declare function createAgentSession(config: AgentSessionConfig & {
4
4
  readonly agent: Agent;
5
5
  }): AgentSession;
6
6
  /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
7
7
  export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;
8
+ /** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
9
+ export declare function resumeAgentRunStream(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeStreamOptions): AsyncGenerator<AgentEvent>;