@arnilo/prism 0.0.15 → 0.0.17

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 (119) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/agent-definitions.js +2 -3
  3. package/dist/agent-loops.js +12 -7
  4. package/dist/agent-run-lifecycle.d.ts +1 -2
  5. package/dist/agent-run-lifecycle.js +1 -1
  6. package/dist/agent-run-state.js +29 -4
  7. package/dist/agents.d.ts +1 -1
  8. package/dist/agents.js +163 -61
  9. package/dist/cache-helpers.js +18 -9
  10. package/dist/checkpoints.d.ts +4 -0
  11. package/dist/checkpoints.js +17 -9
  12. package/dist/cli-init.js +3 -7
  13. package/dist/cli-runner.d.ts +2 -6
  14. package/dist/cli-runner.js +71 -33
  15. package/dist/compaction.js +5 -4
  16. package/dist/config.js +7 -4
  17. package/dist/content.js +26 -24
  18. package/dist/context-budget.js +18 -11
  19. package/dist/contracts.d.ts +16 -3
  20. package/dist/contracts.js +4 -1
  21. package/dist/contribution-parsing.js +6 -2
  22. package/dist/contributions.d.ts +2 -0
  23. package/dist/contributions.js +3 -0
  24. package/dist/conversations.js +2 -1
  25. package/dist/credentials.d.ts +8 -2
  26. package/dist/credentials.js +9 -3
  27. package/dist/event-multiplexer.js +18 -4
  28. package/dist/extensions.d.ts +7 -1
  29. package/dist/extensions.js +64 -6
  30. package/dist/feedback.js +12 -10
  31. package/dist/guardrails.d.ts +1 -1
  32. package/dist/guardrails.js +26 -17
  33. package/dist/identity.js +10 -2
  34. package/dist/index.d.ts +82 -83
  35. package/dist/index.js +42 -42
  36. package/dist/input.d.ts +2 -2
  37. package/dist/input.js +50 -27
  38. package/dist/instruction-injection.d.ts +1 -1
  39. package/dist/middleware.js +9 -1
  40. package/dist/models.d.ts +2 -0
  41. package/dist/models.js +3 -0
  42. package/dist/node/agent-definitions.js +16 -8
  43. package/dist/node/contribution-discovery.d.ts +1 -2
  44. package/dist/node/contribution-discovery.js +3 -3
  45. package/dist/node/session-store-jsonl.js +10 -7
  46. package/dist/node/settings.d.ts +1 -1
  47. package/dist/node/settings.js +1 -1
  48. package/dist/node/system-project-prompts.js +2 -4
  49. package/dist/node/trust.js +1 -1
  50. package/dist/persistence-lifecycle.js +1 -3
  51. package/dist/provider-events.js +3 -1
  52. package/dist/provider-request-policy.js +3 -4
  53. package/dist/providers/media.d.ts +1 -1
  54. package/dist/providers/openai-compatible.d.ts +42 -1
  55. package/dist/providers/openai-compatible.js +110 -49
  56. package/dist/providers/openai-primitives.js +7 -7
  57. package/dist/providers/transport.d.ts +6 -0
  58. package/dist/providers/transport.js +21 -0
  59. package/dist/providers.d.ts +2 -0
  60. package/dist/providers.js +3 -0
  61. package/dist/redaction.d.ts +1 -0
  62. package/dist/redaction.js +26 -9
  63. package/dist/resources.d.ts +2 -2
  64. package/dist/resources.js +2 -2
  65. package/dist/retry.d.ts +5 -0
  66. package/dist/retry.js +8 -1
  67. package/dist/rpc.js +42 -9
  68. package/dist/run-ledger.d.ts +6 -0
  69. package/dist/run-ledger.js +16 -13
  70. package/dist/run-limits.js +49 -10
  71. package/dist/secure-agent.js +1 -1
  72. package/dist/security.js +7 -2
  73. package/dist/session-stores.d.ts +1 -1
  74. package/dist/session-stores.js +28 -24
  75. package/dist/structured-output.js +2 -2
  76. package/dist/system-prompts.js +7 -2
  77. package/dist/testing/compaction-conformance.js +5 -1
  78. package/dist/testing/extension-conformance.js +15 -3
  79. package/dist/testing/feedback.d.ts +1 -3
  80. package/dist/testing/feedback.js +1 -1
  81. package/dist/testing/persistence-schema.js +206 -37
  82. package/dist/testing/provider-conformance.js +3 -3
  83. package/dist/testing/run-ledger-conformance.js +1 -1
  84. package/dist/testing/session-store-conformance.js +1 -1
  85. package/dist/testing/tool-conformance.js +30 -5
  86. package/dist/thinking.js +4 -1
  87. package/dist/tools.d.ts +2 -2
  88. package/dist/tools.js +24 -5
  89. package/docs/0.1.0-readiness.md +139 -0
  90. package/docs/agent-events.md +2 -1
  91. package/docs/agent-session-runtime.md +2 -2
  92. package/docs/cli-rpc.md +1 -5
  93. package/docs/coding-agent-tools.md +2 -0
  94. package/docs/compaction-and-retry.md +3 -1
  95. package/docs/contribution-registries.md +1 -0
  96. package/docs/credentials-and-redaction.md +1 -1
  97. package/docs/extensions.md +1 -1
  98. package/docs/guardrails.md +13 -2
  99. package/docs/index.md +4 -2
  100. package/docs/input-and-prompt-assembly.md +3 -3
  101. package/docs/middleware-hooks.md +2 -2
  102. package/docs/migration.md +40 -0
  103. package/docs/performance.md +33 -0
  104. package/docs/providers/openai-compatible.md +28 -1
  105. package/docs/public-contracts.md +2 -2
  106. package/docs/release-and-install.md +127 -17
  107. package/docs/session-stores.md +1 -1
  108. package/package.json +14 -6
  109. package/docs/review-coverage-2026-07-14.md +0 -260
  110. package/docs/review-coverage-2026-07-15.md +0 -193
  111. package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
  112. package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
  113. package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
  114. package/docs/review-coverage-2026-07-21-phase-5.md +0 -172
  115. package/docs/review-coverage-2026-07-22-phase-6.md +0 -209
  116. package/docs/review-coverage-2026-07-22-phase-7.md +0 -173
  117. package/docs/review-coverage-2026-07-23-phase-8.md +0 -245
  118. package/docs/review-coverage-2026-07-25-phase-9.md +0 -256
  119. package/docs/review-coverage-2026-07-26-phase-10.md +0 -132
@@ -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
  },
@@ -56,7 +56,8 @@ export function conversationThreadFromRecord(record) {
56
56
  const branches = [];
57
57
  if (Array.isArray(value.branches)) {
58
58
  for (const item of value.branches) {
59
- if (item && typeof item === "object" &&
59
+ if (item &&
60
+ typeof item === "object" &&
60
61
  typeof item.leafId === "string" &&
61
62
  typeof item.createdAt === "string") {
62
63
  branches.push({
@@ -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;
@@ -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
  },
@@ -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
  }
@@ -26,11 +26,17 @@ export interface ExtensionEventBus {
26
26
  on(type: ExtensionLifecycleEventName | string, handler: ExtensionEventHandler): () => void;
27
27
  emit(event: ExtensionEvent): Promise<void>;
28
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
+ }
29
35
  export interface ExtensionKernel {
30
36
  readonly registries: ContributionRegistries;
31
37
  readonly middleware: MiddlewareRegistry;
32
38
  readonly events: ExtensionEventBus;
33
- load(extensions: readonly Extension[]): Promise<void>;
39
+ load(extensions: readonly Extension[]): Promise<LoadedExtension[]>;
34
40
  }
35
41
  export declare function createExtensionEventBus(options?: Pick<ExtensionKernelOptions, "errorPolicy" | "secrets">): ExtensionEventBus;
36
42
  export declare function createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel;
@@ -45,90 +45,148 @@ 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
167
  await assertExtensionLoadPolicy(options.loadPolicy, extension);
124
- await extension.setup(api);
168
+ await extension.setup(createApi((fn) => undo.push(fn)));
125
169
  }
126
170
  catch (error) {
171
+ // A failed setup must not leave partial contributions behind.
172
+ unwind(undo);
127
173
  if (errorPolicy === "throw")
128
174
  throw error;
129
175
  await events.emit(extensionError(error, extension.name, secrets));
176
+ continue;
130
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
+ });
131
188
  }
189
+ return loaded;
132
190
  },
133
191
  };
134
192
  }
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 };
package/dist/identity.js CHANGED
@@ -104,9 +104,17 @@ export function narrowIdentity(parent, options) {
104
104
  scopes: Object.freeze([...options.scopes]),
105
105
  credentialRefs: Object.freeze([...(options.credentialRefs ?? parent.credentialRefs ?? [])]),
106
106
  issuedAt: parent.issuedAt,
107
- ...(options.expiresAt !== undefined ? { expiresAt: options.expiresAt } : parent.expiresAt !== undefined ? { expiresAt: parent.expiresAt } : {}),
107
+ ...(options.expiresAt !== undefined
108
+ ? { expiresAt: options.expiresAt }
109
+ : parent.expiresAt !== undefined
110
+ ? { expiresAt: parent.expiresAt }
111
+ : {}),
108
112
  ...(parent.revokedAt !== undefined ? { revokedAt: parent.revokedAt } : {}),
109
- ...(options.metadata !== undefined ? { metadata: options.metadata } : parent.metadata !== undefined ? { metadata: parent.metadata } : {}),
113
+ ...(options.metadata !== undefined
114
+ ? { metadata: options.metadata }
115
+ : parent.metadata !== undefined
116
+ ? { metadata: parent.metadata }
117
+ : {}),
110
118
  verified: true,
111
119
  };
112
120
  assertIdentityActive(child, { limits: options.limits });