@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
package/dist/contracts.js CHANGED
@@ -1,6 +1,62 @@
1
+ /**
2
+ * Thrown by a delegated-run host (e.g. the supervisor) when a nested run suspends on
3
+ * pending decisions inside a tool execution. Core converts it into a root suspension
4
+ * with attributed, root-visible approval ids; the dispatching wrapper attaches `toolCall`.
5
+ */
6
+ export class AgentDelegationSuspendedError extends Error {
7
+ ref;
8
+ pendingDecisions;
9
+ path;
10
+ code = "ERR_PRISM_DELEGATION_SUSPENDED";
11
+ toolCall;
12
+ constructor(ref, pendingDecisions,
13
+ /** Redacted delegation path (child ids) used when decisions carry no attribution. */
14
+ path) {
15
+ super("Delegated run suspended");
16
+ this.ref = ref;
17
+ this.pendingDecisions = pendingDecisions;
18
+ this.path = path;
19
+ this.name = "AgentDelegationSuspendedError";
20
+ }
21
+ }
22
+ /** Shared decision-contract violations. Unknown and foreign approval ids share one non-enumerating error. */
23
+ export class AgentDecisionError extends Error {
24
+ code;
25
+ constructor(code, message, options) {
26
+ super(message, options);
27
+ this.code = code;
28
+ this.name = "AgentDecisionError";
29
+ }
30
+ }
31
+ export const DEFAULT_MAX_PENDING_DECISIONS = 32;
32
+ export const HARD_MAX_PENDING_DECISIONS = 128;
33
+ export const DEFAULT_MAX_STICKY_DECISIONS = 64;
34
+ export const HARD_MAX_STICKY_DECISIONS = 256;
35
+ export const MAX_DECISION_REASON_BYTES = 2 * 1024;
36
+ export const HARD_MAX_DECISION_REASON_BYTES = 8 * 1024;
37
+ export const MAX_ELICITATION_BYTES = 16 * 1024;
38
+ export const HARD_MAX_ELICITATION_BYTES = 64 * 1024;
39
+ export const MAX_ACTION_CONSTRAINTS = 32;
40
+ export const HARD_MAX_ACTION_CONSTRAINTS = 64;
41
+ /** Maximum delegation attribution depth for surfaced nested pending decisions. */
42
+ export const MAX_ATTRIBUTION_DEPTH = 8;
43
+ export const MAX_ACTION_CONSTRAINT_BYTES = 4 * 1024;
44
+ export const HARD_MAX_ACTION_CONSTRAINT_BYTES = 16 * 1024;
1
45
  export class AgentRunStateError extends Error {
2
46
  code = "ERR_PRISM_AGENT_RUN_STATE";
3
- constructor(message) { super(message); this.name = "AgentRunStateError"; }
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "AgentRunStateError";
50
+ }
51
+ }
52
+ /** Durable-loop contract violations: hook-less custom strategy on a durable run, invalid snapshot, or revision drift. */
53
+ export class AgentLoopStateError extends Error {
54
+ code;
55
+ constructor(code, message, options) {
56
+ super(message, options);
57
+ this.code = code;
58
+ this.name = "AgentLoopStateError";
59
+ }
4
60
  }
5
61
  export class AgentRunError extends Error {
6
62
  result;
@@ -10,6 +66,14 @@ export class AgentRunError extends Error {
10
66
  this.result = result;
11
67
  }
12
68
  }
69
+ /** Mid-run steer queue: default pending message count (fail closed at this cap). */
70
+ export const DEFAULT_MAX_PENDING_STEERS = 8;
71
+ /** Absolute pending steer count ceiling if hosts later expose overrides. */
72
+ export const HARD_MAX_PENDING_STEERS = 32;
73
+ /** Mid-run steer queue: default total UTF-8 byte budget across pending messages. */
74
+ export const DEFAULT_MAX_PENDING_STEER_BYTES = 64 * 1024;
75
+ /** Absolute pending steer byte ceiling if hosts later expose overrides. */
76
+ export const HARD_MAX_PENDING_STEER_BYTES = 256 * 1024;
13
77
  export const SESSION_ENTRY_KINDS = [
14
78
  "message",
15
79
  "event",
@@ -25,6 +89,75 @@ export const SESSION_ENTRY_SCHEMA_VERSION = 1;
25
89
  export function isSessionEntryKind(value) {
26
90
  return typeof value === "string" && SESSION_ENTRY_KIND_SET.has(value);
27
91
  }
92
+ /** Host-written `SessionRecord.metadata` / session metadata key for workspace filtering. */
93
+ export const SESSION_SEARCH_WORKSPACE_METADATA_KEY = "workspaceRoot";
94
+ export const DEFAULT_SESSION_SEARCH_LIMIT = 20;
95
+ export const HARD_MAX_SESSION_SEARCH_LIMIT = 100;
96
+ export const DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES = 4 * 1024;
97
+ export const HARD_MAX_SESSION_SEARCH_QUERY_BYTES = 16 * 1024;
98
+ export const DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES = 512;
99
+ export const HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES = 4 * 1024;
100
+ export const DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES = 1 * 1024;
101
+ export const HARD_MAX_SESSION_SEARCH_CURSOR_BYTES = 4 * 1024;
102
+ export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 1_000;
103
+ export const HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS = 5_000;
104
+ export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 10_000;
105
+ export const HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES = 50_000;
106
+ export const DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES = 8 * 1024 * 1024;
107
+ export const HARD_MAX_SESSION_SEARCH_LINEAR_BYTES = 64 * 1024 * 1024;
108
+ export const DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES = 1_000;
109
+ export const HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES = 5_000;
110
+ /**
111
+ * O(1) validation before any scan/query. Applies default page limit; rejects NaN,
112
+ * non-positive limits, oversize query/cursor/filter strings, and invalid order.
113
+ */
114
+ export function resolveSessionSearchQuery(query) {
115
+ const limit = query.limit === undefined ? DEFAULT_SESSION_SEARCH_LIMIT : query.limit;
116
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > HARD_MAX_SESSION_SEARCH_LIMIT) {
117
+ throw new TypeError(`SessionSearchQuery.limit must be a safe integer from 1 to ${HARD_MAX_SESSION_SEARCH_LIMIT}`);
118
+ }
119
+ const order = query.order ?? "desc";
120
+ if (order !== "asc" && order !== "desc") {
121
+ throw new TypeError('SessionSearchQuery.order must be "asc" or "desc"');
122
+ }
123
+ assertSearchStringBytes(query.query, "query", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
124
+ assertSearchStringBytes(query.cursor, "cursor", HARD_MAX_SESSION_SEARCH_CURSOR_BYTES);
125
+ assertSearchStringBytes(query.workspaceRoot, "workspaceRoot", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
126
+ assertSearchStringBytes(query.provider, "provider", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
127
+ assertSearchStringBytes(query.model, "model", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
128
+ assertSearchStringBytes(query.label, "label", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
129
+ assertSearchStringBytes(query.summary, "summary", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
130
+ assertSearchStringBytes(query.tenantId, "tenantId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
131
+ assertSearchStringBytes(query.accountId, "accountId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
132
+ assertSearchStringBytes(query.userId, "userId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
133
+ assertSearchStringBytes(query.fromUpdatedAt, "fromUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
134
+ assertSearchStringBytes(query.toUpdatedAt, "toUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
135
+ return { ...query, limit, order };
136
+ }
137
+ function assertSearchStringBytes(value, name, hardMax) {
138
+ if (value === undefined)
139
+ return;
140
+ if (typeof value !== "string") {
141
+ throw new TypeError(`SessionSearchQuery.${name} must be a string`);
142
+ }
143
+ // ponytail: UTF-8 byte length via TextEncoder; upgrade only if a non-Unicode host appears.
144
+ const bytes = new TextEncoder().encode(value).byteLength;
145
+ if (bytes > hardMax) {
146
+ throw new TypeError(`SessionSearchQuery.${name} exceeds ${hardMax} bytes`);
147
+ }
148
+ }
149
+ export const SESSION_SEARCH_UNSUPPORTED_CODE = "session_search_unsupported";
150
+ /** Thrown when a store opts out of `searchSessions` (memory `unsupported`, JSONL). */
151
+ export class SessionSearchUnsupportedError extends Error {
152
+ code = SESSION_SEARCH_UNSUPPORTED_CODE;
153
+ constructor(message = "session search is unsupported by this store") {
154
+ super(message);
155
+ this.name = "SessionSearchUnsupportedError";
156
+ }
157
+ }
158
+ export function isSessionSearchUnsupported(error) {
159
+ return error instanceof Error && error.code === SESSION_SEARCH_UNSUPPORTED_CODE;
160
+ }
28
161
  /** Stable error code carried by `SessionAppendConflictError`. */
29
162
  export const SESSION_APPEND_CONFLICT_CODE = "session_append_conflict";
30
163
  /**
@@ -51,4 +184,12 @@ export class SessionAppendConflictError extends Error {
51
184
  export function isSessionAppendConflict(error) {
52
185
  return error instanceof Error && error.code === SESSION_APPEND_CONFLICT_CODE;
53
186
  }
187
+ const SESSION_METADATA_KEY_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
188
+ /** Validate a top-level `SessionRecord.metadata` key used by `SessionQuery.metadataKey` filters. */
189
+ export function assertSessionMetadataKey(key) {
190
+ if (typeof key !== "string" || !SESSION_METADATA_KEY_PATTERN.test(key)) {
191
+ throw new RangeError("metadataKey must match /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/");
192
+ }
193
+ return key;
194
+ }
54
195
  //# sourceMappingURL=contracts.js.map
@@ -50,7 +50,11 @@ function parseFrontBlock(lines) {
50
50
  out.set(key, items);
51
51
  }
52
52
  else if (value.startsWith("[") && value.endsWith("]")) {
53
- out.set(key, value.slice(1, -1).split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53
+ out.set(key, value
54
+ .slice(1, -1)
55
+ .split(",")
56
+ .map((s) => s.trim())
57
+ .filter((s) => s.length > 0));
54
58
  }
55
59
  else {
56
60
  out.set(key, value);
@@ -63,7 +67,7 @@ function parseFrontBlock(lines) {
63
67
  // plain string split instead of `node:path`. Expects `.../<name>/SKILL.md`.
64
68
  function parentDirName(path) {
65
69
  const parts = path.split(/[/\\]+/).filter(Boolean);
66
- return parts.length >= 2 ? parts[parts.length - 2] : parts[parts.length - 1] ?? "";
70
+ return parts.length >= 2 ? parts[parts.length - 2] : (parts[parts.length - 1] ?? "");
67
71
  }
68
72
  const NAME_RE = /^[A-Za-z0-9 _-]+$/;
69
73
  /** Parse a `SKILL.md` into a {@link Skill}. Frontmatter keys map to `name`
@@ -4,6 +4,8 @@ import { type ProviderRegistry } from "./providers.js";
4
4
  import { type DuplicateRegistrationOptions } from "./registry-options.js";
5
5
  export interface ContributionRegistry<T> {
6
6
  register(key: string, contribution: T): void;
7
+ /** Remove a contribution; returns false when the key was not registered. */
8
+ unregister(key: string): boolean;
7
9
  get(key: string): T | undefined;
8
10
  resolve(key: string): T;
9
11
  list(): readonly T[];
@@ -9,6 +9,9 @@ export function createContributionRegistry(options = {}) {
9
9
  assertCanRegister(byKey, key, label, key, options.duplicate);
10
10
  byKey.set(key, contribution);
11
11
  },
12
+ unregister(key) {
13
+ return byKey.delete(key);
14
+ },
12
15
  get(key) {
13
16
  return byKey.get(key);
14
17
  },
@@ -0,0 +1,50 @@
1
+ import type { OwnershipScope, SessionRecord } from "./contracts.js";
2
+ /** Conversation thread lifecycle state. Archive is soft; deletion goes through persistence lifecycle. */
3
+ export type ConversationThreadState = "active" | "archived";
4
+ /** Well-known `SessionRecord.metadata` key marking conversation threads. */
5
+ export declare const CONVERSATION_METADATA_KEY = "prismConversation";
6
+ export declare const DEFAULT_MAX_CONVERSATION_CURSOR_BYTES: number;
7
+ export declare const HARD_MAX_CONVERSATION_CURSOR_BYTES: number;
8
+ export interface ConversationBranchRef {
9
+ readonly leafId: string;
10
+ readonly createdAt: string;
11
+ }
12
+ /**
13
+ * Durable user-scoped conversation thread. A thread is an ownership-scoped session
14
+ * branch plus metadata; content lives in session entries and the redacted event ledger.
15
+ */
16
+ export interface ConversationThread extends OwnershipScope {
17
+ readonly id: string;
18
+ readonly title?: string;
19
+ readonly state: ConversationThreadState;
20
+ readonly createdAt: string;
21
+ readonly updatedAt: string;
22
+ /** Branch leaves recorded by the conversation service; the entry tree remains the content source of truth. */
23
+ readonly branches: readonly ConversationBranchRef[];
24
+ /** Host-supplied create metadata; never credentials or raw transcripts. */
25
+ readonly metadata?: Readonly<Record<string, unknown>>;
26
+ }
27
+ /** Opaque thread-bound replay cursor; prevents replaying a cursor minted for another thread. */
28
+ export interface ConversationReplayCursor {
29
+ readonly v: 1;
30
+ readonly threadId: string;
31
+ /** Opaque store keyset cursor; undefined means the start of the thread. */
32
+ readonly cursor?: string;
33
+ }
34
+ export declare class ConversationError extends Error {
35
+ readonly reason: string;
36
+ readonly code = "ERR_PRISM_CONVERSATION";
37
+ constructor(message: string, reason: string);
38
+ }
39
+ export declare function encodeConversationReplayCursor(cursor: ConversationReplayCursor): string;
40
+ export declare function decodeConversationReplayCursor(encoded: string, expectedThreadId: string, maxBytes?: number): ConversationReplayCursor;
41
+ /** Project a persisted session record into a conversation thread. Undefined for non-conversation sessions. */
42
+ export declare function conversationThreadFromRecord(record: SessionRecord): ConversationThread | undefined;
43
+ /** Serialize conversation marker metadata for `SessionRecord.metadata`. */
44
+ export declare function conversationMarkerMetadata(marker: {
45
+ readonly title?: string;
46
+ readonly state: ConversationThreadState;
47
+ readonly branches?: readonly ConversationBranchRef[];
48
+ readonly requestId?: string;
49
+ readonly metadata?: Readonly<Record<string, unknown>>;
50
+ }): Readonly<Record<string, unknown>>;
@@ -0,0 +1,98 @@
1
+ /** Well-known `SessionRecord.metadata` key marking conversation threads. */
2
+ export const CONVERSATION_METADATA_KEY = "prismConversation";
3
+ export const DEFAULT_MAX_CONVERSATION_CURSOR_BYTES = 4 * 1024;
4
+ export const HARD_MAX_CONVERSATION_CURSOR_BYTES = 16 * 1024;
5
+ export class ConversationError extends Error {
6
+ reason;
7
+ code = "ERR_PRISM_CONVERSATION";
8
+ constructor(message, reason) {
9
+ super(message);
10
+ this.reason = reason;
11
+ this.name = "ConversationError";
12
+ }
13
+ }
14
+ export function encodeConversationReplayCursor(cursor) {
15
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
16
+ }
17
+ export function decodeConversationReplayCursor(encoded, expectedThreadId, maxBytes = DEFAULT_MAX_CONVERSATION_CURSOR_BYTES) {
18
+ if (typeof encoded !== "string" || encoded.length === 0) {
19
+ throw new ConversationError("Replay cursor is required", "invalid_cursor");
20
+ }
21
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes) {
22
+ throw new ConversationError("Replay cursor exceeds byte limit", "cursor_too_large");
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
27
+ }
28
+ catch {
29
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
30
+ }
31
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
32
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
33
+ }
34
+ const cursor = parsed;
35
+ if (cursor.v !== 1 || typeof cursor.threadId !== "string" || cursor.threadId.length === 0) {
36
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
37
+ }
38
+ if (cursor.threadId !== expectedThreadId) {
39
+ throw new ConversationError("Replay cursor belongs to another thread", "cursor_thread_mismatch");
40
+ }
41
+ if (cursor.cursor !== undefined && (typeof cursor.cursor !== "string" || cursor.cursor.length === 0)) {
42
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
43
+ }
44
+ return {
45
+ v: 1,
46
+ threadId: cursor.threadId,
47
+ ...(cursor.cursor === undefined ? {} : { cursor: cursor.cursor }),
48
+ };
49
+ }
50
+ /** Project a persisted session record into a conversation thread. Undefined for non-conversation sessions. */
51
+ export function conversationThreadFromRecord(record) {
52
+ const marker = record.metadata?.[CONVERSATION_METADATA_KEY];
53
+ if (!marker || typeof marker !== "object" || Array.isArray(marker))
54
+ return undefined;
55
+ const value = marker;
56
+ const branches = [];
57
+ if (Array.isArray(value.branches)) {
58
+ for (const item of value.branches) {
59
+ if (item &&
60
+ typeof item === "object" &&
61
+ typeof item.leafId === "string" &&
62
+ typeof item.createdAt === "string") {
63
+ branches.push({
64
+ leafId: item.leafId,
65
+ createdAt: item.createdAt,
66
+ });
67
+ }
68
+ }
69
+ }
70
+ const metadata = value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
71
+ ? value.metadata
72
+ : undefined;
73
+ return {
74
+ id: record.id,
75
+ ...(record.tenantId !== undefined ? { tenantId: record.tenantId } : {}),
76
+ ...(record.accountId !== undefined ? { accountId: record.accountId } : {}),
77
+ ...(record.userId !== undefined ? { userId: record.userId } : {}),
78
+ ...(typeof value.title === "string" && value.title.length > 0 ? { title: value.title } : {}),
79
+ state: value.state === "archived" ? "archived" : "active",
80
+ createdAt: record.createdAt,
81
+ updatedAt: record.updatedAt,
82
+ branches: Object.freeze(branches),
83
+ ...(metadata === undefined ? {} : { metadata }),
84
+ };
85
+ }
86
+ /** Serialize conversation marker metadata for `SessionRecord.metadata`. */
87
+ export function conversationMarkerMetadata(marker) {
88
+ return {
89
+ [CONVERSATION_METADATA_KEY]: {
90
+ ...(marker.title === undefined ? {} : { title: marker.title }),
91
+ state: marker.state,
92
+ ...(marker.branches === undefined || marker.branches.length === 0 ? {} : { branches: marker.branches }),
93
+ ...(marker.requestId === undefined ? {} : { requestId: marker.requestId }),
94
+ ...(marker.metadata === undefined ? {} : { metadata: marker.metadata }),
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=conversations.js.map
@@ -1,4 +1,4 @@
1
- import type { Credential, CredentialResolver, CredentialRequest, CredentialResolverSource, OAuthCredentialStore, OAuthCredentials, OAuthProvider } from "./contracts.js";
1
+ import type { Credential, CredentialRequest, CredentialResolver, CredentialResolverSource, OAuthCredentialStore, OAuthCredentials, OAuthProvider } from "./contracts.js";
2
2
  export type CredentialValueSource = string | (() => string | undefined | Promise<string | undefined>) | CredentialResolver;
3
3
  export interface CredentialRecord {
4
4
  readonly name: string;
@@ -10,7 +10,13 @@ export interface MemoryCredentialStore extends CredentialResolver {
10
10
  delete(request: Pick<CredentialRequest, "name" | "provider">): boolean;
11
11
  clear(): void;
12
12
  }
13
- export declare function createMemoryCredentialStore(initial?: readonly CredentialRecord[]): MemoryCredentialStore;
13
+ export interface MemoryCredentialStoreOptions {
14
+ /** When true (default, backward compatible), a provider-scoped request falls back to a
15
+ * providerless record of the same name — that record is then served to EVERY provider.
16
+ * Set false for exact-match-only resolution (strict provider scoping). */
17
+ readonly allowProviderFallback?: boolean;
18
+ }
19
+ export declare function createMemoryCredentialStore(initial?: readonly CredentialRecord[], options?: MemoryCredentialStoreOptions): MemoryCredentialStore;
14
20
  export declare function createChainedCredentialResolver(resolvers: readonly CredentialResolver[]): CredentialResolver;
15
21
  export declare function createExplicitCredentialResolver(sources: readonly CredentialResolverSource[]): CredentialResolver;
16
22
  export declare function createEnvCredentialResolver(env: Readonly<Record<string, string | undefined>>, map: Readonly<Record<string, string>>): CredentialResolver;
@@ -19,4 +25,18 @@ export declare function refreshOAuthCredential(options: {
19
25
  readonly credentials: OAuthCredentials;
20
26
  readonly store?: OAuthCredentialStore;
21
27
  }): Promise<OAuthCredentials>;
28
+ /** A credential store that can also remove entries (revocation fails closed on the delete). */
29
+ export interface RevocableOAuthCredentialStore extends OAuthCredentialStore {
30
+ delete(provider: string, accountId?: string): Promise<boolean> | boolean;
31
+ }
32
+ /**
33
+ * Revokes an OAuth credential: best-effort upstream revocation, then a mandatory local
34
+ * store delete so subsequent connector calls fail closed. The local delete is the trust
35
+ * boundary — an upstream revoke failure never leaves a stored token usable.
36
+ */
37
+ export declare function revokeOAuthCredential(options: {
38
+ readonly provider: OAuthProvider;
39
+ readonly credentials: OAuthCredentials;
40
+ readonly store?: RevocableOAuthCredentialStore;
41
+ }): Promise<void>;
22
42
  export declare function resolveCredentialValue(source: CredentialValueSource | undefined, request: CredentialRequest): Promise<string | undefined>;
@@ -1,5 +1,6 @@
1
- export function createMemoryCredentialStore(initial = []) {
1
+ export function createMemoryCredentialStore(initial = [], options = {}) {
2
2
  const records = new Map();
3
+ const allowProviderFallback = options.allowProviderFallback ?? true;
3
4
  const key = (name, provider) => `${provider ?? ""}\u0000${name}`;
4
5
  const store = {
5
6
  set(record) {
@@ -12,7 +13,10 @@ export function createMemoryCredentialStore(initial = []) {
12
13
  records.clear();
13
14
  },
14
15
  resolve(request) {
15
- return records.get(key(request.name, request.provider)) ?? records.get(key(request.name));
16
+ const exact = records.get(key(request.name, request.provider));
17
+ if (exact !== undefined || !allowProviderFallback || request.provider === undefined)
18
+ return exact;
19
+ return records.get(key(request.name));
16
20
  },
17
21
  };
18
22
  for (const record of initial)
@@ -37,7 +41,9 @@ export function createExplicitCredentialResolver(sources) {
37
41
  export function createEnvCredentialResolver(env, map) {
38
42
  return {
39
43
  resolve(request) {
40
- const envName = map[credentialMapKey(request.name, request.provider)] ?? (request.provider ? map[request.provider] : undefined) ?? map[request.name];
44
+ const envName = map[credentialMapKey(request.name, request.provider)] ??
45
+ (request.provider ? map[request.provider] : undefined) ??
46
+ map[request.name];
41
47
  const value = envName ? env[envName] : undefined;
42
48
  return value ? { type: "api_key", value, metadata: { source: "env", envName } } : undefined;
43
49
  },
@@ -48,6 +54,15 @@ export async function refreshOAuthCredential(options) {
48
54
  await options.store?.set(options.provider.id, refreshed);
49
55
  return refreshed;
50
56
  }
57
+ /**
58
+ * Revokes an OAuth credential: best-effort upstream revocation, then a mandatory local
59
+ * store delete so subsequent connector calls fail closed. The local delete is the trust
60
+ * boundary — an upstream revoke failure never leaves a stored token usable.
61
+ */
62
+ export async function revokeOAuthCredential(options) {
63
+ await options.provider.revoke?.(options.credentials);
64
+ await options.store?.delete(options.provider.id, options.credentials.accountId);
65
+ }
51
66
  function credentialMapKey(name, provider) {
52
67
  return provider ? `${provider}:${name}` : name;
53
68
  }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Device adapter contract (realtime voice / desktop OS control) — 0.0.14.
3
+ *
4
+ * Contracts + deny-by-default policy ONLY. No vendor voice or desktop-control
5
+ * implementation ships in 0.0.14 (demand-gated to 0.1.x). This module composes
6
+ * over the existing `PermissionPolicy` / `RunLimits` / redactor seams; it adds
7
+ * no second approval runtime and no device framework. Hosts implement
8
+ * `DeviceAdapter`, resolve a policy, and admit sessions through the fail-closed
9
+ * gate below. Conformance fixtures (denial/approval/stream-bounds/redaction)
10
+ * live in `runDevicePolicyConformance` for future vendor adapters to run.
11
+ */
12
+ import type { RunLimits } from "./contracts.js";
13
+ import { type SecretRedactor } from "./redaction.js";
14
+ /** Audio / screenshot / stream chunk: 1 MiB default / 8 MiB hard. */
15
+ export declare const DEFAULT_DEVICE_MAX_CHUNK_BYTES: number;
16
+ export declare const HARD_DEVICE_MAX_CHUNK_BYTES: number;
17
+ /** Concurrent device sessions per identity: 1 default / 4 hard. */
18
+ export declare const DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS = 1;
19
+ export declare const HARD_DEVICE_MAX_CONCURRENT_SESSIONS = 4;
20
+ export type DeviceKind = "voice" | "desktop-control";
21
+ export type DevicePolicyErrorCode = "ERR_PRISM_DEVICE_INPUT" | "ERR_PRISM_DEVICE_DISABLED" | "ERR_PRISM_DEVICE_APPROVAL" | "ERR_PRISM_DEVICE_SESSIONS" | "ERR_PRISM_DEVICE_CHUNK" | "ERR_PRISM_DEVICE_RUN_LIMITS";
22
+ export declare class DevicePolicyError extends Error {
23
+ readonly code: DevicePolicyErrorCode;
24
+ constructor(code: DevicePolicyErrorCode, message: string);
25
+ }
26
+ export interface DeviceStreamLimits {
27
+ readonly maxChunkBytes?: number;
28
+ readonly maxConcurrentSessions?: number;
29
+ }
30
+ /**
31
+ * Host-declared device adapter. `enabled` is deny-by-default: a device is
32
+ * admitted only when the host explicitly sets it `true` AND supplies a sandbox
33
+ * AND (when `requireApproval`) explicit per-side-effect approval.
34
+ */
35
+ export interface DeviceAdapter {
36
+ readonly kind: DeviceKind;
37
+ readonly enabled: boolean;
38
+ readonly requireApproval: boolean;
39
+ readonly limits?: DeviceStreamLimits;
40
+ /** Host-owned sandbox identifier; admission fails closed without it. */
41
+ readonly sandbox?: string;
42
+ /** Host-owned network/egress policy identifier. */
43
+ readonly network?: string;
44
+ }
45
+ export interface ResolvedDevicePolicy {
46
+ readonly kind: DeviceKind;
47
+ readonly enabled: boolean;
48
+ readonly requireApproval: boolean;
49
+ readonly maxChunkBytes: number;
50
+ readonly maxConcurrentSessions: number;
51
+ readonly sandbox?: string;
52
+ readonly network?: string;
53
+ /** Shared run accounting the device session must consume. */
54
+ readonly runLimits?: RunLimits;
55
+ }
56
+ export interface DevicePolicyOptions {
57
+ readonly maxChunkBytes?: number;
58
+ readonly maxConcurrentSessions?: number;
59
+ readonly runLimits?: RunLimits;
60
+ }
61
+ export declare function resolveDevicePolicy(adapter: DeviceAdapter, options?: DevicePolicyOptions): ResolvedDevicePolicy;
62
+ export interface DeviceAdmitRequest {
63
+ /** Explicit host approval for this device side effect. */
64
+ readonly approved: boolean;
65
+ /** Currently active device sessions for this identity. */
66
+ readonly activeSessions: number;
67
+ }
68
+ /**
69
+ * Fail-closed admission gate. Denies unless the device is explicitly enabled,
70
+ * sandboxed, approved (when required), under the concurrent-session budget, and
71
+ * bound to shared run accounting. Side effects never replay after reconnect:
72
+ * hosts must re-admit on every resume.
73
+ */
74
+ export declare function assertDeviceAdmit(policy: ResolvedDevicePolicy, request: DeviceAdmitRequest): void;
75
+ export interface DeviceChunkResult {
76
+ readonly accepted: boolean;
77
+ readonly bytes: number;
78
+ /** Present when the chunk exceeded the stream bound and was dropped. */
79
+ readonly marker?: "dropped_oversize";
80
+ }
81
+ /** Stream bound: oversize audio/screenshot/stream chunks are dropped with a marker, never forwarded. */
82
+ export declare function acceptDeviceChunk(policy: ResolvedDevicePolicy, bytes: number): DeviceChunkResult;
83
+ /** Telemetry must be metadata-safe: apply the host redactor before any emit/persist. */
84
+ export declare function redactDeviceTelemetry<T>(redactor: SecretRedactor | undefined, telemetry: T): T;
85
+ export interface DeviceConformanceResult {
86
+ readonly passed: readonly string[];
87
+ }
88
+ /**
89
+ * Conformance pair for future voice / desktop-control adapters. Runs the
90
+ * deny-by-default fixtures (denial, approval, stream bounds, session budget,
91
+ * run accounting, redaction) against a resolved policy and throws on any
92
+ * regression. Tested in 0.0.14 via fixtures only.
93
+ */
94
+ export declare function runDevicePolicyConformance(adapter: DeviceAdapter, options?: DevicePolicyOptions): DeviceConformanceResult;