@arnilo/prism 0.5.6 → 0.7.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 (160) hide show
  1. package/CHANGELOG.md +81 -5
  2. package/README.md +10 -10
  3. package/dist/agent-approval.js +7 -6
  4. package/dist/agent-definitions.js +1 -0
  5. package/dist/agent-loops.js +51 -12
  6. package/dist/agent-run-lifecycle.js +11 -0
  7. package/dist/agent-run-state.d.ts +6 -0
  8. package/dist/agent-run-state.js +29 -9
  9. package/dist/agent-session/session/assemble.js +33 -2
  10. package/dist/agent-session/session/persist.js +6 -2
  11. package/dist/agent-session/session/tool-round.js +1 -0
  12. package/dist/agent-session/session/types.d.ts +10 -0
  13. package/dist/agent-session/session.d.ts +15 -0
  14. package/dist/agent-session/session.js +59 -4
  15. package/dist/agent-tool-dispatch.js +5 -4
  16. package/dist/artifacts.d.ts +39 -1
  17. package/dist/artifacts.js +73 -0
  18. package/dist/attention-compiler.d.ts +121 -0
  19. package/dist/attention-compiler.js +479 -0
  20. package/dist/cli-init.js +20 -6
  21. package/dist/content.d.ts +3 -16
  22. package/dist/content.js +9 -99
  23. package/dist/context-budget.d.ts +32 -2
  24. package/dist/context-budget.js +51 -19
  25. package/dist/contracts-core/agent.d.ts +18 -0
  26. package/dist/contracts-core/agent.js +4 -1
  27. package/dist/contracts-core/attention.d.ts +66 -0
  28. package/dist/contracts-core/attention.js +2 -0
  29. package/dist/contracts-core/compaction.d.ts +59 -0
  30. package/dist/contracts-core/compaction.js +77 -1
  31. package/dist/contracts-core/provider.d.ts +4 -0
  32. package/dist/contracts-core.d.ts +1 -0
  33. package/dist/contracts-core.js +1 -0
  34. package/dist/contracts-protocol.d.ts +29 -0
  35. package/dist/contracts-run-state.d.ts +6 -0
  36. package/dist/host-composition.d.ts +78 -0
  37. package/dist/host-composition.js +248 -0
  38. package/dist/index.d.ts +11 -8
  39. package/dist/index.js +6 -5
  40. package/dist/input.d.ts +19 -1
  41. package/dist/input.js +52 -2
  42. package/dist/media-types.d.ts +34 -0
  43. package/dist/media-types.js +158 -0
  44. package/dist/pinned-fetch.d.ts +2 -2
  45. package/dist/pinned-fetch.js +11 -12
  46. package/dist/redaction.js +74 -1
  47. package/dist/secure-agent.d.ts +2 -0
  48. package/dist/secure-agent.js +6 -1
  49. package/dist/session-stores.d.ts +11 -0
  50. package/dist/session-stores.js +23 -8
  51. package/dist/tool-result-fold.d.ts +12 -0
  52. package/dist/tool-result-fold.js +13 -6
  53. package/dist/tools.d.ts +10 -0
  54. package/dist/tools.js +41 -0
  55. package/docs/acp-agent.md +42 -11
  56. package/docs/acp.md +3 -2
  57. package/docs/ag-ui.md +9 -5
  58. package/docs/agent-definitions.md +9 -1
  59. package/docs/agent-events.md +6 -1
  60. package/docs/agent-loops.md +1 -1
  61. package/docs/agent-session-runtime.md +9 -7
  62. package/docs/attention-compiler.md +272 -0
  63. package/docs/browser-automation.md +5 -2
  64. package/docs/cli-rpc.md +4 -2
  65. package/docs/coding-agent-tools.md +1 -1
  66. package/docs/coding-security.md +5 -3
  67. package/docs/coding-tools.md +1 -1
  68. package/docs/coding-workspaces.md +22 -0
  69. package/docs/compaction-and-retry.md +36 -4
  70. package/docs/compaction-observational-memory.md +62 -9
  71. package/docs/context-and-skills.md +4 -2
  72. package/docs/contributing.md +37 -0
  73. package/docs/conversations.md +1 -1
  74. package/docs/core.md +2 -0
  75. package/docs/dev-inspector.md +4 -0
  76. package/docs/device-adapters.md +1 -0
  77. package/docs/document-reader.md +12 -2
  78. package/docs/documents.md +11 -3
  79. package/docs/enterprise-postgres-state.md +2 -2
  80. package/docs/evaluations.md +168 -4
  81. package/docs/execution-timeline.md +180 -0
  82. package/docs/graft.md +3 -1
  83. package/docs/history/0.7.0-primitive-review.md +254 -0
  84. package/docs/history/migration-0.0.md +2 -2
  85. package/docs/history/release-handoffs.md +70 -1
  86. package/docs/host-compositions.md +147 -0
  87. package/docs/host-security.md +2 -2
  88. package/docs/hosted-sandboxes.md +94 -0
  89. package/docs/index.md +73 -41
  90. package/docs/input-and-prompt-assembly.md +5 -4
  91. package/docs/knowledge-sync.md +84 -0
  92. package/docs/language-intelligence.md +2 -2
  93. package/docs/live-testing.md +4 -1
  94. package/docs/mcp-tools.md +2 -1
  95. package/docs/memory-fabric.md +416 -0
  96. package/docs/migrate-to-0.5.md +8 -3
  97. package/docs/migrate-to-0.6.md +90 -0
  98. package/docs/migrate-to-0.7.md +345 -0
  99. package/docs/migration.md +43 -1
  100. package/docs/model-registry.md +1 -1
  101. package/docs/model-routing.md +79 -4
  102. package/docs/multi-agent-patterns.md +20 -6
  103. package/docs/multimodal-content.md +1 -1
  104. package/docs/obscura.md +3 -1
  105. package/docs/observability.md +52 -1
  106. package/docs/operations.md +13 -1
  107. package/docs/options-index.md +298 -0
  108. package/docs/peer-dependencies.md +96 -0
  109. package/docs/performance.md +34 -2
  110. package/docs/ponytail.md +2 -0
  111. package/docs/postgres-persistence.md +3 -1
  112. package/docs/process-sessions.md +3 -1
  113. package/docs/prompt-registry.md +1 -1
  114. package/docs/provider-caching.md +4 -2
  115. package/docs/provider-conformance.md +2 -2
  116. package/docs/provider-packages.md +23 -23
  117. package/docs/provider-primitives.md +2 -1
  118. package/docs/providers/ai-sdk.md +5 -2
  119. package/docs/providers/bedrock.md +71 -7
  120. package/docs/providers/openai.md +1 -1
  121. package/docs/public-contracts.md +2 -2
  122. package/docs/rag.md +24 -8
  123. package/docs/realtime-voice.md +87 -0
  124. package/docs/release-and-install.md +78 -56
  125. package/docs/runs-and-usage.md +3 -2
  126. package/docs/server.md +6 -4
  127. package/docs/session-stores.md +3 -1
  128. package/docs/speech.md +2 -0
  129. package/docs/sqlite-persistence.md +2 -0
  130. package/docs/supervisors.md +33 -5
  131. package/docs/testing.md +38 -0
  132. package/docs/thinking-and-reasoning.md +3 -1
  133. package/docs/tools.md +7 -6
  134. package/docs/web-tools.md +2 -1
  135. package/docs/wiki.md +1 -1
  136. package/docs/work-artifacts-and-review.md +14 -4
  137. package/docs/work-connectors.md +3 -1
  138. package/docs/work-tools.md +14 -4
  139. package/docs/workflows.md +69 -1
  140. package/docs/working-and-semantic-memory.md +25 -14
  141. package/package.json +5 -5
  142. package/templates/README.md +2 -0
  143. package/templates/business-worker/README.md.tmpl +19 -0
  144. package/templates/business-worker/env.example.tmpl +1 -0
  145. package/templates/business-worker/gitignore.tmpl +11 -0
  146. package/templates/business-worker/manifest.json +11 -0
  147. package/templates/business-worker/package.json.tmpl +23 -0
  148. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  149. package/templates/business-worker/src/index.ts.tmpl +13 -0
  150. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  151. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  152. package/templates/personal-assistant/README.md.tmpl +18 -0
  153. package/templates/personal-assistant/env.example.tmpl +1 -0
  154. package/templates/personal-assistant/gitignore.tmpl +11 -0
  155. package/templates/personal-assistant/manifest.json +11 -0
  156. package/templates/personal-assistant/package.json.tmpl +23 -0
  157. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  158. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  159. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  160. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
@@ -1,7 +1,9 @@
1
1
  /** session (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
2
2
  import { policyList } from "../agent-tool-dispatch.js";
3
+ import { createAttentionStickyFrontier, resolveInputCap, restoreAttentionStickyFrontier, serializeAttentionStickyFrontier, } from "../attention-compiler.js";
3
4
  import { createDefaultCompactionStrategy, isCompactionEntryData } from "../compaction.js";
4
- import { DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS } from "../contracts.js";
5
+ import { estimateAssemblyTokens, estimateTextTokens } from "../context-budget.js";
6
+ import { DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, DEFAULT_SNAPSHOT_CACHE_TTL_MS, HARD_MAX_SNAPSHOT_CACHE_TTL_MS, resolveShouldCompact, } from "../contracts.js";
5
7
  import { GuardrailError, runGuardrails } from "../guardrails.js";
6
8
  import { applyDefaultProviderRequestOptions, createProviderRequestPolicyChain, normalizeProviderRequestPolicyResult, } from "../provider-request-policy.js";
7
9
  import { redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "../redaction.js";
@@ -53,6 +55,28 @@ export class RuntimeAgentSession {
53
55
  restoredSkillBodies = [];
54
56
  /** Skills of the current run (for the bodies snapshot); replaced at each run start. */
55
57
  activeRunSkills = [];
58
+ /** Per-run tool allow-list (Task 21); undefined means the full registered set. */
59
+ activeToolNames;
60
+ /** Sticky frontier for this session (plan 074 C10); created on first use, so a session whose
61
+ * agents never enable the compiler allocates nothing. Mutations stay applied once made, so a
62
+ * later under-ratio turn re-applies them instead of rewriting the prompt-cache prefix. */
63
+ attentionSticky;
64
+ attentionStickyFor() {
65
+ this.attentionSticky ??= createAttentionStickyFrontier();
66
+ return this.attentionSticky;
67
+ }
68
+ /** Plan 074 P3: bounded snapshot for a durable checkpoint; `undefined` when the session never
69
+ * mutated anything, so a compiler-off (or never-over-ratio) session persists nothing extra. */
70
+ serializedAttentionSticky() {
71
+ return this.attentionSticky && this.attentionSticky.thinking.size + this.attentionSticky.toolCallIds.size > 0
72
+ ? serializeAttentionStickyFrontier(this.attentionSticky)
73
+ : undefined;
74
+ }
75
+ /** Plan 074 P3: restore a frontier validated at checkpoint load, so a resumed run keeps its
76
+ * stubs instead of re-deciding its first turn from the ratio. */
77
+ restoreAttentionSticky(persisted) {
78
+ this.attentionSticky = restoreAttentionStickyFrontier(persisted);
79
+ }
56
80
  /** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
57
81
  restoreLoadedSkills(names) {
58
82
  for (const name of names)
@@ -78,12 +102,14 @@ export class RuntimeAgentSession {
78
102
  ledgerFailure;
79
103
  snapshotGeneration = 0;
80
104
  snapshotCache;
105
+ snapshotCacheTtlMs;
81
106
  constructor(config) {
82
107
  this.id = config.id ?? randomId("session");
83
108
  this.agent = config.agent;
84
109
  this.metadata = config.metadata;
85
110
  this.store = config.store ?? config.agent.config.store ?? createMemorySessionStore();
86
111
  this.currentLeafId = config.leafId;
112
+ this.snapshotCacheTtlMs = resolveSnapshotCacheTtlMs(config.snapshotCacheTtlMs);
87
113
  }
88
114
  get leafId() {
89
115
  return this.currentLeafId;
@@ -391,10 +417,23 @@ export class RuntimeAgentSession {
391
417
  }
392
418
  async autoCompact(runId, options, signal, inputMessages) {
393
419
  const compaction = mergeCompaction(this.agent.config.compaction, options.compaction);
394
- if (!compaction || compaction.thresholdEntries === undefined)
420
+ if (!compaction || (compaction.trigger === undefined && compaction.thresholdEntries === undefined))
395
421
  return;
396
422
  const snapshot = await this.snapshot();
397
- if (snapshot.entries.length <= compaction.thresholdEntries || snapshot.entries.at(-1)?.kind === "compaction")
423
+ // A branch that just compacted keeps its fresh summary: no second pass over the same entries.
424
+ if (snapshot.entries.at(-1)?.kind === "compaction")
425
+ return;
426
+ const shouldCompact = await resolveShouldCompact({ trigger: compaction.trigger, thresholdEntries: compaction.thresholdEntries }, {
427
+ sessionId: this.id,
428
+ entryCount: snapshot.entries.length,
429
+ // Estimates of the branch the run is about to send: messages plus carried summaries.
430
+ estimateInputTokens: () => estimateAssemblyTokens(snapshot.messages) + snapshot.summaries.reduce((sum, summary) => sum + estimateTextTokens(summary), 0),
431
+ // Same cap helper the attention compiler resolves its `inputCap` with.
432
+ resolveInputCapTokens: () => resolveInputCap(undefined, options.model ?? this.agent.config.model),
433
+ metadata: compaction.metadata,
434
+ signal,
435
+ });
436
+ if (!shouldCompact)
398
437
  return;
399
438
  await this.compactBranch(compaction, runId, signal, "auto");
400
439
  const compacted = await this.snapshot();
@@ -470,8 +509,24 @@ export class RuntimeAgentSession {
470
509
  const value = reader
471
510
  ? await rebuildSessionContext(reader, { sessionId: this.id, leafId: this.currentLeafId })
472
511
  : rebuildSessionContext(await this.store.list(this.id), { leafId: this.currentLeafId });
473
- this.snapshotCache = { leafId: this.currentLeafId, generation: this.snapshotGeneration, expiresAt: now + 1_000, value };
512
+ this.snapshotCache = {
513
+ leafId: this.currentLeafId,
514
+ generation: this.snapshotGeneration,
515
+ expiresAt: now + this.snapshotCacheTtlMs,
516
+ value,
517
+ };
474
518
  return value;
475
519
  }
476
520
  }
521
+ /**
522
+ * `snapshotCacheTtlMs` resolution: `0` disables the branch cache (a host that needs a
523
+ * fresh store read per snapshot), otherwise a safe integer up to the hard cap.
524
+ */
525
+ function resolveSnapshotCacheTtlMs(value) {
526
+ const ttl = value ?? DEFAULT_SNAPSHOT_CACHE_TTL_MS;
527
+ if (!Number.isSafeInteger(ttl) || ttl < 0 || ttl > HARD_MAX_SNAPSHOT_CACHE_TTL_MS) {
528
+ throw new TypeError(`AgentSessionConfig.snapshotCacheTtlMs must be a safe integer from 0 to ${HARD_MAX_SNAPSHOT_CACHE_TTL_MS}`);
529
+ }
530
+ return ttl;
531
+ }
477
532
  //# sourceMappingURL=session.js.map
@@ -51,8 +51,9 @@ export async function validateElicitationPayload(agent, state, target, payload,
51
51
  // Tool-declared answer-shape validation, re-derived from the current registry (never persisted).
52
52
  const call = state.pendingCalls?.find((entry) => entry.call.id === target.toolCallId)?.call;
53
53
  const tool = call ? activeTools(agent.config.tools).registry.get(call.name) : undefined;
54
- const validate = tool?.elicitation && call
55
- ? safeToolElicitationValidate(tool, call.arguments, {
54
+ const elicitation = tool?.elicitation;
55
+ const validate = elicitation && call
56
+ ? safeToolElicitationValidate(elicitation, call.arguments, {
56
57
  sessionId: state.sessionId,
57
58
  runId: state.runId,
58
59
  toolCallId: target.toolCallId ?? "elicitation",
@@ -68,9 +69,9 @@ export async function validateElicitationPayload(agent, state, target, payload,
68
69
  }
69
70
  }
70
71
  }
71
- function safeToolElicitationValidate(tool, args, context) {
72
+ function safeToolElicitationValidate(elicitation, args, context) {
72
73
  try {
73
- return tool.elicitation(args, context)?.validate;
74
+ return elicitation(args, context)?.validate;
74
75
  }
75
76
  catch {
76
77
  return undefined;
@@ -8,12 +8,26 @@ import type { OwnershipScope } from "./contracts.js";
8
8
  export type ArtifactApprovalState = "pending" | "approved" | "rejected";
9
9
  /** A resolved decision on one revision (pending is the absence of a decision). */
10
10
  export type ArtifactDecisionState = Exclude<ArtifactApprovalState, "pending">;
11
+ /** Optional host semantic verdict. Never treated as citation integrity or proof. */
12
+ export type CitationSupport = "unverified" | "supported" | "unsupported" | "uncertain";
11
13
  /** Bounded citation / data-source reference. Host resolves the body; Prism stores the ref only. */
12
14
  export interface ArtifactCitation {
13
15
  readonly uri: string;
14
16
  readonly title?: string;
15
- /** Data-source kind (e.g. "web", "database", "upload"); host-defined, bounded. */
17
+ /** Data-source kind (e.g. "web", "database", "upload", "rag"); host-defined, bounded. */
16
18
  readonly kind?: string;
19
+ readonly sourceId?: string;
20
+ readonly revision?: string;
21
+ /** SHA-256 hex of the retrieved source snapshot (optional `sha256:` prefix). */
22
+ readonly contentHash?: string;
23
+ readonly retrievedAt?: string;
24
+ readonly excerpt?: string;
25
+ readonly span?: {
26
+ readonly start: number;
27
+ readonly end: number;
28
+ };
29
+ readonly tenantId?: string;
30
+ readonly support?: CitationSupport;
17
31
  }
18
32
  /** One immutable revision of an artifact. `uri`/`hash` reference host-owned content. */
19
33
  export interface ArtifactRevision {
@@ -43,6 +57,8 @@ export interface ArtifactApproval {
43
57
  /** Change-request / rejection note. */
44
58
  readonly note?: string;
45
59
  readonly decidedAt: string;
60
+ /** SHA-256 of bound citation sourceId/revision/contentHash tuples at decision time. */
61
+ readonly evidenceDigest?: string;
46
62
  }
47
63
  /**
48
64
  * Durable artifact record. Stored as a versioned checkpoint value; the checkpoint version
@@ -130,3 +146,25 @@ export declare class ArtifactError extends Error {
130
146
  export declare function artifactCheckpointKey(threadId: string, artifactId: string): string;
131
147
  /** Current review state: the decision on the latest revision, or pending when undecided. */
132
148
  export declare function artifactApprovalState(record: ArtifactRecord): ArtifactApprovalState;
149
+ export declare const HARD_CITATION_EXCERPT_BYTES = 8192;
150
+ export type CitationIntegrityReason = "ok" | "missing_source" | "hash_mismatch" | "span_mismatch" | "revoked_acl" | "revision_changed" | "excerpt_too_large" | "cross_tenant";
151
+ export interface CitationLiveSource {
152
+ readonly contentHash: string;
153
+ readonly revision: string;
154
+ readonly body?: string;
155
+ readonly tenantId?: string;
156
+ readonly authorized?: boolean;
157
+ }
158
+ export interface CitationIntegrityResult {
159
+ readonly ok: boolean;
160
+ readonly reason: CitationIntegrityReason;
161
+ }
162
+ /** Deterministic source existence / hash / span / ACL check. Ignores `support`. */
163
+ export declare function checkCitationIntegrity(citation: ArtifactCitation, live?: CitationLiveSource, options?: {
164
+ readonly boundRevision?: string;
165
+ readonly maxExcerptBytes?: number;
166
+ }): CitationIntegrityResult;
167
+ /** Stable digest of citation identity tuples. Source body changes after approval fail this digest only when citations themselves change; live hash is `checkCitationIntegrity`. */
168
+ export declare function citationBindingDigest(citations: readonly ArtifactCitation[] | undefined): string;
169
+ /** True when the approval digest still matches the revision and (if given) live sources pass integrity. */
170
+ export declare function approvalEvidenceIntact(approval: ArtifactApproval, revision: ArtifactRevision, liveSources?: Readonly<Record<string, CitationLiveSource>>): CitationIntegrityResult;
package/dist/artifacts.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  /** Well-known error codes for ArtifactBodyStore failures. */
2
3
  export const ARTIFACT_BODY_ERROR_CODES = {
3
4
  OWNERSHIP: "ERR_PRISM_ARTIFACT_BODY_OWNERSHIP",
@@ -41,4 +42,76 @@ export function artifactApprovalState(record) {
41
42
  const decision = record.approvals.find((approval) => approval.version === latest.version);
42
43
  return decision?.state ?? "pending";
43
44
  }
45
+ export const HARD_CITATION_EXCERPT_BYTES = 8192;
46
+ function normalizeCitationHash(value) {
47
+ const raw = value.startsWith("sha256:") ? value.slice("sha256:".length) : value;
48
+ return raw.trim().toLowerCase();
49
+ }
50
+ /** Deterministic source existence / hash / span / ACL check. Ignores `support`. */
51
+ export function checkCitationIntegrity(citation, live, options) {
52
+ const maxExcerpt = options?.maxExcerptBytes ?? HARD_CITATION_EXCERPT_BYTES;
53
+ if (citation.excerpt !== undefined && Buffer.byteLength(citation.excerpt, "utf8") > maxExcerpt) {
54
+ return { ok: false, reason: "excerpt_too_large" };
55
+ }
56
+ if (!live)
57
+ return { ok: false, reason: "missing_source" };
58
+ if (live.authorized === false)
59
+ return { ok: false, reason: "revoked_acl" };
60
+ if (citation.tenantId && live.tenantId && citation.tenantId !== live.tenantId) {
61
+ return { ok: false, reason: "cross_tenant" };
62
+ }
63
+ if (!citation.contentHash)
64
+ return { ok: false, reason: "missing_source" };
65
+ if (normalizeCitationHash(citation.contentHash) !== normalizeCitationHash(live.contentHash)) {
66
+ return { ok: false, reason: "hash_mismatch" };
67
+ }
68
+ if (citation.revision !== undefined && citation.revision !== live.revision) {
69
+ return { ok: false, reason: "revision_changed" };
70
+ }
71
+ if (options?.boundRevision !== undefined && options.boundRevision !== live.revision) {
72
+ return { ok: false, reason: "revision_changed" };
73
+ }
74
+ if (citation.span) {
75
+ const { start, end } = citation.span;
76
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end <= start) {
77
+ return { ok: false, reason: "span_mismatch" };
78
+ }
79
+ if (live.body !== undefined) {
80
+ const sliced = live.body.slice(start, end);
81
+ if (citation.excerpt !== undefined && sliced !== citation.excerpt) {
82
+ return { ok: false, reason: "span_mismatch" };
83
+ }
84
+ }
85
+ }
86
+ else if (citation.excerpt !== undefined && live.body !== undefined && citation.excerpt !== live.body) {
87
+ return { ok: false, reason: "span_mismatch" };
88
+ }
89
+ return { ok: true, reason: "ok" };
90
+ }
91
+ /** Stable digest of citation identity tuples. Source body changes after approval fail this digest only when citations themselves change; live hash is `checkCitationIntegrity`. */
92
+ export function citationBindingDigest(citations) {
93
+ const rows = (citations ?? [])
94
+ .map((citation) => `${citation.sourceId ?? ""}|${citation.revision ?? ""}|${citation.contentHash ? normalizeCitationHash(citation.contentHash) : ""}`)
95
+ .sort();
96
+ return createHash("sha256").update(JSON.stringify(rows)).digest("hex");
97
+ }
98
+ /** True when the approval digest still matches the revision and (if given) live sources pass integrity. */
99
+ export function approvalEvidenceIntact(approval, revision, liveSources) {
100
+ if (approval.evidenceDigest !== undefined && approval.evidenceDigest !== citationBindingDigest(revision.citations)) {
101
+ return { ok: false, reason: "revision_changed" };
102
+ }
103
+ if (liveSources === undefined)
104
+ return { ok: true, reason: "ok" };
105
+ for (const citation of revision.citations ?? []) {
106
+ if (!citation.sourceId && !citation.contentHash)
107
+ continue;
108
+ const live = citation.sourceId ? liveSources[citation.sourceId] : undefined;
109
+ const result = checkCitationIntegrity(citation, live, {
110
+ ...(citation.revision === undefined ? {} : { boundRevision: citation.revision }),
111
+ });
112
+ if (!result.ok)
113
+ return result;
114
+ }
115
+ return { ok: true, reason: "ok" };
116
+ }
44
117
  //# sourceMappingURL=artifacts.js.map
@@ -0,0 +1,121 @@
1
+ /** Attention Compiler (plan 074 Tasks 2–3): frozen contracts, the ratio gate, and the two
2
+ * sticky stages (thinking strip, then old tool-result stubs). Creation is synchronous and
3
+ * fails closed; `compileAttention` measures the assembled request once, rewrites only the
4
+ * `history` / `toolResults` groups, and never touches the store, the om ledger, or the
5
+ * frozen prefix. The assembly branch lives in `input.ts`; opt-in agent wiring (Task 5) is not
6
+ * part of this module. */
7
+ import { type ContextBudgetMessageGroups } from "./context-budget.js";
8
+ import type { AttentionCompiler, AttentionCompilerContext, AttentionCompilerOptions, AttentionCompilerSetting, AttentionInputCapOptions, AttentionReport, ContextBlock, Skill, ToolDefinition } from "./contracts.js";
9
+ import { type CompactionTrigger } from "./contracts-core/compaction.js";
10
+ import type { SecretRedactor } from "./redaction.js";
11
+ import { type ResolvedToolResultFoldOptions } from "./tool-result-fold.js";
12
+ export declare const ATTENTION_BUDGET_ERROR_CODE: "attention_budget_exceeded";
13
+ /** C9: still over `triggerRatio` after every eligible stage — host should compact, not delete. */
14
+ export declare class AttentionBudgetError extends Error {
15
+ readonly code: "attention_budget_exceeded";
16
+ constructor(message?: string);
17
+ }
18
+ export declare function isAttentionBudgetError(error: unknown): error is AttentionBudgetError;
19
+ export declare const DEFAULT_ATTENTION_TRIGGER_RATIO = 0.75;
20
+ export declare const DEFAULT_ATTENTION_COMPACT_RATIO = 0.9;
21
+ export declare const DEFAULT_ATTENTION_THINKING_KEEP_TURNS = 1;
22
+ export declare const DEFAULT_ATTENTION_KEEP_LAST = 3;
23
+ export declare const DEFAULT_ATTENTION_RESERVE_TOKENS = 1024;
24
+ export declare function resolveAttentionReserveTokens(value: unknown): number;
25
+ /** C2: host `maxInputTokens` wins; otherwise `contextWindow - (maxOutputTokens ?? 0) - reserve`.
26
+ * Shared with the `input_ratio` compaction trigger (Task 4). Throws when no cap can be derived. */
27
+ export declare function resolveInputCap(options?: AttentionInputCapOptions, model?: AttentionCompilerContext["model"]): number;
28
+ /** Validate compiler options + resolve the input cap. No provider I/O; unknown or
29
+ * unresolvable input throws here rather than on the first turn. */
30
+ export declare function createAttentionCompiler(options?: AttentionCompilerOptions, context?: AttentionCompilerContext): AttentionCompiler;
31
+ /** Resolve the run's compiler from the agent setting plus an optional run overlay, validating
32
+ * both eagerly (no provider I/O) so a typo fails at run start, not on some later turn (C12).
33
+ * Returns `undefined` when the compiler is off — the assembly path then allocates nothing. */
34
+ export declare function resolveRunAttentionCompiler(agent: AttentionCompilerSetting | undefined, run: AttentionCompilerSetting | undefined, model: AttentionCompilerContext["model"]): AttentionCompiler | undefined;
35
+ /** Caller-owned sticky frontier: what this session leaf already mutated (C10). Mutations are
36
+ * monotonic, so a stubbed call stays stubbed and stripped thinking stays stripped even on a
37
+ * later under-ratio turn — restoring either would rewrite the prompt-cache prefix. */
38
+ export interface AttentionStickyFrontier {
39
+ /** SHA-256 keys of assistant messages whose thinking blocks were stripped. */
40
+ readonly thinking: Set<string>;
41
+ /** Tool call ids whose results were stubbed. */
42
+ readonly toolCallIds: Set<string>;
43
+ }
44
+ export declare function createAttentionStickyFrontier(): AttentionStickyFrontier;
45
+ export interface AttentionCompileOptions {
46
+ readonly compiler: AttentionCompiler;
47
+ /** The exact groups the assembler will send; only `history` and `toolResults` are rewritten. */
48
+ readonly groups: ContextBudgetMessageGroups;
49
+ readonly context?: readonly ContextBlock[];
50
+ readonly skills?: readonly Skill[];
51
+ readonly tools?: readonly ToolDefinition[];
52
+ /** Host fold: its `summarize` wins for the rows the compiler picks, and its age/byte gates
53
+ * decide fold-eligibility. Omitted → the deterministic stub and `keepLast` alone. */
54
+ readonly fold?: ResolvedToolResultFoldOptions;
55
+ readonly frontier?: AttentionStickyFrontier;
56
+ readonly redactor?: SecretRedactor;
57
+ readonly signal?: AbortSignal;
58
+ readonly turn?: number;
59
+ readonly sessionId?: string;
60
+ readonly runId?: string;
61
+ }
62
+ export interface AttentionCompilation {
63
+ /** The input groups unchanged when nothing was mutated; otherwise a new object with the
64
+ * rewritten `history` / `toolResults` arrays. The input groups are never mutated. */
65
+ readonly groups: ContextBudgetMessageGroups;
66
+ readonly mutated: boolean;
67
+ readonly report: AttentionReport;
68
+ }
69
+ /**
70
+ * Measure once, then mutate oldest-first until back under `triggerRatio` (C4), or throw
71
+ * `AttentionBudgetError` when every eligible row is exhausted (C9). Front-of-frozen-prefix
72
+ * (instructions, summaries, input) is never touched, and neither are the caller's arrays.
73
+ */
74
+ export declare function compileAttention(options: AttentionCompileOptions): Promise<AttentionCompilation>;
75
+ /** Serialized sticky frontier (plan 074 P3). Hashes and tool-call ids only: no model output,
76
+ * no payload, nothing that needs redaction, so a durable resume can restore it verbatim. */
77
+ export interface PersistedAttentionStickyFrontier {
78
+ readonly v: 1;
79
+ /** Newest-last, so a restored frontier is the tail of the mutations the session made. */
80
+ readonly thinking: readonly string[];
81
+ readonly toolCallIds: readonly string[];
82
+ }
83
+ /** Bounded snapshot of a live frontier; caller-owned (the runtime persists it, the compiler
84
+ * never writes anywhere). */
85
+ export declare function serializeAttentionStickyFrontier(frontier: AttentionStickyFrontier): PersistedAttentionStickyFrontier;
86
+ /** Validate a persisted frontier from an untrusted store (plan 074 P3). Malformed *entries* are
87
+ * dropped one by one — a key the compiler cannot trust simply re-decides on the next turn —
88
+ * while a malformed *shape* yields `undefined` so the caller starts from an empty frontier.
89
+ * Never throws: a resume must not fail because a checkpoint was hand-edited. */
90
+ export declare function parseAttentionStickyFrontier(value: unknown): PersistedAttentionStickyFrontier | undefined;
91
+ /** Rebuild a frontier from a restored snapshot; callers hold the session that owns it. */
92
+ export declare function restoreAttentionStickyFrontier(persisted: PersistedAttentionStickyFrontier): AttentionStickyFrontier;
93
+ /** Consecutive `truncated` turns that arm compaction by default. */
94
+ export declare const DEFAULT_ATTENTION_TRUNCATION_THRESHOLD = 2;
95
+ export interface AttentionTruncationTriggerOptions {
96
+ /** Truncated turns in a row before the trigger fires (default 2). */
97
+ readonly threshold?: number;
98
+ }
99
+ /**
100
+ * Host-side follow-up policy for `truncated` turns (plan 074 P4). `truncated: true` means the
101
+ * gate ran out of *eligible* rows: stubs cannot hold the request under the ratio, so the honest
102
+ * answer is a new prefix at the next task boundary rather than a silent eviction.
103
+ *
104
+ * Wire it by feeding every `attention_compiled` event to `observe` and handing `trigger` to
105
+ * `CompactionOptions.trigger` (agent config or run options). It fires **once per armed streak**,
106
+ * so a branch is compacted once and then left alone until new truncated turns arrive; a mutated
107
+ * turn that was not truncated clears the streak because the pressure was relieved.
108
+ */
109
+ export interface AttentionTruncationTrigger {
110
+ /** Drop-in `CompactionOptions.trigger`. */
111
+ readonly trigger: CompactionTrigger;
112
+ /** Feed an `attention_compiled` event or an `AttentionReport`; returns the resulting streak. */
113
+ observe(report: {
114
+ readonly truncated?: unknown;
115
+ }): number;
116
+ /** Truncated turns in a row since the last fire or relief. */
117
+ readonly streak: () => number;
118
+ /** Clear the streak (host compacted for its own reasons). */
119
+ reset(): void;
120
+ }
121
+ export declare function createAttentionTruncationTrigger(options?: AttentionTruncationTriggerOptions): AttentionTruncationTrigger;