@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
@@ -0,0 +1,138 @@
1
+ import { redactSecrets } from "./redaction.js";
2
+ /** Audio / screenshot / stream chunk: 1 MiB default / 8 MiB hard. */
3
+ export const DEFAULT_DEVICE_MAX_CHUNK_BYTES = 1 * 1024 * 1024;
4
+ export const HARD_DEVICE_MAX_CHUNK_BYTES = 8 * 1024 * 1024;
5
+ /** Concurrent device sessions per identity: 1 default / 4 hard. */
6
+ export const DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS = 1;
7
+ export const HARD_DEVICE_MAX_CONCURRENT_SESSIONS = 4;
8
+ export class DevicePolicyError extends Error {
9
+ code;
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.code = code;
13
+ this.name = "DevicePolicyError";
14
+ }
15
+ }
16
+ function validateCap(name, value, def, hard) {
17
+ const resolved = value ?? def;
18
+ if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > hard) {
19
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_INPUT", `${name} must be a positive safe integer at most ${hard}`);
20
+ }
21
+ return resolved;
22
+ }
23
+ export function resolveDevicePolicy(adapter, options = {}) {
24
+ if (!adapter || (adapter.kind !== "voice" && adapter.kind !== "desktop-control")) {
25
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_INPUT", "device kind must be 'voice' or 'desktop-control'");
26
+ }
27
+ return {
28
+ kind: adapter.kind,
29
+ // Deny-by-default: anything but an explicit `true` resolves to disabled.
30
+ enabled: adapter.enabled === true,
31
+ // Approval is required unless the host explicitly opts out (it should not).
32
+ requireApproval: adapter.requireApproval !== false,
33
+ maxChunkBytes: validateCap("maxChunkBytes", options.maxChunkBytes ?? adapter.limits?.maxChunkBytes, DEFAULT_DEVICE_MAX_CHUNK_BYTES, HARD_DEVICE_MAX_CHUNK_BYTES),
34
+ maxConcurrentSessions: validateCap("maxConcurrentSessions", options.maxConcurrentSessions ?? adapter.limits?.maxConcurrentSessions, DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS, HARD_DEVICE_MAX_CONCURRENT_SESSIONS),
35
+ ...(adapter.sandbox ? { sandbox: adapter.sandbox } : {}),
36
+ ...(adapter.network ? { network: adapter.network } : {}),
37
+ ...(options.runLimits ? { runLimits: options.runLimits } : {}),
38
+ };
39
+ }
40
+ /**
41
+ * Fail-closed admission gate. Denies unless the device is explicitly enabled,
42
+ * sandboxed, approved (when required), under the concurrent-session budget, and
43
+ * bound to shared run accounting. Side effects never replay after reconnect:
44
+ * hosts must re-admit on every resume.
45
+ */
46
+ export function assertDeviceAdmit(policy, request) {
47
+ if (!policy.enabled) {
48
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_DISABLED", `${policy.kind} device is disabled by default`);
49
+ }
50
+ if (!policy.sandbox) {
51
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_DISABLED", `${policy.kind} device requires an explicit sandbox`);
52
+ }
53
+ if (policy.requireApproval && !request.approved) {
54
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_APPROVAL", `${policy.kind} device side effect requires approval`);
55
+ }
56
+ if (!Number.isSafeInteger(request.activeSessions) || request.activeSessions < 0) {
57
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_INPUT", "activeSessions must be a non-negative safe integer");
58
+ }
59
+ if (request.activeSessions >= policy.maxConcurrentSessions) {
60
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_SESSIONS", `concurrent ${policy.kind} sessions capped at ${policy.maxConcurrentSessions}`);
61
+ }
62
+ if (!policy.runLimits) {
63
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_RUN_LIMITS", `${policy.kind} device must consume shared RunLimits`);
64
+ }
65
+ }
66
+ /** Stream bound: oversize audio/screenshot/stream chunks are dropped with a marker, never forwarded. */
67
+ export function acceptDeviceChunk(policy, bytes) {
68
+ if (!Number.isSafeInteger(bytes) || bytes < 0) {
69
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_CHUNK", "chunk bytes must be a non-negative safe integer");
70
+ }
71
+ if (bytes > policy.maxChunkBytes) {
72
+ return { accepted: false, bytes, marker: "dropped_oversize" };
73
+ }
74
+ return { accepted: true, bytes };
75
+ }
76
+ /** Telemetry must be metadata-safe: apply the host redactor before any emit/persist. */
77
+ export function redactDeviceTelemetry(redactor, telemetry) {
78
+ return redactor ? redactor.redact(telemetry) : telemetry;
79
+ }
80
+ /**
81
+ * Conformance pair for future voice / desktop-control adapters. Runs the
82
+ * deny-by-default fixtures (denial, approval, stream bounds, session budget,
83
+ * run accounting, redaction) against a resolved policy and throws on any
84
+ * regression. Tested in 0.0.14 via fixtures only.
85
+ */
86
+ export function runDevicePolicyConformance(adapter, options = {}) {
87
+ const runLimits = options.runLimits ?? { maxTurns: 1 };
88
+ const expectThrow = (code, fn) => {
89
+ let threw = false;
90
+ try {
91
+ fn();
92
+ }
93
+ catch (error) {
94
+ threw = error instanceof DevicePolicyError && error.code === code;
95
+ }
96
+ if (!threw)
97
+ throw new DevicePolicyError(code, `conformance: expected ${code}`);
98
+ };
99
+ const passed = [];
100
+ // 1. Denial by default: a disabled adapter never admits.
101
+ const disabled = resolveDevicePolicy({ ...adapter, enabled: false }, { ...options, runLimits });
102
+ expectThrow("ERR_PRISM_DEVICE_DISABLED", () => assertDeviceAdmit(disabled, { approved: true, activeSessions: 0 }));
103
+ passed.push("denial-by-default");
104
+ // 2. Approval gate: enabled+sandboxed but unapproved denies; approved admits.
105
+ const enabled = resolveDevicePolicy({ ...adapter, enabled: true, requireApproval: true, sandbox: adapter.sandbox ?? "sandbox" }, { ...options, runLimits });
106
+ expectThrow("ERR_PRISM_DEVICE_APPROVAL", () => assertDeviceAdmit(enabled, { approved: false, activeSessions: 0 }));
107
+ assertDeviceAdmit(enabled, { approved: true, activeSessions: 0 });
108
+ passed.push("approval-gate");
109
+ // 3. Session budget: at/over the concurrent cap denies.
110
+ expectThrow("ERR_PRISM_DEVICE_SESSIONS", () => assertDeviceAdmit(enabled, { approved: true, activeSessions: enabled.maxConcurrentSessions }));
111
+ passed.push("session-budget");
112
+ // 4. Run accounting: no shared RunLimits denies.
113
+ const unaccounted = resolveDevicePolicy({ ...adapter, enabled: true, sandbox: "sandbox" }, { ...options, runLimits: undefined });
114
+ expectThrow("ERR_PRISM_DEVICE_RUN_LIMITS", () => assertDeviceAdmit(unaccounted, { approved: true, activeSessions: 0 }));
115
+ passed.push("run-accounting");
116
+ // 5. Stream bounds: oversize chunk dropped with marker; in-bound accepted.
117
+ const over = acceptDeviceChunk(enabled, enabled.maxChunkBytes + 1);
118
+ if (over.accepted || over.marker !== "dropped_oversize") {
119
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_CHUNK", "conformance: oversize chunk must be dropped");
120
+ }
121
+ if (!acceptDeviceChunk(enabled, enabled.maxChunkBytes).accepted) {
122
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_CHUNK", "conformance: in-bound chunk must accept");
123
+ }
124
+ passed.push("stream-bounds");
125
+ // 6. Redaction: telemetry passes through the host redactor (secrets stripped), passthrough when absent.
126
+ const secret = "super-secret-device-token";
127
+ const redactor = { redact: (value) => redactSecrets(value, [secret]) };
128
+ const redacted = redactDeviceTelemetry(redactor, { note: `leak ${secret}` });
129
+ if (redacted.note.includes(secret)) {
130
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_INPUT", "conformance: telemetry secret must be redacted");
131
+ }
132
+ if (redactDeviceTelemetry(undefined, { note: "ok" }).note !== "ok") {
133
+ throw new DevicePolicyError("ERR_PRISM_DEVICE_INPUT", "conformance: telemetry must pass through without a redactor");
134
+ }
135
+ passed.push("redaction");
136
+ return { passed };
137
+ }
138
+ //# sourceMappingURL=devices.js.map
@@ -13,10 +13,21 @@ export function createEventMultiplexer(options = {}) {
13
13
  isClosed = true;
14
14
  else
15
15
  options.signal?.addEventListener("abort", abort, { once: true });
16
+ // With compare set, publish always enqueues and the parked consumer is woken with
17
+ // this token instead of a value, so every event drains through the sorted queue —
18
+ // delivery order follows the comparator even when the consumer is caught up.
19
+ const WAKE = Symbol("wake");
20
+ const wakeWaiter = () => {
21
+ if (!waiter)
22
+ return;
23
+ const resolve = waiter;
24
+ waiter = undefined;
25
+ resolve({ value: WAKE, done: false });
26
+ };
16
27
  function publish(event) {
17
28
  if (isClosed)
18
29
  return;
19
- if (waiter) {
30
+ if (!options.compare && waiter) {
20
31
  const resolve = waiter;
21
32
  waiter = undefined;
22
33
  resolve({ value: event, done: false });
@@ -24,12 +35,11 @@ export function createEventMultiplexer(options = {}) {
24
35
  }
25
36
  if (queue.length < maxQueuedEvents) {
26
37
  queue.push(event);
38
+ wakeWaiter();
27
39
  return;
28
40
  }
29
41
  droppedEvents += 1;
30
- const notice = !overflowNotified && options.overflowEvent
31
- ? options.overflowEvent({ droppedEvents, maxQueuedEvents, policy: overflow })
32
- : undefined;
42
+ const notice = !overflowNotified && options.overflowEvent ? options.overflowEvent({ droppedEvents, maxQueuedEvents, policy: overflow }) : undefined;
33
43
  overflowNotified = true;
34
44
  if (overflow === "close") {
35
45
  queue.length = 0;
@@ -43,6 +53,7 @@ export function createEventMultiplexer(options = {}) {
43
53
  if (overflow === "drop_newest") {
44
54
  if (notice !== undefined)
45
55
  queue[queue.length - 1] = notice;
56
+ wakeWaiter();
46
57
  return;
47
58
  }
48
59
  queue.shift();
@@ -52,6 +63,7 @@ export function createEventMultiplexer(options = {}) {
52
63
  queue.shift();
53
64
  }
54
65
  queue.push(event);
66
+ wakeWaiter();
55
67
  }
56
68
  function observe(source, map) {
57
69
  if (isClosed)
@@ -117,6 +129,8 @@ export function createEventMultiplexer(options = {}) {
117
129
  });
118
130
  if (next.done)
119
131
  return;
132
+ if (next.value === WAKE)
133
+ continue;
120
134
  yield next.value;
121
135
  }
122
136
  }
@@ -4,22 +4,39 @@ import { type MiddlewareRegistry } from "./middleware.js";
4
4
  import { type PermissionPolicy } from "./security.js";
5
5
  export type ExtensionEventHandler = (event: ExtensionEvent) => void | Promise<void>;
6
6
  export type ExtensionErrorPolicy = "event" | "throw";
7
+ export interface ExtensionLoadPolicy {
8
+ /** When set, only listed extension names may load. */
9
+ readonly allowList?: readonly string[];
10
+ /**
11
+ * Host signature / attestation check. Return false or throw to deny.
12
+ * Unsigned extensions fail closed when this callback is provided.
13
+ */
14
+ readonly verifySignature?: (extension: Extension) => boolean | Promise<boolean>;
15
+ }
7
16
  export interface ExtensionKernelOptions {
8
17
  readonly registries?: ContributionRegistries;
9
18
  readonly middleware?: MiddlewareRegistry;
10
19
  readonly errorPolicy?: ExtensionErrorPolicy;
11
20
  readonly secrets?: readonly (string | undefined)[];
12
21
  readonly permission?: PermissionPolicy;
22
+ /** Optional allow-list / signature policy evaluated before `setup`. */
23
+ readonly loadPolicy?: ExtensionLoadPolicy;
13
24
  }
14
25
  export interface ExtensionEventBus {
15
26
  on(type: ExtensionLifecycleEventName | string, handler: ExtensionEventHandler): () => void;
16
27
  emit(event: ExtensionEvent): Promise<void>;
17
28
  }
29
+ export interface LoadedExtension {
30
+ readonly name: string;
31
+ /** Remove this extension's registry contributions and middleware/event subscriptions.
32
+ * Best-effort and idempotent; side effects outside the registries are NOT unwound. */
33
+ dispose(): void;
34
+ }
18
35
  export interface ExtensionKernel {
19
36
  readonly registries: ContributionRegistries;
20
37
  readonly middleware: MiddlewareRegistry;
21
38
  readonly events: ExtensionEventBus;
22
- load(extensions: readonly Extension[]): Promise<void>;
39
+ load(extensions: readonly Extension[]): Promise<LoadedExtension[]>;
23
40
  }
24
41
  export declare function createExtensionEventBus(options?: Pick<ExtensionKernelOptions, "errorPolicy" | "secrets">): ExtensionEventBus;
25
42
  export declare function createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel;
@@ -45,90 +45,163 @@ export function createExtensionKernel(options = {}) {
45
45
  const middleware = options.middleware ?? createMiddlewareRegistry({ ...options, onError: events.emit });
46
46
  const errorPolicy = options.errorPolicy ?? "event";
47
47
  const secrets = options.secrets ?? [];
48
- const api = {
48
+ // Per-extension tracked API: every registration records an undo so a dispose handle
49
+ // (or a failed setup) can unwind exactly what that extension added.
50
+ const createApi = (track) => ({
49
51
  registries,
50
52
  middleware,
51
- on: events.on,
53
+ on(type, handler) {
54
+ const off = events.on(type, handler);
55
+ track?.(off);
56
+ return off;
57
+ },
52
58
  emit: events.emit,
53
- use: middleware.use,
59
+ use(hook, mw) {
60
+ const off = middleware.use(hook, mw);
61
+ track?.(off);
62
+ return off;
63
+ },
54
64
  registerProvider(provider) {
55
65
  registries.providers.register(provider);
66
+ track?.(() => registries.providers.unregister(provider.id));
56
67
  },
57
68
  registerModel(model) {
58
69
  registries.models.register(model);
70
+ track?.(() => registries.models.unregister(model.provider, model.model));
59
71
  },
60
72
  registerTool(tool) {
61
73
  registries.tools.register(tool.name, tool);
74
+ track?.(() => registries.tools.unregister(tool.name));
62
75
  },
63
76
  registerContextProvider(provider) {
64
77
  registries.contextProviders.register(provider.name, provider);
78
+ track?.(() => registries.contextProviders.unregister(provider.name));
65
79
  },
66
80
  registerSkill(skill) {
67
81
  registries.skills.register(skill.name, skill);
82
+ track?.(() => registries.skills.unregister(skill.name));
68
83
  },
69
84
  registerCommand(command) {
70
85
  registries.commands.register(command.name, command);
86
+ track?.(() => registries.commands.unregister(command.name));
71
87
  },
72
88
  registerAgent(agent) {
73
89
  registries.agents.register(agent.name, agent);
90
+ track?.(() => registries.agents.unregister(agent.name));
74
91
  },
75
92
  registerInputBuilder(builder) {
76
93
  registries.inputBuilders.register(builder.name, builder);
94
+ track?.(() => registries.inputBuilders.unregister(builder.name));
77
95
  },
78
96
  registerPromptBuilder(builder) {
79
97
  registries.promptBuilders.register(builder.name, builder);
98
+ track?.(() => registries.promptBuilders.unregister(builder.name));
80
99
  },
81
100
  registerCompactionStrategy(strategy) {
82
101
  registries.compactionStrategies.register(strategy.name, strategy);
102
+ track?.(() => registries.compactionStrategies.unregister(strategy.name));
83
103
  },
84
104
  registerRetryPolicy(policy) {
85
105
  registries.retryPolicies.register(policy.name, policy);
106
+ track?.(() => registries.retryPolicies.unregister(policy.name));
86
107
  },
87
108
  registerStoreFactory(factory) {
88
109
  registries.storeFactories.register(factory.name, factory);
110
+ track?.(() => registries.storeFactories.unregister(factory.name));
89
111
  },
90
112
  registerResourceLoader(key, loader) {
91
113
  registries.resourceLoaders.register(key, loader);
114
+ track?.(() => registries.resourceLoaders.unregister(key));
92
115
  },
93
116
  registerSettingsProvider(key, provider) {
94
117
  registries.settingsProviders.register(key, provider);
118
+ track?.(() => registries.settingsProviders.unregister(key));
95
119
  },
96
120
  registerCredentialResolver(key, resolver) {
97
121
  registries.credentialResolvers.register(key, resolver);
122
+ track?.(() => registries.credentialResolvers.unregister(key));
98
123
  },
99
124
  registerProviderPackage(providerPackage) {
100
125
  registries.providerPackages.register(providerPackage.name, providerPackage);
126
+ track?.(() => registries.providerPackages.unregister(providerPackage.name));
101
127
  },
102
128
  registerAuthMethod(method) {
103
- registries.authMethods.register(authMethodKey(method), method);
129
+ const key = authMethodKey(method);
130
+ registries.authMethods.register(key, method);
131
+ track?.(() => registries.authMethods.unregister(key));
104
132
  },
105
133
  registerProviderRequestPolicy(policy) {
106
134
  registries.providerRequestPolicies.register(policy.name, policy);
135
+ track?.(() => registries.providerRequestPolicies.unregister(policy.name));
107
136
  },
108
137
  registerSystemPromptContribution(contribution) {
109
- registries.systemPromptContributions.register(systemPromptContributionKey(contribution), contribution);
138
+ const key = systemPromptContributionKey(contribution);
139
+ registries.systemPromptContributions.register(key, contribution);
140
+ track?.(() => registries.systemPromptContributions.unregister(key));
110
141
  },
111
142
  registerInstructionInjector(injector) {
112
143
  registries.instructionInjectors.register(injector.name, injector);
144
+ track?.(() => registries.instructionInjectors.unregister(injector.name));
113
145
  },
146
+ });
147
+ const unwind = (undo) => {
148
+ for (const fn of undo.reverse()) {
149
+ try {
150
+ fn();
151
+ }
152
+ catch {
153
+ // best-effort: one stuck undo must not block the rest
154
+ }
155
+ }
114
156
  };
115
157
  return {
116
158
  registries,
117
159
  middleware,
118
160
  events,
119
161
  async load(extensions) {
162
+ const loaded = [];
120
163
  for (const extension of extensions) {
164
+ const undo = [];
121
165
  try {
122
166
  await assertPermission(options.permission, { kind: "extension", action: "setup", target: extension.name });
123
- await extension.setup(api);
167
+ await assertExtensionLoadPolicy(options.loadPolicy, extension);
168
+ await extension.setup(createApi((fn) => undo.push(fn)));
124
169
  }
125
170
  catch (error) {
171
+ // A failed setup must not leave partial contributions behind.
172
+ unwind(undo);
126
173
  if (errorPolicy === "throw")
127
174
  throw error;
128
175
  await events.emit(extensionError(error, extension.name, secrets));
176
+ continue;
129
177
  }
178
+ let disposed = false;
179
+ loaded.push({
180
+ name: extension.name,
181
+ dispose() {
182
+ if (disposed)
183
+ return;
184
+ disposed = true;
185
+ unwind(undo);
186
+ },
187
+ });
130
188
  }
189
+ return loaded;
131
190
  },
132
191
  };
133
192
  }
193
+ async function assertExtensionLoadPolicy(policy, extension) {
194
+ if (!policy)
195
+ return;
196
+ if (policy.allowList && !policy.allowList.includes(extension.name)) {
197
+ throw new Error(`Extension "${extension.name}" is not allow-listed`);
198
+ }
199
+ if (policy.verifySignature) {
200
+ if (!extension.signature)
201
+ throw new Error(`Extension "${extension.name}" is unsigned`);
202
+ const ok = await policy.verifySignature(extension);
203
+ if (!ok)
204
+ throw new Error(`Extension "${extension.name}" failed signature verification`);
205
+ }
206
+ }
134
207
  //# sourceMappingURL=extensions.js.map
package/dist/feedback.js CHANGED
@@ -48,10 +48,14 @@ export async function prepareRunFeedback(input, options) {
48
48
  const tags = normalizeList(input.tags, maxTags, MAX_FEEDBACK_TAG_LENGTH, "tag");
49
49
  const scorerIds = normalizeList(input.scorerIds, maxLinks, MAX_FEEDBACK_ID_LENGTH, "scorer id", true);
50
50
  const evaluationIds = normalizeList(input.evaluationIds, maxLinks, MAX_FEEDBACK_ID_LENGTH, "evaluation id", true);
51
- if (input.rating === undefined && input.comment === undefined && tags.length === 0 && scorerIds.length === 0 && evaluationIds.length === 0) {
51
+ if (input.rating === undefined &&
52
+ input.comment === undefined &&
53
+ tags.length === 0 &&
54
+ scorerIds.length === 0 &&
55
+ evaluationIds.length === 0) {
52
56
  throw new RunFeedbackError("feedback requires rating, comment, tag, scorer, or evaluation", "ERR_PRISM_RUN_FEEDBACK_EMPTY");
53
57
  }
54
- const redact = (value) => options.redactor ? options.redactor.redact(value) : value;
58
+ const redact = (value) => (options.redactor ? options.redactor.redact(value) : value);
55
59
  const comment = input.comment === undefined ? undefined : redact(input.comment);
56
60
  const safeTags = Object.freeze(normalizeList(redact(tags), maxTags, MAX_FEEDBACK_TAG_LENGTH, "tag"));
57
61
  const metadata = input.metadata === undefined ? undefined : redact(input.metadata);
@@ -121,18 +125,16 @@ export function runFeedbackPageLimit(limit, maximum = HARD_FEEDBACK_PAGE_SIZE) {
121
125
  return pageLimit(limit, maximum);
122
126
  }
123
127
  function requireOwnership(input) {
124
- if (!input.tenantId?.trim()
125
- || (input.accountId !== undefined && !input.accountId.trim())
126
- || (input.userId !== undefined && !input.userId.trim())
127
- || (!input.accountId && !input.userId)) {
128
+ if (!input.tenantId?.trim() ||
129
+ (input.accountId !== undefined && !input.accountId.trim()) ||
130
+ (input.userId !== undefined && !input.userId.trim()) ||
131
+ (!input.accountId && !input.userId)) {
128
132
  throw new RunFeedbackError("tenantId and non-empty accountId or userId are required", "ERR_PRISM_RUN_FEEDBACK_OWNERSHIP");
129
133
  }
130
134
  return { tenantId: input.tenantId, accountId: input.accountId, userId: input.userId };
131
135
  }
132
136
  function sameOwnership(expected, actual) {
133
- return expected.tenantId === actual.tenantId
134
- && expected.accountId === actual.accountId
135
- && expected.userId === actual.userId;
137
+ return expected.tenantId === actual.tenantId && expected.accountId === actual.accountId && expected.userId === actual.userId;
136
138
  }
137
139
  function requireId(value, label) {
138
140
  if (!value || value.length > MAX_FEEDBACK_ID_LENGTH || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)) {
@@ -214,7 +216,7 @@ function freezeRecord(record) {
214
216
  });
215
217
  }
216
218
  function cloneFrozenJsonObject(value) {
217
- const cloned = JSON.parse(JSON.stringify(value));
219
+ const cloned = structuredClone(value);
218
220
  if (!cloned || typeof cloned !== "object" || Array.isArray(cloned))
219
221
  throw new RunFeedbackError("metadata must be a JSON object");
220
222
  return deepFreeze(cloned);
@@ -1,4 +1,4 @@
1
- import type { AgentEvent, GuardrailContext, GuardrailRecord, Guardrails, GuardrailStage, GuardrailValue } from "./contracts.js";
1
+ import type { AgentEvent, GuardrailContext, GuardrailRecord, GuardrailStage, Guardrails, GuardrailValue } from "./contracts.js";
2
2
  import type { SecretRedactor } from "./redaction.js";
3
3
  export declare const MAX_GUARDRAIL_CONCURRENCY = 16;
4
4
  export declare class GuardrailError extends Error {
@@ -5,11 +5,11 @@ export class GuardrailError extends Error {
5
5
  code;
6
6
  record;
7
7
  constructor(record) {
8
- super(record.action === "interrupt" ? "Guardrail interruption is unavailable" : "Guardrail blocked run");
8
+ super(record.action === "interrupt"
9
+ ? `Guardrail interruption is unavailable at stage "${record.stage}"; interrupt suspends only at the input stage of durable runs`
10
+ : "Guardrail blocked run");
9
11
  this.name = "GuardrailError";
10
- this.code = record.action === "interrupt"
11
- ? "ERR_PRISM_GUARDRAIL_INTERRUPT_UNAVAILABLE"
12
- : "ERR_PRISM_GUARDRAIL_BLOCKED";
12
+ this.code = record.action === "interrupt" ? "ERR_PRISM_GUARDRAIL_INTERRUPT_UNAVAILABLE" : "ERR_PRISM_GUARDRAIL_BLOCKED";
13
13
  this.record = record;
14
14
  }
15
15
  }
@@ -20,9 +20,7 @@ export async function runGuardrails(options) {
20
20
  return { records: [] };
21
21
  const maxConcurrency = resolveConcurrency(options.guardrails?.maxConcurrency);
22
22
  const controller = new AbortController();
23
- const signal = options.context.signal
24
- ? AbortSignal.any([options.context.signal, controller.signal])
25
- : controller.signal;
23
+ const signal = options.context.signal ? AbortSignal.any([options.context.signal, controller.signal]) : controller.signal;
26
24
  const records = new Array(guards.length);
27
25
  let next = 0;
28
26
  let stopped = false;
@@ -66,14 +64,23 @@ function stageGuards(guardrails, stage) {
66
64
  if (!guardrails)
67
65
  return [];
68
66
  switch (stage) {
69
- case "input": return (guardrails.input ?? []);
70
- case "output": return (guardrails.output ?? []);
71
- case "tool_input": return (guardrails.toolInput ?? []);
72
- case "tool_output": return (guardrails.toolOutput ?? []);
67
+ case "input":
68
+ return (guardrails.input ?? []);
69
+ case "output":
70
+ return (guardrails.output ?? []);
71
+ case "tool_input":
72
+ return (guardrails.toolInput ?? []);
73
+ case "tool_output":
74
+ return (guardrails.toolOutput ?? []);
73
75
  }
74
76
  }
75
77
  async function evaluate(guardrail, options, signal) {
76
- if (!guardrail || typeof guardrail.name !== "string" || !guardrail.name || guardrail.name.length > 128 || guardrail.stage !== options.stage || typeof guardrail.evaluate !== "function") {
78
+ if (!guardrail ||
79
+ typeof guardrail.name !== "string" ||
80
+ !guardrail.name ||
81
+ guardrail.name.length > 128 ||
82
+ guardrail.stage !== options.stage ||
83
+ typeof guardrail.evaluate !== "function") {
77
84
  return record(guardrail?.name ?? "invalid", options.stage, "tripwire", "guardrail_invalid", undefined, options.redactor);
78
85
  }
79
86
  try {
@@ -83,8 +90,12 @@ async function evaluate(guardrail, options, signal) {
83
90
  }
84
91
  return record(guardrail.name, options.stage, decision.action, decision.reason, decision.metadata, options.redactor);
85
92
  }
86
- catch {
87
- return record(guardrail.name, options.stage, "tripwire", "guardrail_failed", undefined, options.redactor);
93
+ catch (error) {
94
+ // Keep the cause diagnosable without leaking internals: message only, bounded and
95
+ // passed through the same redact+bound metadata path as guardrail-provided metadata.
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ const redacted = options.redactor?.redact(message) ?? message;
98
+ return record(guardrail.name, options.stage, "tripwire", "guardrail_failed", { error: boundText(redacted, MAX_REASON_BYTES) }, options.redactor);
88
99
  }
89
100
  }
90
101
  function resolveConcurrency(value) {
@@ -115,9 +126,7 @@ function boundedMetadata(value, redactor) {
115
126
  if (!json || byteLength(json) > MAX_METADATA_BYTES)
116
127
  return { truncated: true };
117
128
  const parsed = JSON.parse(json);
118
- return parsed && typeof parsed === "object" && !Array.isArray(parsed)
119
- ? parsed
120
- : undefined;
129
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
121
130
  }
122
131
  catch {
123
132
  return { invalid: true };
@@ -0,0 +1,92 @@
1
+ import type { OwnershipScope } from "./contracts.js";
2
+ /** Host-authenticated actor (user, service, or agent principal). */
3
+ export interface Principal {
4
+ readonly kind: "user" | "service" | "agent" | string;
5
+ readonly id: string;
6
+ readonly displayName?: string;
7
+ }
8
+ /**
9
+ * Host-verified identity context. Projects onto {@link OwnershipScope}.
10
+ * `verified: true` marks host-verifier output; trust boundaries must obtain this
11
+ * via {@link IdentityVerifier}, never by accepting caller-asserted claims alone.
12
+ */
13
+ export interface AgentIdentity {
14
+ readonly tenantId: string;
15
+ readonly accountId?: string;
16
+ readonly userId?: string;
17
+ /** Acting principal for this run/tool/delegation. */
18
+ readonly principal: Principal;
19
+ /** Human sponsor accountable for the agent lifecycle (Entra-style). */
20
+ readonly sponsor?: Principal;
21
+ /** Resource owner when distinct from sponsor. */
22
+ readonly owner?: Principal;
23
+ /** Parent principal when this identity is a narrowed delegation. */
24
+ readonly delegatedFrom?: Principal;
25
+ readonly scopes: readonly string[];
26
+ /** Late-bound credential resolver keys — never secret material. */
27
+ readonly credentialRefs?: readonly string[];
28
+ readonly issuedAt: string;
29
+ readonly expiresAt?: string;
30
+ readonly revokedAt?: string;
31
+ readonly metadata?: Readonly<Record<string, unknown>>;
32
+ readonly verified: true;
33
+ }
34
+ export interface IdentityVerifier {
35
+ verify(input: unknown): AgentIdentity | Promise<AgentIdentity>;
36
+ }
37
+ export interface AssertIdentityActiveOptions {
38
+ readonly now?: number;
39
+ /** When set, identity.tenantId must equal this value. */
40
+ readonly expectedTenantId?: string;
41
+ readonly limits?: IdentityLimits;
42
+ }
43
+ export interface NarrowIdentityOptions {
44
+ readonly scopes: readonly string[];
45
+ readonly principal?: Principal;
46
+ readonly credentialRefs?: readonly string[];
47
+ readonly expiresAt?: string;
48
+ readonly metadata?: Readonly<Record<string, unknown>>;
49
+ readonly limits?: IdentityLimits;
50
+ }
51
+ export interface IdentityLimits {
52
+ readonly maxScopes?: number;
53
+ readonly maxScopeBytes?: number;
54
+ readonly maxMetadataBytes?: number;
55
+ readonly maxCredentialRefBytes?: number;
56
+ readonly maxPrincipalIdBytes?: number;
57
+ }
58
+ export interface ResolvedIdentityLimits {
59
+ readonly maxScopes: number;
60
+ readonly maxScopeBytes: number;
61
+ readonly maxMetadataBytes: number;
62
+ readonly maxCredentialRefBytes: number;
63
+ readonly maxPrincipalIdBytes: number;
64
+ }
65
+ export declare const DEFAULT_IDENTITY_LIMITS: ResolvedIdentityLimits;
66
+ export declare const HARD_IDENTITY_LIMITS: ResolvedIdentityLimits;
67
+ export declare class IdentityError extends Error {
68
+ readonly reason: string;
69
+ readonly code = "ERR_PRISM_IDENTITY";
70
+ constructor(message: string, reason: string);
71
+ }
72
+ export declare function resolveIdentityLimits(input?: IdentityLimits): ResolvedIdentityLimits;
73
+ /** Project verified identity onto ownership scope fields used by persistence/server. */
74
+ export declare function ownershipFromIdentity(identity: AgentIdentity): OwnershipScope;
75
+ /**
76
+ * Fail closed on shape/limits/expiry/revocation/tenant mismatch.
77
+ * Sync and network-free — host {@link IdentityVerifier} owns remote checks.
78
+ */
79
+ export declare function assertIdentityActive(identity: AgentIdentity, options?: AssertIdentityActiveOptions): void;
80
+ /** Delegation may only narrow scopes; tenant and ownership ids stay immutable. */
81
+ export declare function narrowIdentity(parent: AgentIdentity, options: NarrowIdentityOptions): AgentIdentity;
82
+ /** Refuse ownership that widens or conflicts with a verified identity. */
83
+ export declare function assertIdentityMatchesOwnership(identity: AgentIdentity, ownership?: OwnershipScope): void;
84
+ /**
85
+ * Propagation check: child must share tenant, not widen scopes, and remain active.
86
+ * Used by supervisor/A2A/MCP/server when forwarding identity across a boundary.
87
+ */
88
+ export declare function assertIdentityPropagation(parent: AgentIdentity, child: AgentIdentity, options?: AssertIdentityActiveOptions): void;
89
+ /** Redacted identity refs safe for telemetry/ledger metadata (no secrets, no raw JWT). */
90
+ export declare function identityTelemetryAttributes(identity: AgentIdentity): Readonly<Record<string, string>>;
91
+ /** Resolve run identity: explicit option wins; otherwise agent default. Assert when present. */
92
+ export declare function resolveRunIdentity(runIdentity: AgentIdentity | undefined, agentIdentity: AgentIdentity | undefined, ownership: OwnershipScope | undefined, options?: AssertIdentityActiveOptions): AgentIdentity | undefined;