@arnilo/prism 0.0.96 → 0.1.0

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 +285 -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 +202 -0
  119. package/docs/a2a.md +33 -2
  120. package/docs/acp.md +126 -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 +423 -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 +61 -4
  180. package/docs/rag.md +41 -12
  181. package/docs/release-and-install.md +323 -206
  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 -8
  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
package/dist/input.js CHANGED
@@ -1,26 +1,19 @@
1
- import { redactMessage } from "./redaction.js";
2
1
  import { assertMessagesSupportModelCapabilities } from "./content.js";
2
+ import { applyContextBudget, CONTEXT_BUDGET_REPORT_METADATA_KEY } from "./context-budget.js";
3
+ import { runInstructionInjectors } from "./instruction-injection.js";
4
+ import { redactMessage } from "./redaction.js";
3
5
  import { loadTextResource } from "./resources.js";
6
+ import { skillMessages as buildSkillMessages } from "./skill-disclosure.js";
7
+ import { foldToolResultHistory, foldToolResults } from "./tool-result-fold.js";
4
8
  import { composeSystemPrompt } from "./system-prompts.js";
5
- import { runInstructionInjectors } from "./instruction-injection.js";
6
9
  export function createDefaultInputBuilder() {
7
10
  return {
8
11
  name: "default-input",
9
12
  async build(input, context = {}) {
10
- const groups = {
11
- instructions: [
12
- ...instructionMessages(context.systemInstructions, "System instruction"),
13
- ...instructionMessages(context.developerInstructions, "Developer instruction"),
14
- ...customInstructionMessages(context.instructions),
15
- ],
16
- summaries: summaryMessages(context.summaries),
17
- history: [...(context.history ?? [])],
18
- input: inputMessages(input),
19
- attachments: await attachmentMessages(context),
20
- toolResults: toolResultMessages(context.toolResults),
21
- };
22
- const messages = flattenInputGroups(groups, context.inputLayout ?? "legacy");
23
- return context.middleware ? context.middleware.run("input_assembly", messages) : messages;
13
+ const groups = await buildDefaultInputMessageGroups(input, context);
14
+ // `input_assembly` middleware is applied by assembleProviderInput after build(),
15
+ // never here: builders must not be a security seam.
16
+ return flattenInputGroups(groups, context.inputLayout ?? "cache_aware");
24
17
  },
25
18
  };
26
19
  }
@@ -28,13 +21,13 @@ export async function resolveContextProviders(options) {
28
21
  const blocks = [];
29
22
  for (const provider of options.providers ?? []) {
30
23
  throwIfAborted(options.signal);
31
- blocks.push(...await provider.resolve({
24
+ blocks.push(...(await provider.resolve({
32
25
  sessionId: options.sessionId,
33
26
  runId: options.runId,
34
27
  messages: options.messages,
35
28
  metadata: options.metadata,
36
29
  signal: options.signal,
37
- }));
30
+ })));
38
31
  }
39
32
  // ponytail: injector blocks after host+skill provider blocks (split by origin if per-block ordering matters).
40
33
  if (options.injectedBlocks)
@@ -45,10 +38,17 @@ export function createDefaultPromptBuilder() {
45
38
  return {
46
39
  name: "default-prompt",
47
40
  async build(request) {
41
+ // Tool-capable models receive schemas via request.tools; the text list only serves
42
+ // text-only (or unknown-capability) models — duplicating it doubles tool tokens per turn.
43
+ const tools = request.model?.capabilities?.tools === true ? undefined : request.tools;
48
44
  return [
49
45
  ...contextMessages(request.context),
50
- ...skillMessages(request.skills),
51
- ...toolMessages(request.tools),
46
+ ...buildSkillMessages(request.skills, {
47
+ disclosure: request.skillsDisclosure,
48
+ loaded: request.loadedSkills,
49
+ demotedBodies: request.demotedSkillBodies?.length ? new Set(request.demotedSkillBodies) : undefined,
50
+ }),
51
+ ...toolMessages(tools),
52
52
  ...request.messages,
53
53
  ];
54
54
  },
@@ -56,7 +56,7 @@ export function createDefaultPromptBuilder() {
56
56
  }
57
57
  export function renderPromptTemplate(template, variables, options = {}) {
58
58
  return template.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (match, name) => {
59
- if (!Object.prototype.hasOwnProperty.call(variables, name)) {
59
+ if (!Object.hasOwn(variables, name)) {
60
60
  if (options.missing === "preserve")
61
61
  return match;
62
62
  throw new Error(`Missing prompt template variable: ${name}`);
@@ -65,7 +65,6 @@ export function renderPromptTemplate(template, variables, options = {}) {
65
65
  });
66
66
  }
67
67
  export async function assembleProviderInput(options) {
68
- const inputBuilder = options.inputBuilder ?? createDefaultInputBuilder();
69
68
  const baseContext = {
70
69
  sessionId: options.sessionId,
71
70
  runId: options.runId,
@@ -76,6 +75,18 @@ export async function assembleProviderInput(options) {
76
75
  // filter against loop-local turn. Instructions layer via composeSystemPrompt (no parallel prompt
77
76
  // code); contextBlocks merge via resolveContextProviders (middleware still runs).
78
77
  const turn = options.turn ?? 1;
78
+ const foldContext = {
79
+ sessionId: options.sessionId ?? "",
80
+ runId: options.runId ?? "",
81
+ turn,
82
+ signal: options.signal,
83
+ };
84
+ const foldedHistory = options.toolResultFold
85
+ ? await foldToolResultHistory(options.history ?? [], options.toolResultFold, foldContext)
86
+ : (options.history ?? []);
87
+ const foldedToolResults = options.toolResultFold && options.toolResults?.length
88
+ ? await foldToolResults(options.toolResults, options.toolResultFold, foldContext)
89
+ : options.toolResults;
79
90
  const injectorContribs = options.instructionInjectors?.length
80
91
  ? runInstructionInjectors(options.instructionInjectors, {
81
92
  sessionId: options.sessionId ?? "",
@@ -83,7 +94,7 @@ export async function assembleProviderInput(options) {
83
94
  turn,
84
95
  // Runtime history is already redacted; input is redacted here before injector code sees it.
85
96
  input: inputMessages(options.input).map((message) => redactMessage(message, options.redactor)),
86
- history: options.history ?? [],
97
+ history: foldedHistory,
87
98
  metadata: options.metadata ?? {},
88
99
  signal: options.signal ?? new AbortController().signal,
89
100
  })
@@ -91,51 +102,136 @@ export async function assembleProviderInput(options) {
91
102
  const systemInstructions = injectorContribs.instructions.length
92
103
  ? composeSystemPrompt(injectorContribs.instructions, { base: options.systemInstructions })
93
104
  : options.systemInstructions;
94
- const buildContext = { ...options, ...baseContext, systemInstructions };
95
- const messages = await inputBuilder.build(options.input, buildContext);
96
- const context = await resolveContextProviders({
97
- providers: options.contextProviders,
98
- messages,
99
- injectedBlocks: injectorContribs.contextBlocks.length ? injectorContribs.contextBlocks : undefined,
100
- middleware: options.middleware,
105
+ const buildContext = {
106
+ ...options,
101
107
  ...baseContext,
102
- });
108
+ systemInstructions,
109
+ history: foldedHistory,
110
+ toolResults: foldedToolResults,
111
+ };
112
+ const layout = buildContext.inputLayout ?? "cache_aware";
113
+ let messages;
114
+ let context;
115
+ let skills = options.skills;
116
+ let tools = options.tools;
117
+ let demotedSkillBodies;
118
+ let budgetReport;
119
+ if (options.contextBudget) {
120
+ // ponytail: budget path always uses default groups so eviction kinds stay deterministic;
121
+ // custom inputBuilder still honored when contextBudget is absent.
122
+ const groups = await buildDefaultInputMessageGroups(options.input, buildContext);
123
+ context = await resolveContextProviders({
124
+ providers: options.contextProviders,
125
+ messages: flattenInputGroups(groups, layout),
126
+ injectedBlocks: injectorContribs.contextBlocks.length ? injectorContribs.contextBlocks : undefined,
127
+ middleware: options.middleware,
128
+ ...baseContext,
129
+ });
130
+ const applied = applyContextBudget({
131
+ groups,
132
+ context,
133
+ skills: skills ?? [],
134
+ tools,
135
+ budget: options.contextBudget,
136
+ layout,
137
+ skillsDisclosure: options.skillsDisclosure,
138
+ loadedSkills: options.loadedSkills,
139
+ });
140
+ messages = flattenInputGroups(applied.groups, layout);
141
+ if (buildContext.middleware)
142
+ messages = await buildContext.middleware.run("input_assembly", messages);
143
+ context = applied.context;
144
+ skills = applied.skills;
145
+ tools = applied.tools;
146
+ demotedSkillBodies = applied.demotedSkillBodies;
147
+ if (options.contextBudget.reportOmissions)
148
+ budgetReport = applied.report;
149
+ }
150
+ else {
151
+ const inputBuilder = options.inputBuilder ?? createDefaultInputBuilder();
152
+ messages = await inputBuilder.build(options.input, buildContext);
153
+ // Runtime-owned: input_assembly middleware always runs, regardless of which builder is installed.
154
+ if (buildContext.middleware)
155
+ messages = await buildContext.middleware.run("input_assembly", messages);
156
+ context = await resolveContextProviders({
157
+ providers: options.contextProviders,
158
+ messages,
159
+ injectedBlocks: injectorContribs.contextBlocks.length ? injectorContribs.contextBlocks : undefined,
160
+ middleware: options.middleware,
161
+ ...baseContext,
162
+ });
163
+ }
103
164
  const promptBuilder = options.promptBuilder ?? createDefaultPromptBuilder();
104
165
  const promptRequest = options.middleware
105
166
  ? await options.middleware.run("prompt_build", {
106
167
  messages,
107
168
  context,
108
- skills: options.skills,
109
- tools: options.tools,
169
+ skills,
170
+ skillsDisclosure: options.skillsDisclosure,
171
+ loadedSkills: options.loadedSkills,
172
+ demotedSkillBodies,
173
+ tools,
110
174
  metadata: options.metadata,
111
175
  signal: options.signal,
112
176
  })
113
177
  : {
114
178
  messages,
115
179
  context,
116
- skills: options.skills,
117
- tools: options.tools,
180
+ skills,
181
+ skillsDisclosure: options.skillsDisclosure,
182
+ loadedSkills: options.loadedSkills,
183
+ demotedSkillBodies,
184
+ tools,
118
185
  metadata: options.metadata,
119
186
  signal: options.signal,
120
187
  };
121
- const providerMessages = await promptBuilder.build({ ...promptRequest, tools: options.tools });
188
+ const providerMessages = await promptBuilder.build({ ...promptRequest, tools, model: options.model });
122
189
  assertMessagesSupportModelCapabilities(options.model, providerMessages);
190
+ const metadata = budgetReport ? { ...options.metadata, [CONTEXT_BUDGET_REPORT_METADATA_KEY]: budgetReport } : options.metadata;
123
191
  return {
124
192
  model: options.model,
125
193
  messages: providerMessages,
126
- tools: options.tools,
194
+ tools,
127
195
  context,
128
196
  options: options.providerOptions,
129
- metadata: options.metadata,
197
+ metadata,
130
198
  signal: options.signal,
131
199
  };
132
200
  }
201
+ async function buildDefaultInputMessageGroups(input, context) {
202
+ return {
203
+ instructions: [
204
+ ...instructionMessages(context.systemInstructions, "System instruction"),
205
+ ...instructionMessages(context.developerInstructions, "Developer instruction"),
206
+ ...customInstructionMessages(context.instructions),
207
+ ],
208
+ summaries: summaryMessages(context.summaries),
209
+ history: [...(context.history ?? [])],
210
+ input: inputMessages(input),
211
+ attachments: await attachmentMessages(context),
212
+ toolResults: toolResultMessages(context.toolResults),
213
+ };
214
+ }
133
215
  function flattenInputGroups(groups, layout) {
134
216
  switch (layout) {
135
217
  case "cache_aware":
136
- return [...groups.instructions, ...groups.attachments, ...groups.summaries, ...groups.history, ...groups.toolResults, ...groups.input];
218
+ return [
219
+ ...groups.instructions,
220
+ ...groups.attachments,
221
+ ...groups.summaries,
222
+ ...groups.history,
223
+ ...groups.toolResults,
224
+ ...groups.input,
225
+ ];
137
226
  case "legacy":
138
- return [...groups.instructions, ...groups.summaries, ...groups.history, ...groups.input, ...groups.attachments, ...groups.toolResults];
227
+ return [
228
+ ...groups.instructions,
229
+ ...groups.summaries,
230
+ ...groups.history,
231
+ ...groups.input,
232
+ ...groups.attachments,
233
+ ...groups.toolResults,
234
+ ];
139
235
  }
140
236
  }
141
237
  export function inputMessages(input) {
@@ -146,7 +242,7 @@ export function inputMessages(input) {
146
242
  return [...input];
147
243
  }
148
244
  function instructionMessages(value, label) {
149
- const items = typeof value === "string" ? [value] : value ?? [];
245
+ const items = typeof value === "string" ? [value] : (value ?? []);
150
246
  return items.map((text) => textMessage("system", `${label}:\n${text}`));
151
247
  }
152
248
  function customInstructionMessages(value) {
@@ -198,13 +294,16 @@ async function resourceMessage(uri, context, attachment) {
198
294
  function toolResultMessages(results) {
199
295
  return (results ?? []).map((result) => ({
200
296
  role: "tool",
201
- content: [{
297
+ content: [
298
+ {
202
299
  type: "tool_result",
203
300
  toolCallId: result.toolCallId,
204
301
  name: result.name,
205
302
  result: result.value,
206
303
  error: result.error,
207
- }, ...(result.content ?? [])],
304
+ },
305
+ ...(result.content ?? []),
306
+ ],
208
307
  metadata: result.metadata,
209
308
  }));
210
309
  }
@@ -214,20 +313,18 @@ function textMessage(role, text, metadata) {
214
313
  function contextMessages(context) {
215
314
  return (context ?? []).map((block) => textMessage("system", `${block.title ? `${block.title}:\n` : "Context:\n"}${blockText(block)}`, block.metadata));
216
315
  }
217
- function skillMessages(skills) {
218
- return (skills ?? [])
219
- .filter((skill) => skill.instructions)
220
- .map((skill) => textMessage("system", `Skill ${skill.name}:\n${skill.instructions}`, skill.metadata));
221
- }
222
316
  function toolMessages(tools) {
223
317
  if (!tools?.length)
224
318
  return [];
225
- return [textMessage("system", `Available tools:\n${tools.map((tool) => `- ${tool.name}${tool.description ? `: ${tool.description}` : ""}`).join("\n")}`)];
319
+ return [
320
+ textMessage("system", `Available tools:\n${tools.map((tool) => `- ${tool.name}${tool.description ? `: ${tool.description}` : ""}`).join("\n")}`),
321
+ ];
226
322
  }
227
323
  function blockText(block) {
228
324
  if (typeof block.content === "string")
229
325
  return block.content;
230
- return block.content.map((part) => {
326
+ return block.content
327
+ .map((part) => {
231
328
  if (part.type === "text" || part.type === "thinking")
232
329
  return part.text;
233
330
  if (part.type === "tool_result")
@@ -245,7 +342,8 @@ function blockText(block) {
245
342
  if (part.type === "document")
246
343
  return part.transcript ?? part.name ?? part.url ?? part.resourceUri ?? part.mediaType ?? "[document]";
247
344
  return "[content]";
248
- }).join("\n");
345
+ })
346
+ .join("\n");
249
347
  }
250
348
  function templateValue(value) {
251
349
  if (typeof value === "string")
@@ -259,7 +357,9 @@ function sortJson(value) {
259
357
  return value;
260
358
  if (Array.isArray(value))
261
359
  return value.map(sortJson);
262
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
360
+ return Object.fromEntries(Object.keys(value)
361
+ .sort()
362
+ .map((key) => [key, sortJson(value[key])]));
263
363
  }
264
364
  function throwIfAborted(signal) {
265
365
  if (signal?.aborted)
@@ -1,5 +1,5 @@
1
- import type { ContributionRegistry } from "./contributions.js";
2
1
  import type { ContextBlock, InstructionContext, InstructionInjector, SystemPromptContribution } from "./contracts.js";
2
+ import type { ContributionRegistry } from "./contributions.js";
3
3
  /** Run each selected injector against `ctx`, returning the matching contributions as
4
4
  * `package`-source `SystemPromptContribution[]` (for `composeSystemPrompt`) and
5
5
  * `ContextBlock[]` (for the context merge). Aborts on `ctx.signal`. Non-matching
@@ -21,10 +21,13 @@ export function createMiddlewareRegistry(options = {}) {
21
21
  },
22
22
  async run(hook, value) {
23
23
  let current = value;
24
- for (const middleware of byHook.get(hook) ?? []) {
24
+ for (const [index, middleware] of (byHook.get(hook) ?? []).entries()) {
25
25
  try {
26
26
  let calledNext = false;
27
27
  const next = async (nextValue) => {
28
+ // Double next() forks the chain value nondeterministically — always a bug.
29
+ if (calledNext)
30
+ throw new Error(`Middleware hook "${hook}" #${index}: next() called more than once`);
28
31
  calledNext = true;
29
32
  current = nextValue;
30
33
  return current;
@@ -32,6 +35,11 @@ export function createMiddlewareRegistry(options = {}) {
32
35
  const result = await middleware(current, next);
33
36
  if (!calledNext)
34
37
  current = result;
38
+ else if (result !== undefined && result !== current) {
39
+ // next(v) already committed the chain value; a conflicting return is silently
40
+ // discarded. Ambiguous rather than certainly-wrong, so diagnose, don't throw.
41
+ await options.onError?.(middlewareError(new Error(`Middleware hook "${hook}" #${index}: called next(value) and returned a different value; the next() value wins, the return is discarded`), hook, secrets));
42
+ }
35
43
  }
36
44
  catch (error) {
37
45
  if (errorPolicy === "throw")
package/dist/models.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { ModelConfig } from "./contracts.js";
2
2
  import { type DuplicateRegistrationOptions } from "./registry-options.js";
3
3
  export interface ModelRegistry {
4
4
  register(model: ModelConfig): void;
5
+ /** Remove a model; returns false when it was not registered. */
6
+ unregister(provider: string, model: string): boolean;
5
7
  get(provider: string, model: string): ModelConfig | undefined;
6
8
  resolve(provider: string, model: string): ModelConfig;
7
9
  list(): readonly ModelConfig[];
package/dist/models.js CHANGED
@@ -8,6 +8,9 @@ export function createModelRegistry(models = [], options = {}) {
8
8
  assertCanRegister(byId, id, "model", `${model.provider}/${model.model}`, options.duplicate);
9
9
  byId.set(id, model);
10
10
  },
11
+ unregister(provider, model) {
12
+ return byId.delete(key(provider, model));
13
+ },
11
14
  get(provider, model) {
12
15
  return byId.get(key(provider, model));
13
16
  },
@@ -1,13 +1,12 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
1
2
  import { join } from "node:path";
2
- import { readFile } from "node:fs/promises";
3
- import { readdir, stat } from "node:fs/promises";
4
- import { parseSkillFile, splitFrontmatter } from "../contribution-parsing.js";
5
3
  import { resolveAgentDefinition } from "../agent-definitions.js";
4
+ import { parseSkillFile, splitFrontmatter } from "../contribution-parsing.js";
6
5
  import { assertPermission, isTrusted } from "../security.js";
7
- import { isPathInsideReal } from "./trust.js";
8
- import { readOptionalFile } from "./contribution-discovery.js";
9
6
  import { createSkillRegistry } from "../skills.js";
10
7
  import { isNodeErrorCode } from "./config.js";
8
+ import { readOptionalFile } from "./contribution-discovery.js";
9
+ import { isPathInsideReal } from "./trust.js";
11
10
  const defaultInclude = {
12
11
  systemPrompt: true,
13
12
  agentPrompt: true,
@@ -288,7 +287,13 @@ function resolveSkillContexts(skills, contextProviders) {
288
287
  function collectMetadata(front) {
289
288
  const metadata = {};
290
289
  for (const [key, value] of front) {
291
- if (key !== "name" && key !== "description" && key !== "model" && key !== "tools" && key !== "skills" && key !== "context" && key !== "instructions") {
290
+ if (key !== "name" &&
291
+ key !== "description" &&
292
+ key !== "model" &&
293
+ key !== "tools" &&
294
+ key !== "skills" &&
295
+ key !== "context" &&
296
+ key !== "instructions") {
292
297
  metadata[key] = value;
293
298
  }
294
299
  }
@@ -305,7 +310,10 @@ function getStringList(front, key) {
305
310
  if (Array.isArray(value))
306
311
  return value.map(String);
307
312
  if (typeof value === "string")
308
- return value.split(",").map((s) => s.trim()).filter(Boolean);
313
+ return value
314
+ .split(",")
315
+ .map((s) => s.trim())
316
+ .filter(Boolean);
309
317
  return undefined;
310
318
  }
311
319
  function bodyAsInstructions(text, path) {
@@ -348,7 +356,7 @@ async function readOptional(read, path) {
348
356
  const NAME_RE = /^[A-Za-z0-9 _-]+$/;
349
357
  function parentDirName(path) {
350
358
  const parts = path.split(/[/\\]+/).filter(Boolean);
351
- return parts.length >= 2 ? parts[parts.length - 2] : parts[parts.length - 1] ?? "";
359
+ return parts.length >= 2 ? parts[parts.length - 2] : (parts[parts.length - 1] ?? "");
352
360
  }
353
361
  /** Parse a `CONTEXT.md` into a static {@link ContextProvider}. The markdown
354
362
  * body becomes the context block content; frontmatter `name` overrides the
@@ -1,6 +1,5 @@
1
1
  import type { ContributionFileKind, DiscoveredContribution } from "../contracts.js";
2
- import type { PermissionPolicy } from "../security.js";
3
- import type { TrustPolicy } from "../security.js";
2
+ import type { PermissionPolicy, TrustPolicy } from "../security.js";
4
3
  export interface DiscoveryOptions {
5
4
  readonly kinds: readonly ContributionFileKind[];
6
5
  /** Workspace root. Scans `<root>/.agents/<kind>s/<name>/`. Gated by `trust`. */
@@ -1,10 +1,10 @@
1
1
  import { readdir, readFile, stat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { isJsonObject } from "../config.js";
4
- import { assertPermission } from "../security.js";
5
- import { isPathInsideReal } from "./trust.js";
6
4
  import { parseSkillFile } from "../contribution-parsing.js";
5
+ import { assertPermission } from "../security.js";
7
6
  import { isNodeErrorCode } from "./config.js";
7
+ import { isPathInsideReal } from "./trust.js";
8
8
  /**
9
9
  * Discover contributions on disk. Scans the workspace `.agents/` tree only;
10
10
  * no global root. One `readdir` per kind-root; no `import()`. Inert output —
@@ -72,7 +72,7 @@ async function readEntry(dir, fallbackName, kind, origin) {
72
72
  return readManifestEntry(dir, fallbackName, kind, origin);
73
73
  }
74
74
  }
75
- async function readSkillEntry(dir, fallbackName, origin) {
75
+ async function readSkillEntry(dir, _fallbackName, origin) {
76
76
  const path = join(dir, "SKILL.md");
77
77
  if (!(await isPathInsideReal(dir, path)))
78
78
  return undefined;
@@ -1,6 +1,6 @@
1
- import { mkdir, readFile, appendFile } from "node:fs/promises";
1
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
- import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError, isSessionEntryKind, SESSION_ENTRY_SCHEMA_VERSION } from "../contracts.js";
3
+ import { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, SessionSearchUnsupportedError, } from "../contracts.js";
4
4
  import { isNodeErrorCode } from "./config.js";
5
5
  export function createJsonlSessionStore(pathOrOptions) {
6
6
  const options = typeof pathOrOptions === "string" ? { path: pathOrOptions, createDirectory: true } : pathOrOptions;
@@ -46,6 +46,9 @@ export function createJsonlSessionStore(pathOrOptions) {
46
46
  async get(id) {
47
47
  return findEntry(path, id);
48
48
  },
49
+ async searchSessions() {
50
+ throw new SessionSearchUnsupportedError("JSONL session store does not support searchSessions");
51
+ },
49
52
  };
50
53
  }
51
54
  /** Read a JSONL session file and return both valid entries and per-line parse errors. */
@@ -90,7 +93,10 @@ function parseEntry(line, lineNumber) {
90
93
  }
91
94
  function validateSessionEntry(value, lineNumber, raw) {
92
95
  if (!isBasicSessionEntry(value)) {
93
- return { ok: false, error: { line: lineNumber, message: "Invalid session entry: expected object with id, sessionId, timestamp, and kind", raw } };
96
+ return {
97
+ ok: false,
98
+ error: { line: lineNumber, message: "Invalid session entry: expected object with id, sessionId, timestamp, and kind", raw },
99
+ };
94
100
  }
95
101
  const entry = value;
96
102
  if (entry.parentId !== undefined && typeof entry.parentId !== "string") {
@@ -146,10 +152,10 @@ function isBasicSessionEntry(value) {
146
152
  if (!value || typeof value !== "object" || Array.isArray(value))
147
153
  return false;
148
154
  const entry = value;
149
- return typeof entry.id === "string"
150
- && typeof entry.sessionId === "string"
151
- && typeof entry.timestamp === "string"
152
- && typeof entry.kind === "string";
155
+ return (typeof entry.id === "string" &&
156
+ typeof entry.sessionId === "string" &&
157
+ typeof entry.timestamp === "string" &&
158
+ typeof entry.kind === "string");
153
159
  }
154
160
  function isMessage(value) {
155
161
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -1,6 +1,6 @@
1
1
  import { type ConfigLayer } from "../config.js";
2
- import { type NodeConfigFile } from "./config.js";
3
2
  import type { SettingsProvider } from "../contracts.js";
3
+ import { type NodeConfigFile } from "./config.js";
4
4
  export type NodeSettingsFile = NodeConfigFile;
5
5
  export declare function defaultUserSettingsPath(appName?: string): string;
6
6
  export declare function readSettingsFile(path: string): Promise<ConfigLayer["config"]>;
@@ -1,8 +1,8 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
  import { mergeConfigLayers } from "../config.js";
4
- import { readConfigFile, isNodeErrorCode } from "./config.js";
5
4
  import { createStaticSettingsProvider } from "../settings.js";
5
+ import { isNodeErrorCode, readConfigFile } from "./config.js";
6
6
  export function defaultUserSettingsPath(appName = "prism") {
7
7
  return join(homedir(), ".config", appName, "settings.json");
8
8
  }
@@ -14,10 +14,8 @@ import { readOptionalFile } from "./contribution-discovery.js";
14
14
  * singletons don't fit `discoverContributions`' named-subdir scanner, so this is a
15
15
  * sibling loader mirroring `src/node/instruction-injectors.ts`'s per-concern adapter shape. */
16
16
  export async function loadSystemPromptFiles(options) {
17
- const agentsPath = options.agentsMdPath
18
- ?? (options.workspaceRoot !== undefined ? join(options.workspaceRoot, "AGENTS.md") : undefined);
19
- const systemPath = options.systemMdPath
20
- ?? (options.globalRoot !== undefined ? join(options.globalRoot, ".prism", "agent", "SYSTEM.md") : undefined);
17
+ const agentsPath = options.agentsMdPath ?? (options.workspaceRoot !== undefined ? join(options.workspaceRoot, "AGENTS.md") : undefined);
18
+ const systemPath = options.systemMdPath ?? (options.globalRoot !== undefined ? join(options.globalRoot, ".prism", "agent", "SYSTEM.md") : undefined);
21
19
  const out = [];
22
20
  if (systemPath !== undefined) {
23
21
  const layer = await readSystemFile(systemPath, options.permission);
@@ -1,5 +1,5 @@
1
- import { isAbsolute, relative, resolve, dirname, basename, join } from "node:path";
2
1
  import { realpath } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { isNodeErrorCode } from "./config.js";
4
4
  export function isPathInside(root, target) {
5
5
  const from = resolve(root);