@arnilo/prism 0.8.0 → 0.10.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 (141) hide show
  1. package/CHANGELOG.md +62 -1
  2. package/README.md +13 -12
  3. package/dist/agent-approval.d.ts +17 -2
  4. package/dist/agent-approval.js +15 -6
  5. package/dist/agent-event-source.d.ts +9 -1
  6. package/dist/agent-event-source.js +10 -3
  7. package/dist/agent-loops.js +7 -4
  8. package/dist/agent-run-lifecycle.d.ts +15 -1
  9. package/dist/agent-run-lifecycle.js +82 -11
  10. package/dist/agent-run-state.d.ts +47 -6
  11. package/dist/agent-run-state.js +154 -6
  12. package/dist/agent-session/event-subscriber.d.ts +2 -0
  13. package/dist/agent-session/event-subscriber.js +3 -0
  14. package/dist/agent-session/helpers.js +14 -0
  15. package/dist/agent-session/session/assemble.js +281 -32
  16. package/dist/agent-session/session/persist.d.ts +11 -0
  17. package/dist/agent-session/session/persist.js +48 -16
  18. package/dist/agent-session/session/provider-round.d.ts +14 -4
  19. package/dist/agent-session/session/provider-round.js +226 -19
  20. package/dist/agent-session/session/tool-round.d.ts +2 -2
  21. package/dist/agent-session/session/tool-round.js +78 -6
  22. package/dist/agent-session/session/types.d.ts +44 -3
  23. package/dist/agent-session/session.d.ts +100 -5
  24. package/dist/agent-session/session.js +224 -13
  25. package/dist/attention-compiler.d.ts +51 -2
  26. package/dist/attention-compiler.js +282 -21
  27. package/dist/cache-helpers.d.ts +4 -2
  28. package/dist/cache-helpers.js +8 -6
  29. package/dist/checkpoint-restore.d.ts +45 -0
  30. package/dist/checkpoint-restore.js +54 -0
  31. package/dist/context-budget.d.ts +13 -1
  32. package/dist/context-budget.js +57 -4
  33. package/dist/contracts-core/agent.d.ts +52 -1
  34. package/dist/contracts-core/attention.d.ts +95 -0
  35. package/dist/contracts-core/content.d.ts +10 -0
  36. package/dist/contracts-core/extensions.d.ts +3 -0
  37. package/dist/contracts-core/guardrail-packs.d.ts +46 -0
  38. package/dist/contracts-core/guardrail-packs.js +2 -0
  39. package/dist/contracts-core/loop.d.ts +36 -0
  40. package/dist/contracts-core/provider.d.ts +30 -0
  41. package/dist/contracts-core/run-limits.d.ts +29 -1
  42. package/dist/contracts-core/session.d.ts +23 -5
  43. package/dist/contracts-core/session.js +21 -2
  44. package/dist/contracts-core/usage.d.ts +40 -0
  45. package/dist/contracts-core/usage.js +8 -0
  46. package/dist/contracts-core.d.ts +2 -0
  47. package/dist/contracts-core.js +2 -0
  48. package/dist/contracts-protocol.d.ts +81 -5
  49. package/dist/contracts-run-state.d.ts +91 -2
  50. package/dist/contributions.d.ts +2 -1
  51. package/dist/contributions.js +1 -0
  52. package/dist/extensions.d.ts +15 -1
  53. package/dist/extensions.js +68 -0
  54. package/dist/guardrail-packs/coding-standard.d.ts +3 -0
  55. package/dist/guardrail-packs/coding-standard.js +63 -0
  56. package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
  57. package/dist/guardrail-packs/destructive-commands.js +46 -0
  58. package/dist/guardrail-packs/errors.d.ts +7 -0
  59. package/dist/guardrail-packs/errors.js +9 -0
  60. package/dist/guardrail-packs/index.d.ts +4 -0
  61. package/dist/guardrail-packs/index.js +15 -0
  62. package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
  63. package/dist/guardrail-packs/secrets-hygiene.js +23 -0
  64. package/dist/guardrail-packs/types.d.ts +26 -0
  65. package/dist/guardrail-packs/types.js +2 -0
  66. package/dist/guardrail-packs/validation-respect.d.ts +3 -0
  67. package/dist/guardrail-packs/validation-respect.js +69 -0
  68. package/dist/guardrails.d.ts +61 -1
  69. package/dist/guardrails.js +377 -0
  70. package/dist/index.d.ts +16 -11
  71. package/dist/index.js +10 -7
  72. package/dist/input.d.ts +8 -1
  73. package/dist/input.js +68 -6
  74. package/dist/middleware.d.ts +37 -2
  75. package/dist/middleware.js +41 -0
  76. package/dist/node/session-store-jsonl.js +18 -3
  77. package/dist/observability.js +6 -0
  78. package/dist/provider-events.d.ts +8 -2
  79. package/dist/provider-events.js +60 -2
  80. package/dist/providers/openai-compatible.js +6 -3
  81. package/dist/run-bundle.d.ts +6 -1
  82. package/dist/run-bundle.js +5 -1
  83. package/dist/run-limits.d.ts +11 -1
  84. package/dist/run-limits.js +59 -0
  85. package/dist/session-stores.d.ts +12 -1
  86. package/dist/session-stores.js +21 -4
  87. package/dist/testing/agent-event-source-conformance.js +41 -2
  88. package/dist/testing/prefix-stability-conformance.d.ts +59 -0
  89. package/dist/testing/prefix-stability-conformance.js +172 -0
  90. package/dist/testing/session-store-conformance.d.ts +3 -2
  91. package/dist/testing/session-store-conformance.js +48 -0
  92. package/dist/tools.d.ts +5 -0
  93. package/dist/tools.js +21 -6
  94. package/dist/usage-estimation.d.ts +29 -0
  95. package/dist/usage-estimation.js +79 -0
  96. package/docs/agent-events.md +75 -4
  97. package/docs/agent-session-runtime.md +10 -6
  98. package/docs/attention-compiler.md +89 -8
  99. package/docs/caveman.md +1 -1
  100. package/docs/coding-agent-tools.md +1 -1
  101. package/docs/compaction-and-retry.md +1 -1
  102. package/docs/compaction-llm.md +2 -0
  103. package/docs/compaction-observational-memory.md +54 -7
  104. package/docs/durable-runs.md +46 -3
  105. package/docs/embeddings.md +9 -0
  106. package/docs/evaluations.md +5 -0
  107. package/docs/execution-timeline.md +79 -1
  108. package/docs/extensions.md +20 -3
  109. package/docs/guardrails.md +50 -4
  110. package/docs/hooks.md +282 -0
  111. package/docs/index.md +37 -15
  112. package/docs/input-and-prompt-assembly.md +4 -4
  113. package/docs/instruction-injection.md +1 -0
  114. package/docs/knowledge-sync.md +4 -0
  115. package/docs/live-testing.md +3 -1
  116. package/docs/memory-fabric.md +28 -0
  117. package/docs/middleware-hooks.md +90 -4
  118. package/docs/migrate-to-0.9.md +210 -0
  119. package/docs/migration.md +26 -0
  120. package/docs/multi-agent-patterns.md +25 -2
  121. package/docs/node-jsonl-session-store.md +7 -1
  122. package/docs/observability.md +7 -3
  123. package/docs/options-index.md +4 -1
  124. package/docs/policy-and-audit.md +26 -1
  125. package/docs/prefix-stability-conformance.md +143 -0
  126. package/docs/provider-caching.md +4 -4
  127. package/docs/provider-conformance.md +16 -0
  128. package/docs/provider-packages.md +20 -20
  129. package/docs/public-contracts.md +3 -2
  130. package/docs/rag.md +188 -3
  131. package/docs/release-and-install.md +45 -40
  132. package/docs/runs-and-usage.md +56 -10
  133. package/docs/scoped-agent-memory.md +270 -0
  134. package/docs/scoped-memory.md +138 -0
  135. package/docs/session-store-conformance.md +1 -2
  136. package/docs/session-stores.md +17 -17
  137. package/docs/supervisors.md +32 -12
  138. package/docs/tools.md +18 -1
  139. package/docs/wiki.md +4 -2
  140. package/docs/workflows.md +5 -0
  141. package/package.json +8 -2
@@ -56,7 +56,26 @@ export function resolveSessionSearchQuery(query) {
56
56
  assertSearchStringBytes(query.userId, "userId", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
57
57
  assertSearchStringBytes(query.fromUpdatedAt, "fromUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
58
58
  assertSearchStringBytes(query.toUpdatedAt, "toUpdatedAt", HARD_MAX_SESSION_SEARCH_QUERY_BYTES);
59
- return { ...query, limit, order };
59
+ return { ...query, limit, order, kind: resolveSessionSearchKinds(query.kind) };
60
+ }
61
+ /** Normalize `kind` to concrete entry kinds (`undefined` = any). Unknown or mixed `"any"` fails closed. */
62
+ function resolveSessionSearchKinds(kind) {
63
+ if (kind === undefined || kind === "any")
64
+ return undefined;
65
+ const values = typeof kind === "string" ? [kind] : kind;
66
+ const kinds = [];
67
+ for (const value of values) {
68
+ if (value === "any")
69
+ throw new TypeError('SessionSearchQuery.kind cannot mix "any" with entry kinds');
70
+ if (!isSessionEntryKind(value)) {
71
+ throw new TypeError(`SessionSearchQuery.kind must be a session entry kind or "any"; got ${JSON.stringify(value)}`);
72
+ }
73
+ if (!kinds.includes(value))
74
+ kinds.push(value);
75
+ }
76
+ if (kinds.length === 0)
77
+ throw new TypeError("SessionSearchQuery.kind must name at least one entry kind");
78
+ return kinds;
60
79
  }
61
80
  function assertSearchStringBytes(value, name, hardMax) {
62
81
  if (value === undefined)
@@ -71,7 +90,7 @@ function assertSearchStringBytes(value, name, hardMax) {
71
90
  }
72
91
  }
73
92
  export const SESSION_SEARCH_UNSUPPORTED_CODE = "session_search_unsupported";
74
- /** Thrown when a store opts out of `searchSessions` (memory `unsupported`, JSONL). */
93
+ /** Thrown when a store opts out of `searchSessions` (memory `sessionSearchMode: "unsupported"`). */
75
94
  export class SessionSearchUnsupportedError extends Error {
76
95
  code = SESSION_SEARCH_UNSUPPORTED_CODE;
77
96
  constructor(message = "session search is unsupported by this store") {
@@ -0,0 +1,40 @@
1
+ /** Contracts-core usage family (plan 091 Task 1): labeled token-estimation types.
2
+ *
3
+ * Estimation exists for the "missing usage is never zero usage" accounting rule:
4
+ * when a provider reports no usage, a host may show a labeled approximation. An
5
+ * estimate is never provider truth — reported usage always wins and is never
6
+ * overwritten by one of these. */
7
+ /** Model families with a chars/token table (`MODEL_FAMILY_TOKENS`). `unknown`
8
+ * is the conservative fallback used when a model id/name matches no family. */
9
+ export type ModelFamily = "anthropic" | "openai" | "google" | "deepseek" | "openrouter-generic" | "mistral" | "unknown";
10
+ /** Confidence label on an estimated token count. `high` is reserved for a real
11
+ * tokenizer (Prism ships none); the calibrated family tables are `medium`, and
12
+ * the unknown-family fallback is `low`. */
13
+ export type TokenEstimateConfidence = "high" | "medium" | "low";
14
+ /** Labeled token estimate. Never conflate with `Usage`: estimates are
15
+ * approximations for context metering and labeling, reported usage is provider
16
+ * truth for billing. */
17
+ export interface TokenEstimate {
18
+ readonly tokens: number;
19
+ readonly confidence: TokenEstimateConfidence;
20
+ /** Coarse `confidence === "low"` flag for UI badges and host-side labeling. */
21
+ readonly lowConfidence: boolean;
22
+ }
23
+ /**
24
+ * Context-fill read for hosts (plan 091 Task 2): the latest provider turn's input
25
+ * tokens with their provenance, plus the cap/budget resolved the same way the
26
+ * turn-budget snapshot resolves them. `source: "reported"` means the provider
27
+ * reported those tokens; `"estimated"` means they are a labeled approximation
28
+ * (never billing, never conflated with reported usage). Cap/budget/ratio are
29
+ * absent when the model or run cannot derive them.
30
+ */
31
+ export interface ContextMeter {
32
+ readonly inputTokens: number;
33
+ readonly source: "reported" | "estimated";
34
+ /** Per-request input cap from the model window and `attentionCompiler` reserve. */
35
+ readonly inputCap?: number;
36
+ /** Cumulative run input budget (`RunLimits.maxInputTokens`); absent without run limits. */
37
+ readonly runInputBudget?: number;
38
+ /** `inputTokens / inputCap`; absent when no cap is derivable. */
39
+ readonly usedRatio?: number;
40
+ }
@@ -0,0 +1,8 @@
1
+ /** Contracts-core usage family (plan 091 Task 1): labeled token-estimation types.
2
+ *
3
+ * Estimation exists for the "missing usage is never zero usage" accounting rule:
4
+ * when a provider reports no usage, a host may show a labeled approximation. An
5
+ * estimate is never provider truth — reported usage always wins and is never
6
+ * overwritten by one of these. */
7
+ export {};
8
+ //# sourceMappingURL=usage.js.map
@@ -9,6 +9,7 @@ export * from "./contracts-core/compaction.js";
9
9
  export * from "./contracts-core/content.js";
10
10
  export * from "./contracts-core/embeddings.js";
11
11
  export * from "./contracts-core/extensions.js";
12
+ export * from "./contracts-core/guardrail-packs.js";
12
13
  export * from "./contracts-core/images.js";
13
14
  export * from "./contracts-core/loop.js";
14
15
  export * from "./contracts-core/moderation.js";
@@ -19,4 +20,5 @@ export * from "./contracts-core/run-limits.js";
19
20
  export * from "./contracts-core/session.js";
20
21
  export * from "./contracts-core/speech.js";
21
22
  export * from "./contracts-core/transcription.js";
23
+ export * from "./contracts-core/usage.js";
22
24
  export * from "./contracts-core/video.js";
@@ -5,6 +5,7 @@ export * from "./contracts-core/compaction.js";
5
5
  export * from "./contracts-core/content.js";
6
6
  export * from "./contracts-core/embeddings.js";
7
7
  export * from "./contracts-core/extensions.js";
8
+ export * from "./contracts-core/guardrail-packs.js";
8
9
  export * from "./contracts-core/images.js";
9
10
  export * from "./contracts-core/loop.js";
10
11
  export * from "./contracts-core/moderation.js";
@@ -15,5 +16,6 @@ export * from "./contracts-core/run-limits.js";
15
16
  export * from "./contracts-core/session.js";
16
17
  export * from "./contracts-core/speech.js";
17
18
  export * from "./contracts-core/transcription.js";
19
+ export * from "./contracts-core/usage.js";
18
20
  export * from "./contracts-core/video.js";
19
21
  //# sourceMappingURL=contracts-core.js.map
@@ -1,4 +1,6 @@
1
- import type { AgentLoopOptions, AgentLoopStrategy, ArtifactValidation, CompactionOptions, ContentBlock, ErrorInfo, GuardrailRecord, Guardrails, InstructionInjector, JsonObject, Message, ModelConfig, OwnershipScope, ProviderRequestOptions, ProviderRequestPolicy, ProviderResolver, RetryOptions, RunLimitBreach, RunLimits, Skill, SubscriberOverflowPolicy, SystemPromptConfig, ToolCallAuthority, ToolCallContent, TurnPolicyOptions, Usage } from "./contracts-core.js";
1
+ import type { CacheUsageReport } from "./cache-helpers.js";
2
+ import type { CheckpointRestoreAudit } from "./checkpoint-restore.js";
3
+ import type { AgentLoopOptions, AgentLoopStrategy, ArtifactValidation, BudgetAxisUsage, BudgetConsumedCounters, CompactionOptions, ContentBlock, ErrorInfo, GuardrailRecord, Guardrails, InstructionInjector, JsonObject, Message, ModelConfig, OwnershipScope, ProviderRequestOptions, ProviderRequestPolicy, ProviderResolver, ProviderStopReason, RetryOptions, RunLimitBreach, RunLimitName, RunLimits, Skill, StopHook, SubscriberOverflowPolicy, SystemPromptConfig, ToolCallAuthority, ToolCallContent, ToolCallSummary, TurnBudgets, TurnPolicyOptions, Usage } from "./contracts-core.js";
2
4
  import type { AgentRunInterruption, AgentRunStateOptions } from "./contracts-run-state.js";
3
5
  import type { SecretRedactor } from "./redaction.js";
4
6
  import type { ToolValidator } from "./tools.js";
@@ -28,6 +30,7 @@ export type ProviderEvent = {
28
30
  } | {
29
31
  readonly type: "done";
30
32
  readonly usage?: Usage;
33
+ readonly stopReason?: ProviderStopReason;
31
34
  } | {
32
35
  readonly type: "error";
33
36
  readonly error: ErrorInfo;
@@ -108,6 +111,10 @@ export interface RunOptions {
108
111
  readonly toolNames?: readonly string[];
109
112
  /** Tools disclosure: "all" (default) sends every active tool schema; "search" sends top-k + the generated `search_tools` tool. */
110
113
  readonly toolsDisclosure?: import("./tool-search.js").ToolsDisclosure;
114
+ /** Per-turn restrictive allow-list over the run grant. Overrides `AgentConfig.toolNarrowing`. */
115
+ readonly toolNarrowing?: import("./contracts-core/agent.js").ToolNarrowing;
116
+ /** Opt-in: tools hidden this turn stay callable by name (default off). Overrides agent config. */
117
+ readonly allowHiddenToolCalls?: true;
111
118
  readonly toolsSearch?: import("./tool-search.js").ToolsSearchOptions;
112
119
  /** Opt-in projection-only fold for aged large tool results in provider view; store untouched. */
113
120
  readonly toolResultFold?: import("./tool-result-fold.js").ToolResultFoldOptions;
@@ -130,6 +137,8 @@ export interface RunOptions {
130
137
  * evaluated before every provider request. Omitted → no callback runs.
131
138
  */
132
139
  readonly turnPolicy?: TurnPolicyOptions;
140
+ /** Appended to agent-level stop hooks for this run (plan 106 R1). */
141
+ readonly stopHooks?: readonly StopHook[];
133
142
  }
134
143
  export interface ProviderTurnMetadata {
135
144
  readonly providerId: string;
@@ -140,6 +149,17 @@ export interface ProviderTurnMetadata {
140
149
  readonly httpStatus?: number;
141
150
  readonly rateLimitRemaining?: number;
142
151
  readonly rateLimitResetMs?: number;
152
+ /** Why the provider turn stopped (plan 087 T1); present on `provider_turn_finished` only. */
153
+ readonly stopReason?: ProviderStopReason;
154
+ /** Effective budget state at turn end (plan 087 T1); present on `provider_turn_finished` only. */
155
+ readonly budgets?: TurnBudgets;
156
+ /** Provider-reported cache usage and derived hit rate; absent when cache usage is unknown. */
157
+ readonly cache?: CacheUsageReport;
158
+ /** Effective tool menu this turn. Names hashed in request order; never includes args. */
159
+ readonly tools?: {
160
+ readonly count: number;
161
+ readonly idsHash: string;
162
+ };
143
163
  }
144
164
  export interface ToolExecutionMetadata {
145
165
  readonly durationMs: number;
@@ -175,9 +195,20 @@ export interface DelegatedAgentStep {
175
195
  readonly label?: string;
176
196
  };
177
197
  }
178
- /** Why a run stopped cleanly. `host_policy` is a `RunOptions.turnPolicy` stop; the rest are loop ceilings (F4). */
179
- export type AgentFinishReason = "turn_limit" | "token_limit" | "refusal" | "host_policy";
180
- export type AgentEvent = {
198
+ /** Why a run stopped cleanly. `host_policy` is a `RunOptions.turnPolicy` stop, `hook_limit` a stop-hook continuation cap; the rest are loop ceilings (F4). */
199
+ export type AgentFinishReason = "turn_limit" | "token_limit" | "refusal" | "host_policy" | "hook_limit";
200
+ /**
201
+ * Origin of an agent event forwarded from a delegated child (supervisor child-event passthrough).
202
+ * Present only on child events routed onto a parent stream; absent on a session's own events.
203
+ */
204
+ export interface ChildEventOrigin {
205
+ readonly childId: string;
206
+ readonly delegationId: string;
207
+ /** Delegation depth: 1 is a direct child of the hosting supervisor. */
208
+ readonly depth: number;
209
+ }
210
+ /** Payload union of every agent event; the exported `AgentEvent` adds the optional child origin tag. */
211
+ type AgentEventPayload = {
181
212
  readonly type: "agent_started";
182
213
  readonly sessionId: string;
183
214
  readonly runId: string;
@@ -186,7 +217,7 @@ export type AgentEvent = {
186
217
  readonly sessionId: string;
187
218
  readonly runId: string;
188
219
  readonly usage?: Usage;
189
- /** Why the loop stopped, when a limit/ceiling or a host turn policy ended the run cleanly (F4). Absent = natural end. */
220
+ /** Why the loop stopped, when a limit/ceiling, a host turn policy, or a stop-hook continuation cap ended the run cleanly (F4). Absent = natural end. */
190
221
  readonly finishReason?: AgentFinishReason;
191
222
  /** Host stop detail from `TurnPolicyOptions.stop` (≤256 bytes, redacted). Present only with `finishReason: "host_policy"`. */
192
223
  readonly stopDetail?: string;
@@ -201,6 +232,8 @@ export type AgentEvent = {
201
232
  readonly sessionId: string;
202
233
  readonly runId: string;
203
234
  readonly version: number;
235
+ /** Plan 094 Task 3: audit of the external-state restore hooks that ran before this claim. */
236
+ readonly restore?: CheckpointRestoreAudit;
204
237
  } | {
205
238
  readonly type: "agent_denied";
206
239
  readonly sessionId: string;
@@ -217,6 +250,17 @@ export type AgentEvent = {
217
250
  readonly sessionId: string;
218
251
  readonly runId: string;
219
252
  readonly turn: number;
253
+ } | {
254
+ /**
255
+ * Host middleware completed this turn without a provider request (plan 096). No `usage` field:
256
+ * a deterministic turn has no provider cost, so accounting must never zero-fill one.
257
+ */
258
+ readonly type: "deterministic_turn";
259
+ readonly sessionId: string;
260
+ readonly runId: string;
261
+ readonly turn: number;
262
+ /** Answering middleware id (provenance); ids only, never free host code. */
263
+ readonly middleware: string;
220
264
  } | {
221
265
  readonly type: "provider_turn_started";
222
266
  readonly sessionId: string;
@@ -281,6 +325,13 @@ export type AgentEvent = {
281
325
  readonly reason: string;
282
326
  readonly error: ErrorInfo;
283
327
  readonly metadata: ToolExecutionMetadata;
328
+ } | {
329
+ /** Host `toolNarrowing` asked for names outside the run grant; those names were dropped. */
330
+ readonly type: "tool_narrowing_clamped";
331
+ readonly sessionId: string;
332
+ readonly runId: string;
333
+ readonly turn: number;
334
+ readonly dropped: readonly string[];
284
335
  } | {
285
336
  readonly type: "guardrail_decision";
286
337
  readonly sessionId: string;
@@ -293,6 +344,16 @@ export type AgentEvent = {
293
344
  readonly sessionId: string;
294
345
  readonly runId: string;
295
346
  readonly breach: RunLimitBreach;
347
+ } | {
348
+ /** Terminal attribution for a run that died on a run limit (plan 087 T2): which axis fired,
349
+ * counters at exhaustion, how close the other axes were, and hashes of recent tool calls. */
350
+ readonly type: "budget_exhausted";
351
+ readonly sessionId: string;
352
+ readonly runId: string;
353
+ readonly limit: RunLimitName;
354
+ readonly consumed: BudgetConsumedCounters;
355
+ readonly closestOtherAxes: readonly BudgetAxisUsage[];
356
+ readonly recentToolCalls: readonly ToolCallSummary[];
296
357
  } | {
297
358
  readonly type: "queue_updated";
298
359
  readonly sessionId: string;
@@ -383,6 +444,14 @@ export type AgentEvent = {
383
444
  readonly attempt: number;
384
445
  readonly result: ArtifactValidation;
385
446
  };
447
+ /**
448
+ * One agent event. `child` is set only when the event was forwarded from a delegated child
449
+ * (e.g. supervisor `report: "stream"` passthrough), so hosts can route it onto a parent stream
450
+ * without per-event-type special cases. It never replaces the event's own `sessionId`/`runId`.
451
+ */
452
+ export type AgentEvent = AgentEventPayload & {
453
+ readonly child?: ChildEventOrigin;
454
+ };
386
455
  export type ToolEffectKind = "none" | "local_mutation" | "external_mutation";
387
456
  export type ToolEffectIdempotency = "none" | "optional" | "required" | "tool_managed" | "unsupported";
388
457
  /** Static or validated-argument classification of one tool call's side-effect behavior. */
@@ -688,4 +757,11 @@ export interface ProviderTurnResult {
688
757
  readonly messageId?: string;
689
758
  readonly started: boolean;
690
759
  readonly usage?: Usage;
760
+ /**
761
+ * Provenance for turns that did not come from the provider (plan 096):
762
+ * `{ deterministic: { middleware } }`. Copied onto the assistant `Message.metadata`, so it
763
+ * serializes with the transcript and survives replay. Absent for provider turns.
764
+ */
765
+ readonly metadata?: Readonly<Record<string, unknown>>;
691
766
  }
767
+ export {};
@@ -1,4 +1,5 @@
1
- import type { AgentSessionCloneOptions, AgentSessionForkOptions, CheckpointStore, CompactionOptions, CompactionResult, ContentBlock, ErrorInfo, JsonObject, JsonValue, Message, ModelConfig, OwnershipScope, RunLimitBreach, SessionEntry, SubscribeOptions, ToolCallContent, Usage } from "./contracts-core.js";
1
+ import type { CheckpointRestoreHook } from "./checkpoint-restore.js";
2
+ import type { AgentSessionCloneOptions, AgentSessionForkOptions, CheckpointRecord, CheckpointStore, CompactionOptions, CompactionResult, ContentBlock, ContextMeter, ErrorInfo, GuardrailPackRef, JsonObject, JsonValue, Message, ModelConfig, OwnershipScope, RunLimitBreach, SessionEntry, SubscribeOptions, ToolCallContent, Usage } from "./contracts-core.js";
2
3
  import type { AgentEvent, AgentFinishReason, RunOptions, ToolEffectKind } from "./contracts-protocol.js";
3
4
  export type AgentRunStatus = "succeeded" | "failed" | "aborted" | "suspended" | "denied";
4
5
  export type AgentRunInterruptionKind = "input_guardrail" | "tool_approval" | "elicitation";
@@ -26,6 +27,16 @@ export interface PendingDecision {
26
27
  readonly reason: string;
27
28
  /** Typed payload contract for elicitation decisions. */
28
29
  readonly elicitationSchema?: JsonObject;
30
+ /**
31
+ * Plan 104 T3: pack `ask` rule that gated this call as `pack:<pack>/<rule>` (bounded by the
32
+ * compile-time id limits), present only when a pack rule raised the decision.
33
+ */
34
+ readonly guardrail?: string;
35
+ /** Plan 104 T3: machine-readable ids behind `guardrail`, so a host never parses the name. */
36
+ readonly guardrailRule?: {
37
+ readonly pack: string;
38
+ readonly rule: string;
39
+ };
29
40
  /** Delegation chain, root-first; core-written, never client-supplied. */
30
41
  readonly attribution?: {
31
42
  readonly path: readonly string[];
@@ -37,6 +48,8 @@ export interface AgentRunInterruption {
37
48
  readonly reason: string;
38
49
  readonly toolCallId?: string;
39
50
  readonly toolName?: string;
51
+ /** Plan 104 T3: the pack rule that raised this suspension (`pack:<pack>/<rule>`), when one did. */
52
+ readonly guardrail?: string;
40
53
  /** All unresolved approval requests of this suspension; absent for legacy single approvals. */
41
54
  readonly pendingDecisions?: readonly PendingDecision[];
42
55
  }
@@ -139,6 +152,15 @@ export declare const HARD_MAX_ACTION_CONSTRAINTS = 64;
139
152
  export declare const MAX_ATTRIBUTION_DEPTH = 8;
140
153
  export declare const MAX_ACTION_CONSTRAINT_BYTES: number;
141
154
  export declare const HARD_MAX_ACTION_CONSTRAINT_BYTES: number;
155
+ /**
156
+ * Opaque host sidecar pinned to one checkpoint *record* (git commit, document version,
157
+ * workspace fingerprint) — never part of the run-state value, so it costs no `maxStateBytes`
158
+ * budget and is invisible to state parsing. Bounded to `MAX_AGENT_RUN_METADATA_BYTES` (4 KiB)
159
+ * and redacted like the state value at every write.
160
+ */
161
+ export type AgentRunCheckpointMetadata = Readonly<Record<string, string>>;
162
+ /** Host source for checkpoint sidecar metadata: a fixed map or a live provider resolved per write. */
163
+ export type AgentRunCheckpointMetadataSource = AgentRunCheckpointMetadata | (() => AgentRunCheckpointMetadata | undefined);
142
164
  export interface AgentRunStateOptions {
143
165
  readonly checkpoints: CheckpointStore;
144
166
  /** Host-authored immutable revision required for durable runs. */
@@ -156,6 +178,12 @@ export interface AgentRunStateOptions {
156
178
  readonly checkpointPolicy?: "decision" | "every-turn";
157
179
  /** Suspend every tool call before its side effect. */
158
180
  readonly interruptBeforeTool?: boolean;
181
+ /**
182
+ * Sidecar metadata written with every checkpoint of this run (and carried into a resumed
183
+ * run). A provider is resolved at each checkpoint write, so a host closure can pin state
184
+ * that moves mid-run (e.g. the current git commit). Absent = records stay byte-identical.
185
+ */
186
+ readonly checkpointMetadata?: AgentRunCheckpointMetadataSource;
159
187
  readonly maxStateBytes?: number;
160
188
  readonly fencingToken?: number;
161
189
  /** Enables sticky auto-apply when a nested suspension first surfaces during this run. */
@@ -204,6 +232,23 @@ export interface AgentRunResume {
204
232
  /** Batch decision path; exactly one of decision/decisions. Applied as one atomic CAS transition. */
205
233
  readonly decisions?: readonly RunDecision[];
206
234
  }
235
+ /**
236
+ * Checkpoint handed to a restore hook (plan 094 Task 3). `checkpoint.value` is the raw stored
237
+ * run-state value; `metadata` is the redacted, bounded sidecar map hosts write via
238
+ * `AgentRunStateOptions.checkpointMetadata`.
239
+ */
240
+ export interface AgentCheckpointRestoreContext {
241
+ readonly runId: string;
242
+ readonly sessionId: string;
243
+ /** Version of the checkpoint being claimed; a hook may pass it to an external system's own CAS. */
244
+ readonly version: number;
245
+ /** State being claimed: `running` for crash recovery, `suspended` for a decision resume. */
246
+ readonly status: AgentRunStatus | "running";
247
+ readonly metadata?: AgentRunCheckpointMetadata;
248
+ readonly checkpoint: CheckpointRecord;
249
+ }
250
+ /** Host code restoring one external layer before a durable resume applies. */
251
+ export type AgentCheckpointRestoreHook = CheckpointRestoreHook<AgentCheckpointRestoreContext>;
207
252
  export interface AgentRunResumeOptions {
208
253
  readonly checkpoints: CheckpointStore;
209
254
  /** Current host-authored revision; must exactly match the checkpoint. */
@@ -224,6 +269,20 @@ export interface AgentRunResumeOptions {
224
269
  readonly persistSessionState?: boolean;
225
270
  /** Opt-in (plan 018 Task 6): restore persisted loaded-skill bodies (requires `persistSessionState` too). */
226
271
  readonly includeSkillBodies?: boolean;
272
+ /**
273
+ * Checkpoint sidecar metadata for the claim write (and the resumed run's later checkpoints).
274
+ * Absent = the record's existing metadata is preserved unchanged.
275
+ */
276
+ readonly checkpointMetadata?: AgentRunCheckpointMetadataSource;
277
+ /**
278
+ * Plan 094 Task 3: external-state restore hooks. Every hook must succeed (sequentially, each
279
+ * within `restoreHookTimeoutMs`) before the claim write and the conversation restore apply;
280
+ * the first failure throws `CheckpointRestoreError` naming the hook and leaves the checkpoint
281
+ * suspended. Hosts that register hooks on the lifecycle instead pass them once there.
282
+ */
283
+ readonly restoreHooks?: readonly AgentCheckpointRestoreHook[];
284
+ /** Per-hook restore ceiling in ms; defaults to `DEFAULT_CHECKPOINT_RESTORE_TIMEOUT_MS`. */
285
+ readonly restoreHookTimeoutMs?: number;
227
286
  }
228
287
  /** Bounded live-event options for `resumeAgentRunStream()`; `signal` is inherited from the base resume options. */
229
288
  export interface AgentRunResumeStreamOptions extends AgentRunResumeOptions, SubscribeOptions {
@@ -235,6 +294,8 @@ export interface AgentRunRef {
235
294
  export interface AgentRunStatusResult {
236
295
  readonly state: AgentRunState;
237
296
  readonly version: number;
297
+ /** Checkpoint sidecar metadata; absent when the record carries none (or carries only malformed entries). */
298
+ readonly metadata?: AgentRunCheckpointMetadata;
238
299
  }
239
300
  export declare class AgentRunStateError extends Error {
240
301
  readonly code = "ERR_PRISM_AGENT_RUN_STATE";
@@ -301,6 +362,12 @@ export interface SteerOptions {
301
362
  }
302
363
  export interface AgentSession {
303
364
  readonly id: string;
365
+ /**
366
+ * Plan 104 Task 2: guardrail pack refs this session enforces (the restored rows after a durable
367
+ * resume); `undefined` when it enforces none. Pass to `snapshotRunBundle({ packs })` for the
368
+ * recorded identity that matches enforcement.
369
+ */
370
+ readonly guardrailPackRefs?: readonly GuardrailPackRef[];
304
371
  /** Current branch leaf entry id; advances on every append/run and is re-pointed by `checkout`.
305
372
  * Undefined until the first entry lands (a fresh session with no history). */
306
373
  readonly leafId: string | undefined;
@@ -312,13 +379,35 @@ export interface AgentSession {
312
379
  * Fails closed when no run is active or the pending queue exceeds caps.
313
380
  */
314
381
  steer(input: string | Message | readonly Message[], options?: SteerOptions): void;
315
- /** Subscribe first, then start exactly one run and yield only that run's events until it terminates. */
382
+ /**
383
+ * Subscribe first, then start exactly one run and yield only that run's events until it terminates.
384
+ * This subscription belongs to `stream()`: it is closed when the owned run settles (so a pre-flight
385
+ * rejection unblocks the consumer instead of parking it behind a run that never emits).
386
+ */
316
387
  stream(input: string | Message | readonly Message[], options?: RunOptions & SubscribeOptions): AsyncIterable<AgentEvent>;
317
388
  compact(options?: CompactionOptions): Promise<CompactionResult>;
389
+ /**
390
+ * Subscribe to this session's live events. A run-scoped subscriber (default) is closed when the run
391
+ * ends, suspends, or is denied; `SubscribeOptions.acrossRuns: true` keeps it open across runs until
392
+ * `subscription.close()`, `closeSubscribers()`, or a queue overflow closes it.
393
+ */
318
394
  subscribe(options?: SubscribeOptions): AsyncIterable<AgentEvent>;
395
+ /**
396
+ * Plan 106 R2: session teardown. Dispatches `session_shutdown` middleware once (idempotent), then
397
+ * closes every subscriber (`acrossRuns` included); calling it twice is a no-op. Call after the
398
+ * active run settles. Subscriber-only teardown stays `break`ing the `for await` (or calling the
399
+ * iterator's `return()`). See [Agent/session runtime](agent-session-runtime.md).
400
+ */
401
+ close(): Promise<void>;
319
402
  abort(reason?: unknown): void;
320
403
  entries(): Promise<readonly SessionEntry[]>;
321
404
  checkout(leafId?: string): Promise<void>;
405
+ /**
406
+ * Context-fill read (plan 091 T2): latest provider turn's input tokens
407
+ * (reported or labeled estimate) plus the resolved per-request cap, run input
408
+ * budget, and used ratio. Before any provider turn it estimates stored history.
409
+ */
410
+ contextMeter(): ContextMeter;
322
411
  fork(options?: AgentSessionForkOptions): AgentSession;
323
412
  clone(options?: AgentSessionCloneOptions): Promise<AgentSession>;
324
413
  }
@@ -1,4 +1,4 @@
1
- import type { AgentDefinition, AuthMethod, CommandDefinition, CompactionStrategy, ContextProvider, CredentialResolver, DiscoveredContribution, InputBuilder, InstructionInjector, PromptBuilder, ProviderPackage, ProviderRequestPolicy, ResourceLoader, RetryPolicy, SettingsProvider, Skill, StoreFactory, SystemPromptContribution, ToolDefinition } from "./contracts.js";
1
+ import type { AgentDefinition, AuthMethod, CommandDefinition, CompactionStrategy, ContextProvider, CredentialResolver, DiscoveredContribution, InputBuilder, InstructionInjector, PromptBuilder, ProviderPackage, ProviderRequestPolicy, ResourceLoader, RetryPolicy, SettingsProvider, Skill, StopHook, StoreFactory, SystemPromptContribution, ToolDefinition } from "./contracts.js";
2
2
  import { type ModelRegistry } from "./models.js";
3
3
  import { type ProviderRegistry } from "./providers.js";
4
4
  import { type DuplicateRegistrationOptions } from "./registry-options.js";
@@ -35,6 +35,7 @@ export interface ContributionRegistries {
35
35
  readonly providerRequestPolicies: ContributionRegistry<ProviderRequestPolicy>;
36
36
  readonly systemPromptContributions: ContributionRegistry<SystemPromptContribution>;
37
37
  readonly instructionInjectors: ContributionRegistry<InstructionInjector>;
38
+ readonly stopHooks: ContributionRegistry<StopHook>;
38
39
  }
39
40
  export interface ContributionRegistriesOptions extends DuplicateRegistrationOptions {
40
41
  }
@@ -49,6 +49,7 @@ export function createContributionRegistries(options = {}) {
49
49
  providerRequestPolicies: createContributionRegistry(registryOptions("provider request policy")),
50
50
  systemPromptContributions: createContributionRegistry(registryOptions("system prompt contribution")),
51
51
  instructionInjectors: createContributionRegistry(registryOptions("instruction injector")),
52
+ stopHooks: createContributionRegistry(registryOptions("stop hook")),
52
53
  };
53
54
  }
54
55
  /** Register discovered contributions into the given registries. Inert: skill
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition, ContextProvider, Extension, ExtensionEvent, ExtensionLifecycleEventName, InstructionInjector, Skill, ToolDefinition } from "./contracts.js";
1
+ import type { AgentEvent, CommandDefinition, ContextProvider, Extension, ExtensionEvent, ExtensionLifecycleEventName, InstructionInjector, Skill, StopHook, ToolDefinition } from "./contracts.js";
2
2
  import { type ContributionRegistries } from "./contributions.js";
3
3
  import { type MiddlewareRegistry } from "./middleware.js";
4
4
  import { type PermissionPolicy } from "./security.js";
@@ -40,6 +40,18 @@ export interface ExtensionKernel {
40
40
  }
41
41
  export declare function createExtensionEventBus(options?: Pick<ExtensionKernelOptions, "errorPolicy" | "secrets">): ExtensionEventBus;
42
42
  export declare function createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel;
43
+ export interface AgentEventBridgeOptions {
44
+ /** Bridge-side failure (a source error, or a bus listener that throws under `errorPolicy: "throw"`).
45
+ * Never rethrown — the bridge must not fail the run it is observing. */
46
+ readonly onError?: (error: unknown) => void;
47
+ }
48
+ /**
49
+ * Forward an `AgentEvent` iterable onto the extension bus, one mapped lifecycle event at a time
50
+ * (plan 106 R2). Handlers run in event order and never in the run's path, so a slow or throwing
51
+ * listener cannot stall or fail the observed run. Returns an unsubscribe that stops forwarding and
52
+ * releases the source iterator.
53
+ */
54
+ export declare function forwardAgentEvents(source: AsyncIterable<AgentEvent>, events: Pick<ExtensionEventBus, "emit">, options?: AgentEventBridgeOptions): () => void;
43
55
  /** Host-owned activation: copy contributed entries into the `createAgent()`
44
56
  * fields that accept plain arrays. Contributions stay inert until the host
45
57
  * passes the returned fields into runtime config. Array slots only —
@@ -51,6 +63,8 @@ export interface ActivatedKernelConfig {
51
63
  readonly skills: readonly Skill[];
52
64
  readonly instructionInjectors: readonly InstructionInjector[];
53
65
  readonly context: readonly ContextProvider[];
66
+ /** Run-end stop hooks (plan 106 R1); pass to `createAgent({ stopHooks })`. */
67
+ readonly stopHooks: readonly StopHook[];
54
68
  /** For host command surfaces (CLI/RPC/UI); not part of `AgentConfig`. */
55
69
  readonly commands: readonly CommandDefinition[];
56
70
  /** The kernel middleware registry itself; runs only when passed to runtime config. */
@@ -143,6 +143,10 @@ export function createExtensionKernel(options = {}) {
143
143
  registries.instructionInjectors.register(injector.name, injector);
144
144
  track?.(() => registries.instructionInjectors.unregister(injector.name));
145
145
  },
146
+ registerStopHook(hook) {
147
+ registries.stopHooks.register(hook.name, hook);
148
+ track?.(() => registries.stopHooks.unregister(hook.name));
149
+ },
146
150
  });
147
151
  const unwind = (undo) => {
148
152
  for (const fn of undo.reverse()) {
@@ -190,12 +194,76 @@ export function createExtensionKernel(options = {}) {
190
194
  },
191
195
  };
192
196
  }
197
+ /**
198
+ * Lifecycle hook each forwarded `AgentEvent` maps onto (plan 106 R2). Notification only: the bus
199
+ * receives the original event as `payload` and nothing is transformed. Events not listed here are
200
+ * ignored — notably `agent_finished` stays an AgentEvent and is not re-emitted as a bus event.
201
+ */
202
+ const AGENT_EVENT_BRIDGE = {
203
+ agent_started: "before_agent_start",
204
+ turn_started: "turn",
205
+ turn_finished: "turn",
206
+ tool_execution_started: "tool_call",
207
+ tool_execution_finished: "tool_result",
208
+ };
209
+ /**
210
+ * Forward an `AgentEvent` iterable onto the extension bus, one mapped lifecycle event at a time
211
+ * (plan 106 R2). Handlers run in event order and never in the run's path, so a slow or throwing
212
+ * listener cannot stall or fail the observed run. Returns an unsubscribe that stops forwarding and
213
+ * releases the source iterator.
214
+ */
215
+ export function forwardAgentEvents(source, events, options = {}) {
216
+ const iterator = source[Symbol.asyncIterator]();
217
+ const report = (error) => {
218
+ try {
219
+ options.onError?.(error);
220
+ }
221
+ catch {
222
+ // A throwing error callback must not become an unhandled rejection.
223
+ }
224
+ };
225
+ let stopped = false;
226
+ // Serially chained so a listener that awaits keeps event order; the run never awaits this chain.
227
+ let pending = Promise.resolve();
228
+ void (async () => {
229
+ try {
230
+ while (!stopped) {
231
+ const { value, done } = await iterator.next();
232
+ if (done || stopped)
233
+ return;
234
+ const type = AGENT_EVENT_BRIDGE[value.type];
235
+ if (type === undefined)
236
+ continue;
237
+ const event = { type, payload: value };
238
+ pending = pending.then(() => events.emit(event)).catch(report);
239
+ }
240
+ }
241
+ catch (error) {
242
+ if (!stopped)
243
+ report(error);
244
+ }
245
+ })();
246
+ return () => {
247
+ if (stopped)
248
+ return;
249
+ stopped = true;
250
+ void (async () => {
251
+ try {
252
+ await iterator.return?.();
253
+ }
254
+ catch (error) {
255
+ report(error);
256
+ }
257
+ })();
258
+ };
259
+ }
193
260
  export function activateKernel(kernel) {
194
261
  return {
195
262
  tools: kernel.registries.tools.list(),
196
263
  skills: kernel.registries.skills.list(),
197
264
  instructionInjectors: kernel.registries.instructionInjectors.list(),
198
265
  context: kernel.registries.contextProviders.list(),
266
+ stopHooks: kernel.registries.stopHooks.list(),
199
267
  commands: kernel.registries.commands.list(),
200
268
  middleware: kernel.middleware,
201
269
  };
@@ -0,0 +1,3 @@
1
+ import type { GuardrailPackDefinition } from "./types.js";
2
+ /** Canned coding hygiene: file mutations confined to configured roots, test files read-only. */
3
+ export declare const codingStandardPack: GuardrailPackDefinition;