@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
@@ -1,11 +1,13 @@
1
- import { type PersistedAttentionStickyFrontier } from "./attention-compiler.js";
2
- import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
1
+ import { type PersistedAttentionFoldLedger, type PersistedAttentionStickyFrontier } from "./attention-compiler.js";
2
+ import type { Agent, AgentRunCheckpointMetadata, AgentRunCheckpointMetadataSource, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, CheckpointRecord, CheckpointStore, GuardrailRule, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
3
3
  import type { SecretRedactor } from "./redaction.js";
4
4
  import { type LoadedSkillBodiesEntry } from "./skill-load.js";
5
5
  export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
6
6
  export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
7
7
  export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
8
8
  export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
9
+ /** Sidecar metadata ceiling per checkpoint record (not the run-state value). */
10
+ export declare const MAX_AGENT_RUN_METADATA_BYTES: number;
9
11
  /** One gated tool call awaiting or holding a decision inside a suspended durable run. */
10
12
  export interface PendingToolCall {
11
13
  readonly call: ToolCallContent;
@@ -50,6 +52,13 @@ export interface StoredAgentRunState extends AgentRunState {
50
52
  /** Plan 074 P3: sticky attention mutations (thinking hashes + tool-call ids), so a durable
51
53
  * resume keeps its stubs instead of re-deciding on the first turn. Validated on load. */
52
54
  readonly attentionSticky?: PersistedAttentionStickyFrontier;
55
+ /** Plan 086 T3: folded bodies (`attention.compiler.durable`), so a resumed fold re-applies
56
+ * the same stub bytes instead of re-summarizing. Written and restored independently of
57
+ * `persistSessionState`. Validated on load. */
58
+ readonly attentionFold?: PersistedAttentionFoldLedger;
59
+ /** Plan 104 T2: compiled pack refs plus pack-owned state, so a resume re-enforces exactly what
60
+ * the suspended run enforced. Written only with `persistSessionState`; validated on load. */
61
+ readonly guardrailPacks?: PersistedGuardrailPacks;
53
62
  };
54
63
  /** Per-run allow-list (Task 21). Absent = full registered set (legacy checkpoints). */
55
64
  readonly toolNames?: readonly string[];
@@ -60,17 +69,34 @@ export interface StoredAgentRunState extends AgentRunState {
60
69
  */
61
70
  readonly checkpointPolicy?: "every-turn";
62
71
  /**
63
- * Set when a terminal state was written by a `RunOptions.turnPolicy` stop (plan 084 Task 2):
64
- * the run succeeded cleanly but its frontier is intact, so `decision: "continue"` may resume
65
- * it. Absent on every other state — a naturally finished run is never continuable.
72
+ * Set when a terminal state was written by a clean run-end stop that leaves the frontier intact:
73
+ * a `RunOptions.turnPolicy` stop (`host_policy`, plan 084 Task 2) or a stop-hook continuation cap
74
+ * (`hook_limit`, plan 106 R1). The run succeeded but `decision: "continue"` may resume it. Absent
75
+ * on every other state — a naturally finished run is never continuable.
66
76
  */
67
- readonly stopReason?: "host_policy";
77
+ readonly stopReason?: "host_policy" | "hook_limit";
68
78
  }
69
79
  /** Session-state caps (plan 015 Task 4): bounded names charged against the run-state byte budget. */
70
80
  export declare const MAX_PERSISTED_SKILL_NAMES = 64;
71
81
  export declare const MAX_PERSISTED_SKILL_NAME_CHARS = 256;
72
82
  /** Plan 041: activated-tool names ride the same budget discipline (cap 128; multiple searches accumulate). */
73
83
  export declare const MAX_PERSISTED_ACTIVATED_TOOL_NAMES = 128;
84
+ /** Plan 104 T2: one replayable pack row — the id, the version it was compiled at, and host options. */
85
+ interface PersistedGuardrailPackRef {
86
+ readonly id: string;
87
+ readonly version: number;
88
+ readonly options?: Readonly<Record<string, unknown>>;
89
+ /** Inline pattern rules; closures and `RegExp` patterns never reach a checkpoint (refused at save). */
90
+ readonly rules?: readonly GuardrailRule[];
91
+ }
92
+ /**
93
+ * Plan 104 T2: the checkpoint-side pack block written with `persistSessionState`. Rows replay a
94
+ * registered pack by `id`/`version` or an inline pack by its pattern `rules` (plan 104 T3).
95
+ */
96
+ export interface PersistedGuardrailPacks {
97
+ readonly packs: readonly PersistedGuardrailPackRef[];
98
+ readonly state?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
99
+ }
74
100
  /** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
75
101
  export declare const BUILT_IN_LOOP_REVISIONS: Readonly<Record<string, string>>;
76
102
  /** Validate a strategy snapshot as JSON-compatible and package it for the durable envelope. */
@@ -81,7 +107,20 @@ export declare function validateRunStateOptions(options: AgentRunStateOptions):
81
107
  export declare function loadAgentRunState(checkpoints: CheckpointStore, ref: AgentRunRef, ownership?: OwnershipScope): Promise<{
82
108
  readonly record: CheckpointRecord;
83
109
  readonly state: StoredAgentRunState;
110
+ readonly metadata?: AgentRunCheckpointMetadata;
84
111
  }>;
112
+ /** Resolve a host metadata source. A throwing provider fails the checkpoint write (fail closed). */
113
+ export declare function resolveCheckpointMetadata(source: AgentRunCheckpointMetadataSource | undefined): AgentRunCheckpointMetadata | undefined;
114
+ /**
115
+ * Redact + bound a sidecar metadata map for a checkpoint write. Values must be strings;
116
+ * redaction runs first so a replacement marker is still charged against the 4 KiB ceiling.
117
+ */
118
+ export declare function boundCheckpointMetadata(metadata: AgentRunCheckpointMetadata, redactor?: SecretRedactor): AgentRunCheckpointMetadata;
119
+ /**
120
+ * Read-side normalization (legacy tolerance): absent, oversize, or non-string entries are
121
+ * dropped, never thrown — a malformed sidecar must not block a resume.
122
+ */
123
+ export declare function readCheckpointMetadata(metadata: unknown): AgentRunCheckpointMetadata | undefined;
85
124
  export declare function saveAgentRunState(input: {
86
125
  readonly checkpoints: CheckpointStore;
87
126
  readonly state: StoredAgentRunState;
@@ -90,6 +129,7 @@ export declare function saveAgentRunState(input: {
90
129
  readonly fencingToken?: number;
91
130
  readonly redactor?: SecretRedactor;
92
131
  readonly maxStateBytes?: number;
132
+ readonly metadata?: AgentRunCheckpointMetadata;
93
133
  }): Promise<{
94
134
  readonly record: CheckpointRecord;
95
135
  readonly state: StoredAgentRunState;
@@ -112,3 +152,4 @@ export declare function initialAgentRunState(input: {
112
152
  readonly interruptBeforeTool?: boolean;
113
153
  }): StoredAgentRunState;
114
154
  export declare function parseAgentRunState(value: unknown, version?: number): StoredAgentRunState;
155
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { parseAttentionStickyFrontier } from "./attention-compiler.js";
2
+ import { parseAttentionStickyFrontier, restoreAttentionFoldLedger, } from "./attention-compiler.js";
3
3
  import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
4
4
  import { validateLoadedSkillBodies } from "./skill-load.js";
5
5
  import { HARD_RUN_TOOL_NAMES } from "./tools.js";
@@ -7,6 +7,8 @@ export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
7
7
  export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
8
8
  export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
9
9
  export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
10
+ /** Sidecar metadata ceiling per checkpoint record (not the run-state value). */
11
+ export const MAX_AGENT_RUN_METADATA_BYTES = 4 * 1024;
10
12
  const MAX_DEPTH = 32;
11
13
  const MAX_PROPERTIES = 256;
12
14
  /** Session-state caps (plan 015 Task 4): bounded names charged against the run-state byte budget. */
@@ -14,6 +16,12 @@ export const MAX_PERSISTED_SKILL_NAMES = 64;
14
16
  export const MAX_PERSISTED_SKILL_NAME_CHARS = 256;
15
17
  /** Plan 041: activated-tool names ride the same budget discipline (cap 128; multiple searches accumulate). */
16
18
  export const MAX_PERSISTED_ACTIVATED_TOOL_NAMES = 128;
19
+ /** Plan 104 T2: persisted pack refs and state (cap matches `MAX_GUARDRAIL_PACKS`; ids match the pack cap). */
20
+ const MAX_PERSISTED_GUARDRAIL_PACKS = 8;
21
+ const MAX_PERSISTED_GUARDRAIL_PACK_ID_CHARS = 96;
22
+ const MAX_PERSISTED_GUARDRAIL_PACK_RULES = 64;
23
+ /** Per-pack options/state byte ceiling; the whole session state is still bounded by `maxStateBytes`. */
24
+ const MAX_PERSISTED_GUARDRAIL_PACK_BYTES = 8 * 1024;
17
25
  /** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
18
26
  export const BUILT_IN_LOOP_REVISIONS = {
19
27
  "single-shot": "1",
@@ -116,10 +124,55 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
116
124
  record.value.sessionId !== ref.sessionId) {
117
125
  throw new AgentRunStateError("Agent run session mismatch");
118
126
  }
119
- return { record, state: parseAgentRunState(record.value, record.version) };
127
+ const metadata = readCheckpointMetadata(record.metadata);
128
+ return { record, state: parseAgentRunState(record.value, record.version), ...(metadata ? { metadata } : {}) };
129
+ }
130
+ /** Resolve a host metadata source. A throwing provider fails the checkpoint write (fail closed). */
131
+ export function resolveCheckpointMetadata(source) {
132
+ return typeof source === "function" ? source() : source;
133
+ }
134
+ function checkpointMetadataBytes(metadata) {
135
+ return Buffer.byteLength(JSON.stringify(metadata), "utf8");
136
+ }
137
+ /**
138
+ * Redact + bound a sidecar metadata map for a checkpoint write. Values must be strings;
139
+ * redaction runs first so a replacement marker is still charged against the 4 KiB ceiling.
140
+ */
141
+ export function boundCheckpointMetadata(metadata, redactor) {
142
+ const redacted = redactor?.redact(metadata) ?? metadata;
143
+ if (!redacted || typeof redacted !== "object" || Array.isArray(redacted)) {
144
+ throw new AgentRunStateError("Checkpoint metadata must be an object");
145
+ }
146
+ const bounded = {};
147
+ for (const [key, value] of Object.entries(redacted)) {
148
+ if (typeof value !== "string")
149
+ throw new AgentRunStateError(`Checkpoint metadata value for ${key} must be a string`);
150
+ bounded[key] = value;
151
+ }
152
+ if (checkpointMetadataBytes(bounded) > MAX_AGENT_RUN_METADATA_BYTES) {
153
+ throw new AgentRunStateError(`Checkpoint metadata exceeds ${MAX_AGENT_RUN_METADATA_BYTES} bytes`);
154
+ }
155
+ return Object.freeze(bounded);
156
+ }
157
+ /**
158
+ * Read-side normalization (legacy tolerance): absent, oversize, or non-string entries are
159
+ * dropped, never thrown — a malformed sidecar must not block a resume.
160
+ */
161
+ export function readCheckpointMetadata(metadata) {
162
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
163
+ return undefined;
164
+ const bounded = {};
165
+ for (const [key, value] of Object.entries(metadata)) {
166
+ if (typeof value === "string")
167
+ bounded[key] = value;
168
+ }
169
+ if (Object.keys(bounded).length === 0 || checkpointMetadataBytes(bounded) > MAX_AGENT_RUN_METADATA_BYTES)
170
+ return undefined;
171
+ return Object.freeze(bounded);
120
172
  }
121
173
  export async function saveAgentRunState(input) {
122
174
  const bounded = boundState(input.redactor?.redact(input.state) ?? input.state, input.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
175
+ const metadata = input.metadata === undefined ? undefined : boundCheckpointMetadata(input.metadata, input.redactor);
123
176
  const record = await input.checkpoints.saveCheckpoint({
124
177
  namespace: AGENT_RUN_STATE_NAMESPACE,
125
178
  key: bounded.runId,
@@ -128,6 +181,7 @@ export async function saveAgentRunState(input) {
128
181
  fencingToken: input.fencingToken,
129
182
  value: bounded,
130
183
  category: "agent-run",
184
+ ...(metadata ? { metadata } : {}),
131
185
  ...input.ownership,
132
186
  });
133
187
  return { record, state: { ...bounded, version: record.version } };
@@ -222,7 +276,7 @@ export function parseAgentRunState(value, version) {
222
276
  if (state.checkpointPolicy !== undefined && state.checkpointPolicy !== "every-turn") {
223
277
  throw new AgentRunStateError("Malformed agent run checkpoint policy");
224
278
  }
225
- if (state.stopReason !== undefined && state.stopReason !== "host_policy") {
279
+ if (state.stopReason !== undefined && state.stopReason !== "host_policy" && state.stopReason !== "hook_limit") {
226
280
  throw new AgentRunStateError("Malformed agent run stop reason");
227
281
  }
228
282
  // Load bounds against the hard cap, not the default: the configured maxStateBytes is a
@@ -294,12 +348,106 @@ function validateSessionState(sessionState) {
294
348
  }
295
349
  }
296
350
  const attention = sessionState.attentionSticky;
297
- if (attention === undefined)
298
- return;
299
351
  // Both arrays are capped by the parser, and a malformed frontier is dropped rather than
300
352
  // failing the resume: re-deciding a mutation is safe, refusing to resume is not.
301
- if (parseAttentionStickyFrontier(attention) === undefined) {
353
+ if (attention !== undefined && parseAttentionStickyFrontier(attention) === undefined) {
302
354
  throw new AgentRunStateError("Malformed agent run attention frontier");
303
355
  }
356
+ // Plan 086 T3: the fold ledger gets the same treatment — malformed entries are dropped by the
357
+ // parser, a malformed shape fails the load rather than the first provider turn.
358
+ const fold = sessionState.attentionFold;
359
+ if (fold !== undefined && restoreAttentionFoldLedger(fold) === undefined) {
360
+ throw new AgentRunStateError("Malformed agent run attention fold ledger");
361
+ }
362
+ validateGuardrailPackState(sessionState.guardrailPacks);
363
+ }
364
+ /**
365
+ * Plan 104 T2/T3: bounds for the persisted pack block. Rows replay a registered pack by id/version or
366
+ * an inline pack by its pattern rules, and state may only name those rows — anything else fails the
367
+ * load, because a dropped pack silently re-allows what it existed to deny. Rule data is re-validated
368
+ * (pattern compile, id/reason caps) by the compiler that replays it; this checks the JSON envelope.
369
+ */
370
+ function validateGuardrailPackState(value) {
371
+ if (value === undefined)
372
+ return;
373
+ const raw = value;
374
+ if (!raw || typeof raw !== "object" || !Array.isArray(raw.packs)) {
375
+ throw new AgentRunStateError("Malformed agent run guardrail pack state");
376
+ }
377
+ if (raw.packs.length > MAX_PERSISTED_GUARDRAIL_PACKS) {
378
+ throw new AgentRunStateError(`Persisted guardrail packs exceed ${MAX_PERSISTED_GUARDRAIL_PACKS} entries`);
379
+ }
380
+ const ids = new Set();
381
+ for (const row of raw.packs) {
382
+ if (!isPlainObject(row))
383
+ throw new AgentRunStateError("Malformed agent run guardrail pack row");
384
+ const { id, version, options } = row;
385
+ if (typeof id !== "string" || !id.trim() || id.length > MAX_PERSISTED_GUARDRAIL_PACK_ID_CHARS) {
386
+ throw new AgentRunStateError(`Persisted guardrail pack ids must be non-empty strings of at most ${MAX_PERSISTED_GUARDRAIL_PACK_ID_CHARS} chars`);
387
+ }
388
+ if (ids.has(id))
389
+ throw new AgentRunStateError(`Duplicate persisted guardrail pack id "${id}"`);
390
+ ids.add(id);
391
+ if (!Number.isSafeInteger(version) || version < 1) {
392
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" version must be a positive integer`);
393
+ }
394
+ if (options !== undefined && !isPlainObject(options)) {
395
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" options must be an object`);
396
+ }
397
+ if (options !== undefined)
398
+ boundPackBytes(options, `Pack "${id}" options`);
399
+ validatePersistedPackRules(id, row);
400
+ }
401
+ if (raw.state === undefined)
402
+ return;
403
+ if (!isPlainObject(raw.state))
404
+ throw new AgentRunStateError("Malformed agent run guardrail pack state");
405
+ for (const [id, state] of Object.entries(raw.state)) {
406
+ if (!ids.has(id))
407
+ throw new AgentRunStateError(`Persisted guardrail pack state names unknown pack "${id}"`);
408
+ if (!isPlainObject(state))
409
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" state must be an object`);
410
+ boundPackBytes(state, `Pack "${id}" state`);
411
+ }
412
+ }
413
+ function isPlainObject(value) {
414
+ return typeof value === "object" && value !== null && !Array.isArray(value);
415
+ }
416
+ /** Plan 104 T3: a persisted inline pack must be pattern data — a closure or `RegExp` cannot restore. */
417
+ function validatePersistedPackRules(id, row) {
418
+ if (row.rules === undefined)
419
+ return;
420
+ if (!Array.isArray(row.rules))
421
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rules must be an array`);
422
+ if (row.rules.length === 0 || row.rules.length > MAX_PERSISTED_GUARDRAIL_PACK_RULES) {
423
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rules must number 1..${MAX_PERSISTED_GUARDRAIL_PACK_RULES}`);
424
+ }
425
+ for (const rule of row.rules) {
426
+ if (!isPlainObject(rule))
427
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rule must be an object`);
428
+ const { id: ruleId, pattern, deny } = rule;
429
+ if (typeof ruleId !== "string" || !ruleId.trim() || ruleId.length > MAX_PERSISTED_GUARDRAIL_PACK_ID_CHARS) {
430
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rule ids must be non-empty strings`);
431
+ }
432
+ if (deny !== undefined)
433
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rule "${ruleId}" carries a deny predicate`);
434
+ if (pattern !== undefined && typeof pattern !== "string") {
435
+ throw new AgentRunStateError(`Persisted guardrail pack "${id}" rule "${ruleId}" pattern must be a string`);
436
+ }
437
+ boundPackBytes(rule, `Pack "${id}" rule "${ruleId}"`);
438
+ }
439
+ }
440
+ /** A pack's state must be JSON and under its per-pack ceiling: refuse, never truncate. */
441
+ function boundPackBytes(value, label) {
442
+ let text;
443
+ try {
444
+ text = JSON.stringify(value);
445
+ }
446
+ catch {
447
+ throw new AgentRunStateError(`${label} must be JSON serializable`);
448
+ }
449
+ if ((text ? Buffer.byteLength(text) : 0) > MAX_PERSISTED_GUARDRAIL_PACK_BYTES) {
450
+ throw new AgentRunStateError(`${label} exceeds ${MAX_PERSISTED_GUARDRAIL_PACK_BYTES} bytes`);
451
+ }
304
452
  }
305
453
  //# sourceMappingURL=agent-run-state.js.map
@@ -3,6 +3,8 @@ import type { AgentEvent, SubscribeOptions } from "../contracts.js";
3
3
  export declare class EventSubscriber implements AsyncIterable<AgentEvent>, AsyncIterator<AgentEvent> {
4
4
  private readonly sessionId;
5
5
  private readonly onClose;
6
+ /** Plan 104 T5: run-scoped unless the host opted into `acrossRuns`. */
7
+ readonly acrossRuns: boolean;
6
8
  private readonly queue;
7
9
  private readonly waiters;
8
10
  private readonly maxQueuedEvents;
@@ -1,6 +1,8 @@
1
1
  export class EventSubscriber {
2
2
  sessionId;
3
3
  onClose;
4
+ /** Plan 104 T5: run-scoped unless the host opted into `acrossRuns`. */
5
+ acrossRuns;
4
6
  queue = [];
5
7
  waiters = [];
6
8
  maxQueuedEvents;
@@ -12,6 +14,7 @@ export class EventSubscriber {
12
14
  const maxQueuedEvents = options.maxQueuedEvents ?? 1024;
13
15
  this.maxQueuedEvents = Number.isFinite(maxQueuedEvents) ? Math.max(1, Math.floor(maxQueuedEvents)) : 1024;
14
16
  this.overflow = options.overflow ?? "close";
17
+ this.acrossRuns = options.acrossRuns === true;
15
18
  }
16
19
  [Symbol.asyncIterator]() {
17
20
  return this;
@@ -160,6 +160,8 @@ export function createUsageAccumulator() {
160
160
  const sums = new Map();
161
161
  let costCurrency;
162
162
  let costCompatible = true;
163
+ let estimated = false;
164
+ let confidence;
163
165
  return {
164
166
  add(usage) {
165
167
  for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
@@ -181,6 +183,13 @@ export function createUsageAccumulator() {
181
183
  if (costCompatible)
182
184
  sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
183
185
  }
186
+ // Run totals keep estimate provenance (plan 091 T2): one estimated turn
187
+ // labels the total, and the weakest confidence wins.
188
+ if (usage.estimated === true) {
189
+ estimated = true;
190
+ if (usage.confidence !== undefined && (confidence === undefined || usage.confidence === "low"))
191
+ confidence = usage.confidence;
192
+ }
184
193
  },
185
194
  value() {
186
195
  if (sums.size === 0)
@@ -192,6 +201,11 @@ export function createUsageAccumulator() {
192
201
  }
193
202
  if (costCompatible && sums.has("cost") && costCurrency !== undefined)
194
203
  usage.currency = costCurrency;
204
+ if (estimated) {
205
+ usage.estimated = true;
206
+ if (confidence !== undefined)
207
+ usage.confidence = confidence;
208
+ }
195
209
  return Object.keys(usage).length > 0 ? usage : undefined;
196
210
  },
197
211
  };