@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
@@ -1,4 +1,4 @@
1
- import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError } from "./contracts.js";
1
+ import { DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
2
2
  import { createId } from "./ids.js";
3
3
  export function createSessionEntry(options) {
4
4
  const { createId, now, ...entry } = options;
@@ -13,7 +13,9 @@ export function createSessionEntry(options) {
13
13
  // this if a legitimate branch exceeds it.
14
14
  const MAX_BRANCH_PAGES = 64;
15
15
  export function getSessionBranchEntries(input, options = {}) {
16
- return typeof input === "function" ? readBranchFromReader(input, options) : getSessionBranchEntriesCore(input, options);
16
+ return typeof input === "function"
17
+ ? readBranchFromReader(input, options)
18
+ : getSessionBranchEntriesCore(input, options);
17
19
  }
18
20
  async function readBranchFromReader(reader, query) {
19
21
  const items = [];
@@ -29,8 +31,7 @@ async function readBranchFromReader(reader, query) {
29
31
  // indexEntries + the parentId walk order it and still reject missing parents / dupes.
30
32
  return getSessionBranchEntriesCore(items, { leafId: query.leafId });
31
33
  }
32
- function getSessionBranchEntriesCore(entries, options = {}) {
33
- const index = indexEntries(entries);
34
+ function getSessionBranchEntriesCore(entries, options = {}, index = indexEntries(entries)) {
34
35
  const leafId = options.leafId ?? entries.at(-1)?.id;
35
36
  if (!leafId)
36
37
  return [];
@@ -50,7 +51,7 @@ export function listSessionBranches(entries) {
50
51
  const index = indexEntries(entries);
51
52
  return entries
52
53
  .filter((entry) => !index.parentIds.has(entry.id))
53
- .map((entry) => ({ leafId: entry.id, entries: getSessionBranchEntries(entries, { leafId: entry.id }) }));
54
+ .map((entry) => ({ leafId: entry.id, entries: getSessionBranchEntriesCore(entries, { leafId: entry.id }, index) }));
54
55
  }
55
56
  export function rebuildSessionContext(input, options = {}) {
56
57
  if (typeof input === "function") {
@@ -66,13 +67,15 @@ async function rebuildSessionContextFromReader(reader, query) {
66
67
  }
67
68
  function rebuildSessionContextCore(entries, options = {}) {
68
69
  const branch = getSessionBranchEntriesCore(entries, options);
69
- const compaction = [...branch].reverse().find((entry) => entry.kind === "compaction" && entry.summary && isCompactionEntryData(entry.data));
70
+ const compaction = [...branch]
71
+ .reverse()
72
+ .find((entry) => entry.kind === "compaction" && entry.summary && isCompactionEntryData(entry.data));
70
73
  if (!compaction || !isCompactionEntryData(compaction.data)) {
71
74
  return {
72
75
  leafId: branch.at(-1)?.id,
73
76
  entries: branch,
74
- messages: branch.flatMap((entry) => entry.kind === "message" && entry.message ? [entry.message] : []),
75
- summaries: branch.flatMap((entry) => entry.kind === "summary" && entry.summary ? [entry.summary] : []),
77
+ messages: branch.flatMap((entry) => (entry.kind === "message" && entry.message ? [entry.message] : [])),
78
+ summaries: branch.flatMap((entry) => (entry.kind === "summary" && entry.summary ? [entry.summary] : [])),
76
79
  };
77
80
  }
78
81
  const keepIds = new Set(compaction.data.keepEntryIds ?? []);
@@ -93,16 +96,17 @@ function rebuildSessionContextCore(entries, options = {}) {
93
96
  }
94
97
  return { leafId: branch.at(-1)?.id, entries: branch, messages, summaries };
95
98
  }
96
- export function createMemorySessionStore(initialEntries = []) {
99
+ export function createMemorySessionStore(initialEntries = [], options = {}) {
97
100
  const byId = new Map();
98
101
  const bySession = new Map();
99
102
  const leafBySession = new Map();
100
103
  const idempotencySeen = new Set();
104
+ const mode = options.sessionSearchMode ?? "linear";
101
105
  for (const entry of initialEntries)
102
106
  add(entry);
103
107
  return {
104
- async append(entry, options) {
105
- add(entry, options);
108
+ async append(entry, appendOptions) {
109
+ add(entry, appendOptions);
106
110
  },
107
111
  async list(sessionId) {
108
112
  return (bySession.get(sessionId) ?? []).map(cloneEntry);
@@ -111,6 +115,11 @@ export function createMemorySessionStore(initialEntries = []) {
111
115
  const entry = byId.get(id);
112
116
  return entry ? cloneEntry(entry) : undefined;
113
117
  },
118
+ async searchSessions(query) {
119
+ if (mode === "unsupported")
120
+ throw new SessionSearchUnsupportedError();
121
+ return searchMemorySessionsLinear(bySession, leafBySession, query);
122
+ },
114
123
  };
115
124
  function add(entry, options) {
116
125
  // ponytail: idempotency dedup keyed on (session, key, expectedParentId) so a
@@ -123,17 +132,22 @@ export function createMemorySessionStore(initialEntries = []) {
123
132
  if (dedupKey !== undefined && idempotencySeen.has(dedupKey)) {
124
133
  throw new SessionAppendConflictError({ code: SESSION_APPEND_CONFLICT_CODE, idempotencyDuplicate: true });
125
134
  }
126
- // expectedParentId is existence validation (the parent must already be in the
127
- // store or be undefined for a root). Tip-CAS is intentionally NOT used: prism
135
+ // expectedParentId is same-session existence validation (the parent must already
136
+ // be in the store for THIS session, or be undefined for a root). A parent from
137
+ // another session is rejected: the write would be unreadable, since every branch
138
+ // walk is per-session. Tip-CAS is intentionally NOT used: prism
128
139
  // allows branching from any existing leaf (checkout + append), so a stale-but-
129
140
  // existing parent is a valid branch, not a conflict. DB adapters may layer
130
141
  // stricter tip-CAS via unique constraints for linear-only sessions.
131
- if (options?.expectedParentId !== undefined && !byId.has(options.expectedParentId)) {
132
- throw new SessionAppendConflictError({
133
- code: SESSION_APPEND_CONFLICT_CODE,
134
- expectedParentId: options.expectedParentId,
135
- currentLeafId: leafBySession.get(entry.sessionId),
136
- });
142
+ if (options?.expectedParentId !== undefined) {
143
+ const parent = byId.get(options.expectedParentId);
144
+ if (!parent || parent.sessionId !== entry.sessionId) {
145
+ throw new SessionAppendConflictError({
146
+ code: SESSION_APPEND_CONFLICT_CODE,
147
+ expectedParentId: options.expectedParentId,
148
+ currentLeafId: leafBySession.get(entry.sessionId),
149
+ });
150
+ }
137
151
  }
138
152
  if (byId.has(entry.id))
139
153
  throw new Error(`Duplicate session entry id: ${entry.id}`);
@@ -146,6 +160,166 @@ export function createMemorySessionStore(initialEntries = []) {
146
160
  leafBySession.set(entry.sessionId, entry.id);
147
161
  }
148
162
  }
163
+ function searchMemorySessionsLinear(bySession, leafBySession, query) {
164
+ const q = resolveSessionSearchQuery(query);
165
+ q.signal?.throwIfAborted();
166
+ let sessionsScanned = 0;
167
+ let entriesScanned = 0;
168
+ let bytesScanned = 0;
169
+ const matches = [];
170
+ for (const [sessionId, entries] of bySession) {
171
+ if (sessionsScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS)
172
+ break;
173
+ if (entriesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES)
174
+ break;
175
+ if (bytesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES)
176
+ break;
177
+ q.signal?.throwIfAborted();
178
+ sessionsScanned += 1;
179
+ let updatedAt = "";
180
+ let label;
181
+ let summary;
182
+ let workspaceRoot;
183
+ let tenantId;
184
+ let accountId;
185
+ let userId;
186
+ let matchedLabel = false;
187
+ let matchedSummary = false;
188
+ let matchedQuery = false;
189
+ let matchedProvider = false;
190
+ let matchedModel = false;
191
+ let snippetSource;
192
+ for (const entry of entries) {
193
+ if (entriesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES)
194
+ break;
195
+ if (bytesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES)
196
+ break;
197
+ entriesScanned += 1;
198
+ const text = entrySearchText(entry);
199
+ bytesScanned += utf8Bytes(text) + utf8Bytes(entry.label) + utf8Bytes(entry.summary);
200
+ if (entry.timestamp > updatedAt)
201
+ updatedAt = entry.timestamp;
202
+ if (entry.label)
203
+ label = entry.label;
204
+ if (entry.summary)
205
+ summary = entry.summary;
206
+ const meta = entry.metadata;
207
+ if (meta) {
208
+ if (typeof meta[SESSION_SEARCH_WORKSPACE_METADATA_KEY] === "string") {
209
+ workspaceRoot = meta[SESSION_SEARCH_WORKSPACE_METADATA_KEY];
210
+ }
211
+ if (typeof meta.tenantId === "string")
212
+ tenantId = meta.tenantId;
213
+ if (typeof meta.accountId === "string")
214
+ accountId = meta.accountId;
215
+ if (typeof meta.userId === "string")
216
+ userId = meta.userId;
217
+ }
218
+ if (q.label && entry.label?.includes(q.label))
219
+ matchedLabel = true;
220
+ if (q.summary && entry.summary?.includes(q.summary))
221
+ matchedSummary = true;
222
+ if (q.query) {
223
+ const hay = `${entry.label ?? ""}\n${entry.summary ?? ""}\n${text}`;
224
+ if (hay.includes(q.query)) {
225
+ matchedQuery = true;
226
+ snippetSource ??= entry.label ?? entry.summary ?? text;
227
+ }
228
+ }
229
+ if (q.provider && (entry.model?.provider === q.provider || metaProvider(entry) === q.provider))
230
+ matchedProvider = true;
231
+ if (q.model && entry.model?.model === q.model)
232
+ matchedModel = true;
233
+ }
234
+ if (q.workspaceRoot && workspaceRoot !== q.workspaceRoot)
235
+ continue;
236
+ if (q.tenantId && tenantId !== q.tenantId)
237
+ continue;
238
+ if (q.accountId && accountId !== q.accountId)
239
+ continue;
240
+ if (q.userId && userId !== q.userId)
241
+ continue;
242
+ if (q.label && !matchedLabel)
243
+ continue;
244
+ if (q.summary && !matchedSummary)
245
+ continue;
246
+ if (q.query && !matchedQuery)
247
+ continue;
248
+ if (q.provider && !matchedProvider)
249
+ continue;
250
+ if (q.model && !matchedModel)
251
+ continue;
252
+ if (q.fromUpdatedAt && updatedAt < q.fromUpdatedAt)
253
+ continue;
254
+ if (q.toUpdatedAt && updatedAt > q.toUpdatedAt)
255
+ continue;
256
+ matches.push({
257
+ sessionId,
258
+ leafId: leafBySession.get(sessionId),
259
+ updatedAt: updatedAt || undefined,
260
+ label,
261
+ summary,
262
+ snippet: clipSnippet(snippetSource ?? label ?? summary),
263
+ metadata: workspaceRoot !== undefined ? { [SESSION_SEARCH_WORKSPACE_METADATA_KEY]: workspaceRoot } : undefined,
264
+ });
265
+ }
266
+ matches.sort((a, b) => compareSearchHit(a, b, q.order));
267
+ const afterCursor = q.cursor ? decodeSearchCursor(q.cursor) : undefined;
268
+ const filtered = afterCursor ? matches.filter((hit) => isAfterSearchCursor(hit, afterCursor, q.order)) : matches;
269
+ const page = filtered.slice(0, q.limit);
270
+ const last = page.at(-1);
271
+ return {
272
+ items: page,
273
+ nextCursor: filtered.length > q.limit && last?.updatedAt ? encodeSearchCursor(last.updatedAt, last.sessionId) : undefined,
274
+ };
275
+ }
276
+ function entrySearchText(entry) {
277
+ if (!entry.message?.content)
278
+ return "";
279
+ const parts = [];
280
+ for (const block of entry.message.content) {
281
+ if (block.type === "text" && typeof block.text === "string")
282
+ parts.push(block.text);
283
+ }
284
+ return parts.join("\n");
285
+ }
286
+ function metaProvider(entry) {
287
+ const value = entry.metadata?.provider;
288
+ return typeof value === "string" ? value : undefined;
289
+ }
290
+ function utf8Bytes(value) {
291
+ return value ? new TextEncoder().encode(value).byteLength : 0;
292
+ }
293
+ function clipSnippet(value) {
294
+ if (!value)
295
+ return undefined;
296
+ const encoded = new TextEncoder().encode(value);
297
+ if (encoded.byteLength <= DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES)
298
+ return value;
299
+ return new TextDecoder().decode(encoded.slice(0, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES));
300
+ }
301
+ function encodeSearchCursor(updatedAt, sessionId) {
302
+ return `${updatedAt}\t${sessionId}`;
303
+ }
304
+ function decodeSearchCursor(cursor) {
305
+ const tab = cursor.indexOf("\t");
306
+ if (tab <= 0 || tab === cursor.length - 1)
307
+ throw new TypeError("SessionSearchQuery.cursor is invalid");
308
+ return { updatedAt: cursor.slice(0, tab), sessionId: cursor.slice(tab + 1) };
309
+ }
310
+ function compareSearchHit(a, b, order) {
311
+ const aAt = a.updatedAt ?? "";
312
+ const bAt = b.updatedAt ?? "";
313
+ const cmp = aAt < bAt ? -1 : aAt > bAt ? 1 : a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0;
314
+ return order === "asc" ? cmp : -cmp;
315
+ }
316
+ function isAfterSearchCursor(hit, cursor, order) {
317
+ const at = hit.updatedAt ?? "";
318
+ if (order === "asc") {
319
+ return at > cursor.updatedAt || (at === cursor.updatedAt && hit.sessionId > cursor.sessionId);
320
+ }
321
+ return at < cursor.updatedAt || (at === cursor.updatedAt && hit.sessionId < cursor.sessionId);
322
+ }
149
323
  function cloneEntry(entry) {
150
324
  return structuredClone(entry);
151
325
  }
@@ -153,8 +327,8 @@ function isCompactionEntryData(value) {
153
327
  if (!value || typeof value !== "object" || Array.isArray(value))
154
328
  return false;
155
329
  const data = value;
156
- return (data.throughEntryId === undefined || typeof data.throughEntryId === "string")
157
- && (data.keepEntryIds === undefined || (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string")));
330
+ return ((data.throughEntryId === undefined || typeof data.throughEntryId === "string") &&
331
+ (data.keepEntryIds === undefined || (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string"))));
158
332
  }
159
333
  function indexEntries(entries) {
160
334
  const byId = new Map();
@@ -0,0 +1,35 @@
1
+ import type { Message, Skill } from "./contracts.js";
2
+ export type SkillsDisclosure = "progressive" | "eager";
3
+ export declare const EMPTY_SKILL_DESCRIPTION: "(no description)";
4
+ export declare const DEFAULT_MAX_SKILL_CATALOG_ENTRIES = 64;
5
+ export declare const HARD_MAX_SKILL_CATALOG_ENTRIES = 256;
6
+ export declare const DEFAULT_MAX_SKILL_DESCRIPTION_BYTES = 512;
7
+ export declare const HARD_MAX_SKILL_DESCRIPTION_BYTES = 4096;
8
+ export declare const DEFAULT_MAX_SKILL_INSTRUCTION_BYTES = 32768;
9
+ export declare const HARD_MAX_SKILL_INSTRUCTION_BYTES = 262144;
10
+ export declare const SKILL_DISCLOSURE_ERROR_CODE: "skill_disclosure_exceeded";
11
+ export declare class SkillDisclosureError extends Error {
12
+ readonly code: "skill_disclosure_exceeded";
13
+ constructor(message: string);
14
+ }
15
+ export declare function isSkillDisclosureError(error: unknown): error is SkillDisclosureError;
16
+ export interface LoadedSkillSet {
17
+ has(name: string): boolean;
18
+ add(name: string): void;
19
+ list(): readonly string[];
20
+ clear(): void;
21
+ }
22
+ export declare function createLoadedSkillSet(): LoadedSkillSet;
23
+ export interface SkillRenderContext {
24
+ readonly disclosure?: SkillsDisclosure;
25
+ readonly loaded?: LoadedSkillSet;
26
+ /** Budget demotion: render catalog-only even when eager/loaded. */
27
+ readonly demotedBodies?: ReadonlySet<string>;
28
+ }
29
+ export declare function resolveSkillsDisclosure(run?: SkillsDisclosure, agent?: SkillsDisclosure): SkillsDisclosure;
30
+ export declare function capSkillCatalog(skills: readonly Skill[]): readonly Skill[];
31
+ /** Skills that should appear in prompt assembly for the given disclosure mode. */
32
+ export declare function selectSkillsForPrompt(skills: readonly Skill[], context?: SkillRenderContext): readonly Skill[];
33
+ export declare function skillHasRenderableBody(skill: Skill, context?: SkillRenderContext): boolean;
34
+ export declare function skillPromptText(skill: Skill, context?: SkillRenderContext): string | undefined;
35
+ export declare function skillMessages(skills: readonly Skill[] | undefined, context?: SkillRenderContext): Message[];
@@ -0,0 +1,101 @@
1
+ import { estimateTextBytes } from "./context-budget.js";
2
+ export const EMPTY_SKILL_DESCRIPTION = "(no description)";
3
+ export const DEFAULT_MAX_SKILL_CATALOG_ENTRIES = 64;
4
+ export const HARD_MAX_SKILL_CATALOG_ENTRIES = 256;
5
+ export const DEFAULT_MAX_SKILL_DESCRIPTION_BYTES = 512;
6
+ export const HARD_MAX_SKILL_DESCRIPTION_BYTES = 4_096;
7
+ export const DEFAULT_MAX_SKILL_INSTRUCTION_BYTES = 32_768;
8
+ export const HARD_MAX_SKILL_INSTRUCTION_BYTES = 262_144;
9
+ export const SKILL_DISCLOSURE_ERROR_CODE = "skill_disclosure_exceeded";
10
+ export class SkillDisclosureError extends Error {
11
+ code = SKILL_DISCLOSURE_ERROR_CODE;
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "SkillDisclosureError";
15
+ }
16
+ }
17
+ export function isSkillDisclosureError(error) {
18
+ return error instanceof Error && error.code === SKILL_DISCLOSURE_ERROR_CODE;
19
+ }
20
+ export function createLoadedSkillSet() {
21
+ const names = new Set();
22
+ return {
23
+ has(name) {
24
+ return names.has(name);
25
+ },
26
+ add(name) {
27
+ names.add(name);
28
+ },
29
+ list() {
30
+ return [...names];
31
+ },
32
+ clear() {
33
+ names.clear();
34
+ },
35
+ };
36
+ }
37
+ export function resolveSkillsDisclosure(run, agent) {
38
+ return run ?? agent ?? "progressive";
39
+ }
40
+ export function capSkillCatalog(skills) {
41
+ if (skills.length > HARD_MAX_SKILL_CATALOG_ENTRIES) {
42
+ throw new SkillDisclosureError(`Skill catalog exceeds hard cap (${HARD_MAX_SKILL_CATALOG_ENTRIES} entries)`);
43
+ }
44
+ return skills.slice(0, DEFAULT_MAX_SKILL_CATALOG_ENTRIES);
45
+ }
46
+ /** Skills that should appear in prompt assembly for the given disclosure mode. */
47
+ export function selectSkillsForPrompt(skills, context = {}) {
48
+ const disclosure = context.disclosure ?? "progressive";
49
+ if (disclosure === "eager")
50
+ return skills.filter((skill) => skill.instructions);
51
+ return skills;
52
+ }
53
+ export function skillHasRenderableBody(skill, context = {}) {
54
+ const disclosure = context.disclosure ?? "progressive";
55
+ const loaded = context.loaded?.has(skill.name) ?? false;
56
+ const demoted = context.demotedBodies?.has(skill.name) ?? false;
57
+ return !demoted && (disclosure === "eager" || loaded) && !!skill.instructions;
58
+ }
59
+ export function skillPromptText(skill, context = {}) {
60
+ const includeBody = skillHasRenderableBody(skill, context);
61
+ if (includeBody) {
62
+ if (!skill.instructions)
63
+ return undefined;
64
+ const bytes = estimateTextBytes(skill.instructions);
65
+ if (bytes > HARD_MAX_SKILL_INSTRUCTION_BYTES) {
66
+ throw new SkillDisclosureError(`Skill ${skill.name} instructions exceed hard cap (${HARD_MAX_SKILL_INSTRUCTION_BYTES} bytes)`);
67
+ }
68
+ return `Skill ${skill.name}:\n${skill.instructions}`;
69
+ }
70
+ let description = skill.description?.trim() || EMPTY_SKILL_DESCRIPTION;
71
+ const bytes = estimateTextBytes(description);
72
+ if (bytes > HARD_MAX_SKILL_DESCRIPTION_BYTES) {
73
+ throw new SkillDisclosureError(`Skill ${skill.name} description exceeds hard cap (${HARD_MAX_SKILL_DESCRIPTION_BYTES} bytes)`);
74
+ }
75
+ if (bytes > DEFAULT_MAX_SKILL_DESCRIPTION_BYTES) {
76
+ description = truncateUtf8Bytes(description, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES);
77
+ }
78
+ return `Skill ${skill.name}: ${description}`;
79
+ }
80
+ export function skillMessages(skills, context = {}) {
81
+ return capSkillCatalog(selectSkillsForPrompt(skills ?? [], context))
82
+ .map((skill) => {
83
+ const text = skillPromptText(skill, context);
84
+ return text ? textMessage("system", text, skill.metadata) : undefined;
85
+ })
86
+ .filter((message) => message !== undefined);
87
+ }
88
+ function textMessage(role, text, metadata) {
89
+ return { role, content: [{ type: "text", text }], metadata };
90
+ }
91
+ function truncateUtf8Bytes(text, maxBytes) {
92
+ const encoded = new TextEncoder().encode(text);
93
+ if (encoded.length <= maxBytes)
94
+ return text;
95
+ const suffix = new TextEncoder().encode("…");
96
+ let end = Math.max(0, maxBytes - suffix.length);
97
+ while (end > 0 && (encoded[end] & 0xc0) === 0x80)
98
+ end--;
99
+ return new TextDecoder().decode(encoded.slice(0, end)) + "…";
100
+ }
101
+ //# sourceMappingURL=skill-disclosure.js.map
@@ -0,0 +1,25 @@
1
+ import type { Skill, SkillRegistry, ToolDefinition } from "./contracts.js";
2
+ import { type LoadedSkillSet } from "./skill-disclosure.js";
3
+ export declare const DEFAULT_LOAD_SKILL_TOOL_NAME: "load_skill";
4
+ export declare const SKILL_LOAD_ERROR_CODE: "skill_load_failed";
5
+ export declare const MAX_LOAD_SKILL_RESULT_BYTES = 512;
6
+ export declare class SkillLoadError extends Error {
7
+ readonly code: "skill_load_failed";
8
+ constructor(message: string);
9
+ }
10
+ export declare function isSkillLoadError(error: unknown): error is SkillLoadError;
11
+ export interface ResolveSkillLoadOptions {
12
+ readonly registry: SkillRegistry;
13
+ readonly name: string;
14
+ readonly tools?: readonly ToolDefinition[];
15
+ readonly loaded?: LoadedSkillSet;
16
+ readonly activeSkillNames?: readonly string[];
17
+ }
18
+ export declare function resolveSkillLoad(options: ResolveSkillLoadOptions): Skill;
19
+ export interface CreateLoadSkillToolOptions {
20
+ readonly registry: SkillRegistry;
21
+ readonly loaded?: LoadedSkillSet;
22
+ readonly tools?: readonly ToolDefinition[];
23
+ readonly name?: string;
24
+ }
25
+ export declare function createLoadSkillTool(options: CreateLoadSkillToolOptions): ToolDefinition;
@@ -0,0 +1,112 @@
1
+ import { estimateTextBytes } from "./context-budget.js";
2
+ import { HARD_MAX_SKILL_INSTRUCTION_BYTES } from "./skill-disclosure.js";
3
+ export const DEFAULT_LOAD_SKILL_TOOL_NAME = "load_skill";
4
+ export const SKILL_LOAD_ERROR_CODE = "skill_load_failed";
5
+ export const MAX_LOAD_SKILL_RESULT_BYTES = 512;
6
+ export class SkillLoadError extends Error {
7
+ code = SKILL_LOAD_ERROR_CODE;
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "SkillLoadError";
11
+ }
12
+ }
13
+ export function isSkillLoadError(error) {
14
+ return error instanceof Error && error.code === SKILL_LOAD_ERROR_CODE;
15
+ }
16
+ export function resolveSkillLoad(options) {
17
+ const name = options.name.trim();
18
+ if (!name)
19
+ throw new SkillLoadError("Skill name is required");
20
+ let skill;
21
+ try {
22
+ skill = options.registry.resolve(name);
23
+ }
24
+ catch {
25
+ throw new SkillLoadError(`Unknown skill: ${name}`);
26
+ }
27
+ if (options.activeSkillNames && !options.activeSkillNames.includes(skill.name)) {
28
+ throw new SkillLoadError(`Skill ${skill.name} is not active for this run`);
29
+ }
30
+ const toolNames = new Set((options.tools ?? []).map((tool) => tool.name));
31
+ const missingTool = skill.toolNames?.find((toolName) => !toolNames.has(toolName));
32
+ if (missingTool)
33
+ throw new SkillLoadError(`Skill ${skill.name} requires inactive tool: ${missingTool}`);
34
+ if (!skill.instructions?.trim())
35
+ throw new SkillLoadError(`Skill ${skill.name} has no instructions to load`);
36
+ const bytes = estimateTextBytes(skill.instructions);
37
+ if (bytes > HARD_MAX_SKILL_INSTRUCTION_BYTES) {
38
+ throw new SkillLoadError(`Skill ${skill.name} instructions exceed hard cap (${HARD_MAX_SKILL_INSTRUCTION_BYTES} bytes)`);
39
+ }
40
+ if (options.loaded?.has(skill.name))
41
+ throw new SkillLoadError(`Skill ${skill.name} is already loaded`);
42
+ return skill;
43
+ }
44
+ function skillLoadMetadata(context) {
45
+ const metadata = context.metadata;
46
+ if (!metadata || typeof metadata !== "object")
47
+ return undefined;
48
+ return metadata;
49
+ }
50
+ function capLoadSkillText(text) {
51
+ const bytes = estimateTextBytes(text);
52
+ if (bytes <= MAX_LOAD_SKILL_RESULT_BYTES)
53
+ return text;
54
+ const suffix = "…";
55
+ let end = MAX_LOAD_SKILL_RESULT_BYTES - new TextEncoder().encode(suffix).length;
56
+ const encoded = new TextEncoder().encode(text);
57
+ while (end > 0 && (encoded[end] & 0xc0) === 0x80)
58
+ end--;
59
+ return new TextDecoder().decode(encoded.slice(0, end)) + suffix;
60
+ }
61
+ export function createLoadSkillTool(options) {
62
+ const toolName = options.name ?? DEFAULT_LOAD_SKILL_TOOL_NAME;
63
+ return {
64
+ name: toolName,
65
+ description: "Load the full instructions for an active skill by exact name.",
66
+ parameters: {
67
+ type: "object",
68
+ properties: {
69
+ name: { type: "string", description: "Exact skill name from the catalog." },
70
+ },
71
+ required: ["name"],
72
+ },
73
+ execute(args, context) {
74
+ const fail = (reason, text) => {
75
+ const bounded = capLoadSkillText(text);
76
+ return {
77
+ toolCallId: context.toolCallId,
78
+ name: toolName,
79
+ value: { ok: false, reason, text: bounded },
80
+ content: [{ type: "text", text: bounded }],
81
+ };
82
+ };
83
+ const name = typeof args.name === "string" ? args.name : "";
84
+ const metadata = skillLoadMetadata(context);
85
+ const loaded = metadata?.loadedSkills ?? options.loaded;
86
+ if (!loaded)
87
+ return fail("no_loaded_set", "Skill load state is unavailable for this session.");
88
+ try {
89
+ const skill = resolveSkillLoad({
90
+ registry: options.registry,
91
+ name,
92
+ tools: metadata?.activeTools ?? options.tools,
93
+ loaded,
94
+ activeSkillNames: metadata?.activeSkillNames,
95
+ });
96
+ loaded.add(skill.name);
97
+ const text = capLoadSkillText(`Loaded skill ${skill.name} for this session.`);
98
+ return {
99
+ toolCallId: context.toolCallId,
100
+ name: toolName,
101
+ value: { ok: true, name: skill.name, text },
102
+ content: [{ type: "text", text }],
103
+ };
104
+ }
105
+ catch (error) {
106
+ const message = error instanceof Error ? error.message : "Skill load failed";
107
+ return fail("skill_load_failed", message);
108
+ }
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=skill-load.js.map
@@ -1,4 +1,4 @@
1
- import type { AgentConfig, ModelCapabilities, ModelConfig, ProviderRequestOptions, RunOptions, StructuredOutputOptions } from "./contracts.js";
1
+ import type { AgentConfig, ModelCapabilities, ModelConfig, ProviderRequest, ProviderRequestOptions, RunOptions, StructuredOutputOptions } from "./contracts.js";
2
2
  export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES = 65536;
3
3
  export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH = 128;
4
4
  export declare class StructuredOutputError extends Error {
@@ -9,3 +9,7 @@ export declare function modelSupportsStructuredOutput(capabilities?: ModelCapabi
9
9
  export declare function validateStructuredOutputOptions(options: StructuredOutputOptions): StructuredOutputOptions;
10
10
  export declare function assertStructuredOutputRequestSupported(model: ModelConfig, options?: ProviderRequestOptions): void;
11
11
  export declare function resolveRunProviderOptions(runOptions: Pick<RunOptions, "providerOptions" | "loop">, config: Pick<AgentConfig, "providerOptions" | "loop">): ProviderRequestOptions | undefined;
12
+ /** Strip native schema so a tool-eligible turn can call tools freely. */
13
+ export declare function withoutStructuredOutput(request: ProviderRequest): ProviderRequest;
14
+ /** Artifact/revision turn: keep/restore schema and withdraw tools. */
15
+ export declare function artifactStructuredOutputRequest(request: ProviderRequest, schema?: StructuredOutputOptions): ProviderRequest;
@@ -38,8 +38,8 @@ export function assertStructuredOutputRequestSupported(model, options) {
38
38
  return;
39
39
  validateStructuredOutputOptions(options.structuredOutput);
40
40
  if (!modelSupportsStructuredOutput(model.capabilities)) {
41
- throw new StructuredOutputError("unsupported_model", `Model ${model.provider}/${model.model} does not declare structuredOutput capability; `
42
- + `set loop.structuredOutputMode to "artifact-loop" or choose a capable model`);
41
+ throw new StructuredOutputError("unsupported_model", `Model ${model.provider}/${model.model} does not declare structuredOutput capability; ` +
42
+ `set loop.structuredOutputMode to "artifact-loop" or choose a capable model`);
43
43
  }
44
44
  }
45
45
  function isGenerateValidateReviseLoopOptions(loop) {
@@ -56,4 +56,22 @@ export function resolveRunProviderOptions(runOptions, config) {
56
56
  structuredOutput: validateStructuredOutputOptions(loop.structuredOutput),
57
57
  });
58
58
  }
59
+ /** Strip native schema so a tool-eligible turn can call tools freely. */
60
+ export function withoutStructuredOutput(request) {
61
+ if (!request.options?.structuredOutput)
62
+ return request;
63
+ const { structuredOutput: _drop, ...options } = request.options;
64
+ return { ...request, options: Object.keys(options).length > 0 ? options : undefined };
65
+ }
66
+ /** Artifact/revision turn: keep/restore schema and withdraw tools. */
67
+ export function artifactStructuredOutputRequest(request, schema) {
68
+ const structuredOutput = schema ?? request.options?.structuredOutput;
69
+ if (!structuredOutput)
70
+ return { ...request, tools: undefined };
71
+ return {
72
+ ...request,
73
+ tools: undefined,
74
+ options: { ...request.options, structuredOutput },
75
+ };
76
+ }
59
77
  //# sourceMappingURL=structured-output.js.map
@@ -1,7 +1,12 @@
1
1
  // ponytail: Phase 31 — `source: "user"` is the global base layer; `source: "app"` sits above package.
2
2
  // Behavioral change from Phase 14: `source: "user"` is now the global base (rank 0), not a high-priority caller override.
3
3
  // Unknown custom sources sort after package but before app/run so they cannot override host/run layers.
4
- const sourceRank = new Map([["user", 0], ["package", 1], ["app", 2], ["run", 3]]);
4
+ const sourceRank = new Map([
5
+ ["user", 0],
6
+ ["package", 1],
7
+ ["app", 2],
8
+ ["run", 3],
9
+ ]);
5
10
  const unknownSourceRank = 1.5;
6
11
  export function composeSystemPrompt(contributions = [], options = {}) {
7
12
  const parts = baseParts(options.base);
@@ -40,7 +45,7 @@ function isContributionArray(value) {
40
45
  return Array.isArray(value);
41
46
  }
42
47
  function baseParts(base) {
43
- return (typeof base === "string" ? [base] : base ?? []).filter((text) => text.length > 0);
48
+ return (typeof base === "string" ? [base] : (base ?? [])).filter((text) => text.length > 0);
44
49
  }
45
50
  function rank(layer) {
46
51
  return sourceRank.get(layer.source ?? "") ?? unknownSourceRank;
@@ -0,0 +1,4 @@
1
+ import type { AgentEventSource } from "../contracts.js";
2
+ export type AgentEventSourceConformanceFactory = () => AgentEventSource | Promise<AgentEventSource>;
3
+ /** Assert durable append/page/replay ownership and cursor behavior without a database dependency. */
4
+ export declare function assertAgentEventSourceConforms(factory: AgentEventSourceConformanceFactory): Promise<void>;