@roll-agent/runtime 0.25.1 → 0.26.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 (53) hide show
  1. package/dist/engine/agent-session.d.ts +45 -14
  2. package/dist/engine/agent-session.js +1 -1
  3. package/dist/engine/capability-manifest.d.ts +1 -0
  4. package/dist/engine/capability-manifest.js +1 -1
  5. package/dist/engine/compaction-checkpoint.d.ts +619 -174
  6. package/dist/engine/compaction-checkpoint.js +1 -1
  7. package/dist/engine/compaction-media.d.ts +7 -0
  8. package/dist/engine/compaction-media.js +1 -0
  9. package/dist/engine/compaction-semantic-state.d.ts +707 -457
  10. package/dist/engine/compaction-semantic-state.js +1 -1
  11. package/dist/engine/compactor.d.ts +6 -24
  12. package/dist/engine/compactor.js +1 -1
  13. package/dist/engine/conversation-engine.js +1 -1
  14. package/dist/engine/model-request.d.ts +22 -0
  15. package/dist/engine/model-request.js +1 -0
  16. package/dist/engine/request-budget.d.ts +143 -0
  17. package/dist/engine/request-budget.js +1 -0
  18. package/dist/engine/system-prompt.js +1 -1
  19. package/dist/engine/tool-input-activity.d.ts +22 -0
  20. package/dist/engine/tool-input-activity.js +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +1 -1
  23. package/dist/server/runtime-protocol-adapter.js +1 -1
  24. package/dist/server/runtime-server.js +1 -1
  25. package/dist/service/runtime-service.d.ts +4 -3
  26. package/dist/service/runtime-service.js +1 -1
  27. package/dist/store/semantic-ledger.d.ts +120 -0
  28. package/dist/store/semantic-ledger.js +1 -0
  29. package/dist/store/thread-store.d.ts +54 -19
  30. package/dist/store/thread-store.js +1 -1
  31. package/dist/store/turn-input.d.ts +161 -0
  32. package/dist/store/turn-input.js +1 -0
  33. package/dist/tool-bridge/build-tools.d.ts +2 -0
  34. package/dist/tool-bridge/build-tools.js +1 -1
  35. package/dist/tool-bridge/file-tools/edit-file-tool.d.ts +2 -2
  36. package/dist/tool-bridge/file-tools/edit-file-tool.js +1 -1
  37. package/dist/tool-bridge/file-tools/file-change-result.d.ts +2 -0
  38. package/dist/tool-bridge/file-tools/file-change-result.js +1 -1
  39. package/dist/tool-bridge/file-tools/file-io.d.ts +2 -0
  40. package/dist/tool-bridge/file-tools/file-io.js +1 -1
  41. package/dist/tool-bridge/file-tools/file-write-state.d.ts +31 -0
  42. package/dist/tool-bridge/file-tools/file-write-state.js +1 -0
  43. package/dist/tool-bridge/file-tools/text-diff.d.ts +7 -15
  44. package/dist/tool-bridge/file-tools/text-diff.js +1 -1
  45. package/dist/tool-bridge/file-tools/write-file-tool.d.ts +2 -1
  46. package/dist/tool-bridge/file-tools/write-file-tool.js +1 -1
  47. package/dist/tool-bridge/invalid-schedule-retry.d.ts +1 -1
  48. package/dist/tool-bridge/invalid-schedule-retry.js +1 -1
  49. package/dist/tool-bridge/session-exec-tool.d.ts +2 -2
  50. package/dist/tool-bridge/transcript-tool.d.ts +29 -4
  51. package/dist/tool-bridge/transcript-tool.js +1 -1
  52. package/dist/types/events.d.ts +14 -1
  53. package/package.json +4 -3
@@ -1,9 +1,12 @@
1
- import { type ModelMessage } from "ai";
2
- import { type AppOutputResult, type AppOutputDescriptor, type RuntimeDurableEventV13, type RuntimeDurableEventV15, type RuntimeEventCursor, type RuntimeEventId } from "@roll-agent/protocol";
1
+ import { type ContextRecoveryNote, type ContextRecoveryState, type SemanticLedgerRow } from "./semantic-ledger.ts";
2
+ import { type ModelMessage, type UserModelMessage } from "ai";
3
+ import { type AppOutputResult, type AppOutputDescriptor, type RuntimeDurableEventV13, type RuntimeDurableEventV16, type RuntimeEventCursor, type RuntimeEventId } from "@roll-agent/protocol";
3
4
  import { type ToolExecutionPersistenceMetadata, type ToolExecutionRecord, type ToolExecutionRecordId } from "../tool-bridge/tool-execution-record.ts";
4
- import { COMPACTION_TRANSCRIPT_COMPLETENESS, type ArchivedTranscriptMessage, type CompactionCheckpoint, type CompactionCheckpointDraftInput, type CompactionSemanticEvidenceWatermarks } from "../engine/compaction-checkpoint.ts";
5
- import { type CompactionSemanticState } from "../engine/compaction-semantic-state.ts";
5
+ import { COMPACTION_TRANSCRIPT_COMPLETENESS, type ArchivedTranscriptMessage, type CompactionCheckpoint, type CompactionCheckpointV3 } from "../engine/compaction-checkpoint.ts";
6
+ import { type SemanticArchiveItem } from "../engine/compaction-semantic-state.ts";
6
7
  import { type ThreadOrigin, type ScheduledThreadOrigin, type ThreadDerivedFrom } from "./thread-origin.ts";
8
+ import { type AcceptedTurnInput, type TurnInputSkillCheckpoint } from "./turn-input.ts";
9
+ import type { ExplicitSkillCheckpointV1 } from "../engine/explicit-skill-context.ts";
7
10
  export declare const TOOL_EXECUTION_RETENTION_POLICY: {
8
11
  readonly maxBytesPerThread: number;
9
12
  readonly maxRecordsPerThread: 2000;
@@ -18,7 +21,7 @@ export interface AppendRuntimeEventInput {
18
21
  readonly threadId: string;
19
22
  readonly turnId?: string;
20
23
  readonly timestamp: string;
21
- readonly event: RuntimeDurableEventV13 | RuntimeDurableEventV15;
24
+ readonly event: RuntimeDurableEventV13 | RuntimeDurableEventV16;
22
25
  }
23
26
  export interface StoredRuntimeEvent {
24
27
  readonly eventId: RuntimeEventId;
@@ -27,7 +30,7 @@ export interface StoredRuntimeEvent {
27
30
  readonly threadId: string;
28
31
  readonly turnId?: string;
29
32
  readonly timestamp: string;
30
- readonly event: RuntimeDurableEventV15;
33
+ readonly event: RuntimeDurableEventV16;
31
34
  }
32
35
  export interface ResumeRuntimeEventsResult {
33
36
  readonly events: readonly StoredRuntimeEvent[];
@@ -42,7 +45,7 @@ export declare class RuntimeEventCursorGapError extends Error {
42
45
  readonly cursor: RuntimeEventCursor;
43
46
  constructor(cursor: RuntimeEventCursor);
44
47
  }
45
- export declare const TRANSCRIPT_ENTRY_KINDS: readonly ["message", "tool_execution"];
48
+ export declare const TRANSCRIPT_ENTRY_KINDS: readonly ["message", "tool_execution", "legacy_context"];
46
49
  export type TranscriptEntryKind = (typeof TRANSCRIPT_ENTRY_KINDS)[number];
47
50
  export type TranscriptCompleteness = (typeof COMPACTION_TRANSCRIPT_COMPLETENESS)[number];
48
51
  export declare const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS: {
@@ -82,6 +85,10 @@ export interface ForkThreadSnapshotInput {
82
85
  readonly model?: string;
83
86
  }
84
87
  export interface ThreadSnapshot {
88
+ readonly semanticLedger?: readonly SemanticLedgerRow[];
89
+ readonly contextRecovery?: ContextRecoveryState;
90
+ readonly contextRecoveryNotes?: readonly ContextRecoveryNote[];
91
+ readonly turnInputs?: readonly AcceptedTurnInput[];
85
92
  readonly appOutputs?: readonly {
86
93
  readonly operationId: string;
87
94
  readonly result: AppOutputResult;
@@ -138,18 +145,11 @@ export interface CompactionEvidenceWatermarks {
138
145
  }
139
146
  export interface CommitCompactionInput {
140
147
  readonly messages: readonly ModelMessage[];
141
- /** Active projection observed while the checkpoint draft was built. */
142
148
  readonly expectedActiveMessages: readonly ModelMessage[];
143
- /** Latest checkpoint observed while the checkpoint draft was built. */
144
149
  readonly expectedLatestCheckpointId: string | undefined;
145
- readonly draft: CompactionCheckpointDraftInput;
146
- /** Harness-validated semantic recovery state; model output is never persisted directly. */
147
- readonly semanticState: CompactionSemanticState;
148
- /** Evidence actually removed from active history and processed by the semantic draft. */
149
- readonly semanticEvidenceWatermarks: CompactionSemanticEvidenceWatermarks;
150
- readonly evidenceWatermarks: CompactionEvidenceWatermarks;
151
- /** Narrow V1 migration payload; archived atomically and never inserted into active history. */
152
- readonly legacySnapshotTranscriptFragments?: readonly string[];
150
+ readonly summary: string;
151
+ /** Synchronously validate the final model projection inside the CAS transaction. Must not mutate the store. */
152
+ readonly validateCheckpoint?: (checkpoint: CompactionCheckpointV3) => void;
153
153
  }
154
154
  export interface ThreadSessionState {
155
155
  readonly messages: readonly ModelMessage[];
@@ -164,7 +164,22 @@ export type CheckpointTranscriptEntry = {
164
164
  readonly message: ModelMessage;
165
165
  } | ({
166
166
  readonly kind: "tool_execution";
167
- } & SequencedToolExecutionRecord);
167
+ } & SequencedToolExecutionRecord) | {
168
+ readonly kind: "legacy_context";
169
+ /** 0 is checkpoint metadata; subsequent ordinals identify ledger items at that generation. */
170
+ readonly sequence: number;
171
+ readonly value: {
172
+ readonly checkpoint: CompactionCheckpoint;
173
+ readonly memo?: string;
174
+ readonly notes?: string;
175
+ } | {
176
+ readonly ledgerItem: SemanticArchiveItem;
177
+ readonly generation: number;
178
+ readonly status: string;
179
+ readonly scope: string;
180
+ readonly resolution: string | null;
181
+ };
182
+ };
168
183
  export interface CheckpointTranscriptPage {
169
184
  readonly checkpointId: CompactionCheckpoint["id"];
170
185
  readonly kind: TranscriptEntryKind;
@@ -217,6 +232,12 @@ export declare class ThreadStore {
217
232
  countRuntimeEvents(threadId: string): number;
218
233
  private validateRuntimeEventCursor;
219
234
  private enforceRuntimeEventRetentionInTransaction;
235
+ /** Internal acceptance boundary; the same receipt cannot reinsert compacted active history. */
236
+ acceptTurnInput(threadId: string, inputId: string, message: UserModelMessage): AcceptedTurnInput;
237
+ getTurnInput(threadId: string, inputId: string): AcceptedTurnInput | undefined;
238
+ listTurnInputs(threadId: string): readonly AcceptedTurnInput[];
239
+ getTurnInputSkillCheckpoint(threadId: string, sequence: number): TurnInputSkillCheckpoint | undefined;
240
+ recordTurnSkillCheckpoint(threadId: string, inputId: string, checkpoint: ExplicitSkillCheckpointV1): void;
220
241
  appendMessages(threadId: string, messages: readonly ModelMessage[], options?: AppendMessagesOptions): void;
221
242
  recoverUncoveredToolExecutions(threadId: string, createMessage: (records: readonly SequencedToolExecutionRecord[]) => ModelMessage): ModelMessage | undefined;
222
243
  private appendMessagesInTransaction;
@@ -244,8 +265,22 @@ export declare class ThreadStore {
244
265
  getLatestSummaryCheckpoint(threadId: string): CompactionCheckpoint | undefined;
245
266
  getCheckpoint(threadId: string, checkpointId: string): CompactionCheckpoint | undefined;
246
267
  loadSessionState(threadId: string): ThreadSessionState;
247
- commitCompaction(threadId: string, input: CommitCompactionInput): CompactionCheckpoint;
268
+ commitCompaction(threadId: string, input: CommitCompactionInput): CompactionCheckpointV3;
269
+ private legacySummaryFragmentsToArchive;
270
+ getContextRecovery(threadId: string): ContextRecoveryState | undefined;
271
+ /** Historical ledger access; new task summaries do not add semantic facts. */
272
+ getSemanticLedgerRows(threadId: string): {
273
+ status: string;
274
+ generation: number;
275
+ scope: string;
276
+ category: "resource" | "decision" | "running_session" | "goal" | "constraint" | "completed_work" | "pending_work" | "uncertainty";
277
+ item_id: string;
278
+ first_tool_sequence: number;
279
+ item_json: string;
280
+ resolution_json?: string | null | undefined;
281
+ }[];
248
282
  readCheckpointTranscript(threadId: string, options: ReadCheckpointTranscriptOptions): CheckpointTranscriptPage;
283
+ private listLegacyContextEntries;
249
284
  private maxTranscriptMessageSequence;
250
285
  private maxToolExecutionSequence;
251
286
  private withCurrentTranscriptCompleteness;
@@ -1 +1 @@
1
- import{chmodSync as e,existsSync as t,mkdirSync as n}from"node:fs";import{homedir as r}from"node:os";import{resolve as s}from"node:path";import{randomUUID as o}from"node:crypto";import{DatabaseSync as i}from"node:sqlite";import{modelMessageSchema as a}from"ai";import{APP_OUTPUT_LIMITS as c,appOutputResultSchema as d,appOutputDescriptorSchema as E,describeAppOutput as _,RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as u,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as h,runtimeDurableEventV15Schema as p,runtimeEventCursorSchema as T,runtimeEventIdSchema as l,timestampSchema as m}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as N,prepareToolExecutionRecordForPersistence as S,prepareAppOutputForPersistence as R,redactSecretText as g}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as O,COMPACTION_TRANSCRIPT_COMPLETENESS as I,TRANSCRIPT_MESSAGE_PROVENANCES as v,UnsupportedCompactionCheckpointVersionError as L,archivedTranscriptMessageSchema as x,createCompactionCheckpoint as A,createCompactionCheckpointDraft as C,parseCompactionCheckpoint as f}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as q}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as b}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as M,repairActiveToolProtocol as D}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as w}from"../engine/explicit-skill-context.js";import{threadOriginSchema as U,threadDerivedFromSchema as y,scheduledThreadOriginSchema as F}from"./thread-origin.js";const k=7,W=100,X=500,j=50,H=500,P=100,B=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:u,maxRecordsPerThread:h,maxAgeMs:2592e6};export class RuntimeEventCursorExpiredError extends Error{cursor;constructor(e){super("Runtime event cursor is older than the retained event prefix"),this.name="RuntimeEventCursorExpiredError",this.cursor=e}}export class RuntimeEventCursorGapError extends Error{cursor;constructor(e){super("Runtime event cursor does not belong to the current Thread event log"),this.name="RuntimeEventCursorGapError",this.cursor=e}}export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution"];export const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS={rawTranscript:"raw_transcript",recoveryEvidence:"recovery_evidence"};const G="legacy_assumed";export function expandTilde(e){return e.startsWith("~/")?s(r(),e.slice(2)):e}export function defaultThreadsDir(){return s(r(),".roll-agent","threads")}function Y(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function K(e,t){return"valid"===e.status?e:"fallback"===e.status?{status:"fallback",reason:e.reason,...void 0!==t?{lastValidCheckpointId:t.id}:{}}:{status:"skipped",...void 0!==t?{lastValidCheckpointId:t.id}:{}}}function $(e){if(Number.isInteger(e.schema_version)&&e.schema_version>O)throw new L(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=f(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function J(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:U.parse(e.origin_json?JSON.parse(e.origin_json):{kind:"interactive"}),...e.derived_from_json?{derivedFrom:y.parse(JSON.parse(e.derived_from_json))}:{}}}function V(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>V(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,V(n,t)])):e}function z(){return l.parse(o())}function Q(e,t,n){return T.parse(`rte1:${e}:${String(t)}:${n}`)}function Z(e){const t=T.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=l.safeParse(n[3]);return void 0===n[1]||!Number.isSafeInteger(r)||r<0||!s.success?void 0:{eventLogId:n[1],threadSequence:r,eventId:s.data}}function ee(e){return new Date(Date.parse(m.parse(e))).toISOString()}function te(e,t,n){const r=l.parse(n.event_id),s=p.parse(JSON.parse(n.event_json));return{eventId:r,cursor:Q(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:"tool.completed"===s.type&&null!=n.app_output_json?{...s,appOutput:E.parse(JSON.parse(n.app_output_json))}:s}}export class ThreadStore{db;now;readOnly;constructor(r=defaultThreadsDir(),o={}){this.now=o.now??(()=>new Date),this.readOnly=o.readOnly??!1;const a=expandTilde(r);if(o.readOnly){this.db=new i(s(a,"threads.db"),{readOnly:!0});try{this.db.exec(`PRAGMA busy_timeout = ${String(B)}; PRAGMA query_only = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>7)throw new Error(`Unsupported ThreadStore schema v${String(e.user_version)}`)}catch(e){throw this.db.close(),e}return}t(a)||n(a,{recursive:!0,mode:448}),"win32"!==process.platform&&e(a,448);const c=s(a,"threads.db");if(this.db=new i(c),"win32"!==process.platform)try{e(c,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(B)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>7)throw new Error(`ThreadStore schema v${String(e.user_version)} 高于当前支持的 v${String(7)}`);this.db.exec("BEGIN IMMEDIATE");try{if(this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),e.user_version<7){const e=this.db.prepare("PRAGMA table_info(threads)").all();for(const t of["origin_json","derived_from_json"])e.some(e=>e.name===t)||this.db.exec(`ALTER TABLE threads ADD COLUMN ${t} TEXT`)}Y(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS operation_app_outputs (\n thread_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n result_json TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n byte_length INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, operation_id),\n FOREIGN KEY (thread_id, operation_id) REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS tool_execution_context_coverage (\n thread_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n representation TEXT NOT NULL\n CHECK (representation IN ('raw_transcript', 'recovery_evidence', 'legacy_assumed')),\n transcript_sequence INTEGER,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, execution_id),\n CHECK (\n (representation = 'legacy_assumed' AND transcript_sequence IS NULL)\n OR (representation <> 'legacy_assumed' AND transcript_sequence IS NOT NULL)\n ),\n FOREIGN KEY (thread_id, execution_id)\n REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE,\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);\n CREATE TABLE IF NOT EXISTS thread_runtime_event_state (\n thread_id TEXT PRIMARY KEY,\n event_log_id TEXT NOT NULL UNIQUE,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n retained_from_sequence INTEGER NOT NULL\n CHECK (retained_from_sequence >= 0 AND retained_from_sequence <= next_sequence),\n retained_predecessor_event_id TEXT,\n latest_event_id TEXT,\n retained_count INTEGER NOT NULL CHECK (retained_count >= 0),\n retained_bytes INTEGER NOT NULL CHECK (retained_bytes >= 0),\n CHECK (\n (next_sequence = 0\n AND retained_from_sequence = 0\n AND retained_predecessor_event_id IS NULL\n AND latest_event_id IS NULL)\n OR (next_sequence > 0 AND latest_event_id IS NOT NULL)\n ),\n CHECK (\n (retained_from_sequence = 0 AND retained_predecessor_event_id IS NULL)\n OR (retained_from_sequence > 0 AND retained_predecessor_event_id IS NOT NULL)\n ),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS runtime_events (\n thread_id TEXT NOT NULL,\n thread_sequence INTEGER NOT NULL CHECK (thread_sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n turn_id TEXT,\n event_timestamp TEXT NOT NULL,\n event_json TEXT NOT NULL,\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, thread_sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_runtime_events_thread_created\n ON runtime_events(thread_id, created_at, thread_sequence);"),this.db.prepare("PRAGMA table_info(runtime_events)").all().some(e=>"app_output_json"===e.name)||this.db.exec("ALTER TABLE runtime_events ADD COLUMN app_output_json TEXT"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),e.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),e.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),e.user_version<6&&this.db.prepare("INSERT OR IGNORE INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, NULL, created_at\n FROM tool_executions").run(G),this.initializeMissingRuntimeEventStatesInTransaction();const t=this.now().toISOString();this.enforceAllToolExecutionRetentionInTransaction(t),this.enforceAllRuntimeEventRetentionInTransaction(t),this.db.exec(`PRAGMA user_version = ${String(7)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=N(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceAppOutputRetention(n),this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=a.parse(JSON.parse(t.message_json)),r=w(e);n.run(JSON.stringify(r),t.storage_rowid)}}}initializeMissingRuntimeEventStatesInTransaction(){const e=this.db.prepare("SELECT threads.id\n FROM threads\n LEFT JOIN thread_runtime_event_state\n ON thread_runtime_event_state.thread_id = threads.id\n WHERE thread_runtime_event_state.thread_id IS NULL").all(),t=this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)");for(const n of e)t.run(n.id,o())}enforceAllRuntimeEventRetentionInTransaction(e){const t=this.db.prepare("SELECT thread_id AS id FROM thread_runtime_event_state").all();for(const n of t)this.enforceRuntimeEventRetentionInTransaction(n.id,e)}createThread(e={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const t=e.id??o(),n=this.now().toISOString(),r=U.parse(e.origin??{kind:"interactive"}),s=void 0===e.derivedFrom?void 0:y.parse(e.derivedFrom);this.db.exec("SAVEPOINT create_thread");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at, origin_json, derived_from_json) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n,JSON.stringify(r),void 0===s?null:JSON.stringify(s)),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,I[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)").run(t,o()),this.db.exec("RELEASE create_thread"),t}catch(e){throw this.db.exec("ROLLBACK TO create_thread; RELEASE create_thread"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?J(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(J).filter(t=>void 0===e.origin||t.origin.kind===e.origin)}backfillScheduledOrigin(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=F.parse(t);return this.db.prepare("UPDATE threads SET origin_json = ? WHERE id = ? AND origin_json IS NULL").run(JSON.stringify(n),e).changes>0}readSnapshot(e){this.db.exec("BEGIN");try{const t=this.getThread(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在`);const n=this.now().toISOString(),r=this.getMessages(e),s=e=>void 0!==this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e),o=s("transcript_messages")?this.db.prepare("SELECT sequence, provenance, created_at, message_json FROM transcript_messages WHERE thread_id = ? ORDER BY sequence").all(e).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):r.map((e,n)=>x.parse({sequence:n,message:e,provenance:"legacy_snapshot",createdAt:t.createdAt})),i=s("tool_executions")?this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? ORDER BY sequence").all(e).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence})):[],a=s("compaction_checkpoints")?this.db.prepare("SELECT schema_version, checkpoint_json FROM compaction_checkpoints WHERE thread_id = ? ORDER BY generation").all(e).map($):[],c=s("tool_execution_context_coverage")?this.db.prepare("SELECT execution_id AS executionId, representation, transcript_sequence AS transcriptSequence, created_at AS createdAt FROM tool_execution_context_coverage WHERE thread_id = ?").all(e):i.map(e=>({executionId:e.id,representation:"legacy_assumed",transcriptSequence:null,createdAt:n})),d=s("thread_tool_execution_state")?this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e):void 0,E={thread:t,capturedAt:n,appOutputs:s("operation_app_outputs")?this.db.prepare("SELECT operation_id, result_json, expires_at FROM operation_app_outputs WHERE thread_id = ?").all(e).map(e=>({operationId:e.operation_id,result:this.readAppOutputRow(e.result_json,e.expires_at),expiresAt:e.expires_at})):[],messages:r,transcript:o,toolExecutions:i,checkpoints:a,toolExecutionCoverage:c,nextToolExecutionSequence:d?.next_sequence??(i.at(-1)?.sequence??-1)+1,transcriptCompleteness:s("thread_transcript_state")?this.getTranscriptCompleteness(e):"legacy_snapshot"};return this.db.exec("COMMIT"),E}catch(e){throw this.db.exec("ROLLBACK"),e}}forkSnapshot(e,t={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=new Map(e.checkpoints.map(e=>[e.id,o()])),r=e.checkpoints.map(e=>{const t=f(V(e,n));return f({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),s=e.messages.map(e=>a.parse(V(e,n))),i=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const n=this.createThread({...void 0===t.id?{}:{id:t.id},...void 0===t.title?void 0===e.thread.title?{}:{title:e.thread.title}:{title:t.title},...void 0===t.model?{}:{model:t.model},origin:{kind:"interactive"},derivedFrom:{threadId:e.thread.id,origin:e.thread.origin,capturedAt:e.capturedAt}});this.replaceMessagesInTransaction(n,D(s).messages,i);const o=this.db.prepare("INSERT INTO transcript_messages (thread_id, sequence, role, message_json, provenance, created_at) VALUES (?, ?, ?, ?, ?, ?)");for(const t of e.transcript){const e=x.parse(t);o.run(n,e.sequence,e.message.role,JSON.stringify(e.message),e.provenance,e.createdAt)}this.db.prepare("UPDATE thread_transcript_state SET completeness = ? WHERE thread_id = ?").run(e.transcriptCompleteness,n);const a=this.db.prepare("INSERT INTO tool_executions (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");for(const{sequence:t,...r}of e.toolExecutions){const e=N(r);a.run(n,t,e.id,e.toolCallId,e.agentName,e.toolName,JSON.stringify(e),e.createdAt)}for(const t of e.appOutputs??[])this.insertAppOutput(n,t.operationId,t.result,t.expiresAt);this.enforceAppOutputRetention(n),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(e.nextToolExecutionSequence,n);const c=this.db.prepare("INSERT INTO tool_execution_context_coverage (thread_id, execution_id, representation, transcript_sequence, created_at) VALUES (?, ?, ?, ?, ?)");for(const t of e.toolExecutionCoverage)c.run(n,t.executionId,t.representation,t.transcriptSequence,t.createdAt);const d=this.db.prepare("INSERT INTO compaction_checkpoints (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence, tool_from_sequence, tool_through_sequence, checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const e of r)d.run(e.id,n,e.generation,e.version,e.transcript.messages.fromSequenceExclusive,e.transcript.messages.throughSequence,e.transcript.toolExecutions.fromSequenceExclusive,e.transcript.toolExecutions.throughSequence,JSON.stringify(e),e.createdAt);return this.db.exec("COMMIT"),n}catch(e){throw this.db.exec("ROLLBACK"),e}}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}countTranscriptMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM transcript_messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}updateModel(e,t){this.db.prepare("UPDATE threads SET model = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){const t=p.parse(e.event),n="tool.completed"===t.type?(({appOutput:e,...t})=>t)(t):t,r=JSON.stringify(n),s=ee(e.timestamp),o=Buffer.byteLength(JSON.stringify(t),"utf8")+Buffer.byteLength(s,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(o>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e.threadId);if(void 0===n)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=z(),a=n.next_sequence;return this.db.prepare("INSERT INTO runtime_events\n (thread_id, thread_sequence, event_id, turn_id, event_timestamp,\n event_json, size_bytes, created_at, app_output_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,s,r,o,s,"tool.completed"===t.type?JSON.stringify(t.appOutput):null),this.db.prepare("UPDATE thread_runtime_event_state\n SET next_sequence = ?, latest_event_id = ?,\n retained_count = retained_count + 1,\n retained_bytes = retained_bytes + ?\n WHERE thread_id = ?").run(a+1,i,o,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,s),this.db.exec("COMMIT"),{eventId:i,cursor:Q(n.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:s,event:t}}catch(e){throw this.db.exec("ROLLBACK"),e}}getRuntimeEventCursor(e){const t=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);return 0===t.next_sequence||null===t.latest_event_id?null:Q(t.event_log_id,t.next_sequence-1,l.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:Z(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);this.db.exec("BEGIN IMMEDIATE");try{this.enforceRuntimeEventRetentionInTransaction(e,this.now().toISOString());const r=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===r)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);if(null===t&&r.retained_from_sequence>0)throw new RuntimeEventCursorExpiredError(t);if(null!==t&&void 0!==n&&this.validateRuntimeEventCursor(r,t,n,e),0===r.next_sequence||null===r.latest_event_id)return this.db.exec("COMMIT"),{events:[],throughCursor:null,replayedCount:0};const s=r.next_sequence-1,o=n?.threadSequence??-1,i=this.db.prepare("SELECT *\n FROM runtime_events\n WHERE thread_id = ?\n AND thread_sequence > ?\n AND thread_sequence <= ?\n ORDER BY thread_sequence ASC").all(e,o,s).map(t=>te(r.event_log_id,e,t)),a=Q(r.event_log_id,s,l.parse(r.latest_event_id));return this.db.exec("COMMIT"),{events:i,throughCursor:a,replayedCount:i.length}}catch(e){throw e instanceof RuntimeEventCursorExpiredError||e instanceof RuntimeEventCursorGapError?this.db.exec("COMMIT"):this.db.exec("ROLLBACK"),e}}countRuntimeEvents(e){return this.db.prepare("SELECT COUNT(*) AS count FROM runtime_events WHERE thread_id = ?").get(e).count}validateRuntimeEventCursor(e,t,n,r){if(n.eventLogId!==e.event_log_id||n.threadSequence>=e.next_sequence)throw new RuntimeEventCursorGapError(t);const s=e.retained_from_sequence-1;if(n.threadSequence<s)throw new RuntimeEventCursorExpiredError(t);if(n.threadSequence===s){if(n.eventId!==e.retained_predecessor_event_id)throw new RuntimeEventCursorGapError(t);return}const o=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(r,n.threadSequence);if(void 0===o||o.event_id!==n.eventId)throw new RuntimeEventCursorGapError(t)}enforceRuntimeEventRetentionInTransaction(e,t){const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===n||0===n.retained_count)return;let r=n.retained_from_sequence;if(n.retained_count>RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread){const t=this.db.prepare("SELECT thread_sequence AS keep_from\n FROM runtime_events\n WHERE thread_id = ?\n ORDER BY thread_sequence DESC\n LIMIT 1 OFFSET ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread-1);r=Math.max(r,t?.keep_from??n.next_sequence)}if(n.retained_bytes>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread){const t=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM (\n SELECT thread_sequence,\n SUM(size_bytes) OVER (\n ORDER BY thread_sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM runtime_events\n WHERE thread_id = ?\n )\n WHERE retained_bytes <= ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread);r=Math.max(r,t.keep_from??n.next_sequence)}const s=new Date(Date.parse(t)-RUNTIME_EVENT_RETENTION_POLICY.maxAgeMs).toISOString(),o=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM runtime_events\n WHERE thread_id = ? AND created_at >= ?").get(e,s);if(r=Math.max(r,o.keep_from??n.next_sequence),r<=n.retained_from_sequence)return;const i=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(e,r-1);if(void 0===i)throw new Error("Runtime event retention boundary is not a continuous retained prefix");const a=this.db.prepare("SELECT COUNT(*) AS removed_count, SUM(size_bytes) AS removed_bytes\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence < ?").get(e,r);this.db.prepare("DELETE FROM runtime_events WHERE thread_id = ? AND thread_sequence < ?").run(e,r),this.db.prepare("UPDATE thread_runtime_event_state\n SET retained_from_sequence = ?, retained_predecessor_event_id = ?,\n retained_count = retained_count - ?,\n retained_bytes = retained_bytes - ?\n WHERE thread_id = ?").run(r,i.event_id,a.removed_count,a.removed_bytes??0,e)}appendMessages(e,t,n={}){const r=n.toolExecutionCoverage;if(0===t.length){if(void 0!==r&&r.executionIds.length>0)throw new Error("Tool execution coverage 必须与至少一条 transcript message 原子写入");return}if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);if(void 0!==r&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.rawTranscript&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence)throw new Error("Tool execution coverage representation 无效");const s=r?.executionIds??[],o=new Set;for(const e of s){if("string"!=typeof e||0===e.length)throw new Error("Tool execution coverage execution id 无效");if(o.has(e))throw new Error(`Tool execution coverage execution id 重复: ${e}`);o.add(e)}const i=t.map(e=>w(a.parse(e))),c=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.appendMessagesInTransaction(e,i,c,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}recoverUncoveredToolExecutions(e,t){this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=this.uncoveredToolExecutionRecords(e,{afterSequence:-1,throughSequence:Number.MAX_SAFE_INTEGER,toolCallId:void 0,limit:Number.MAX_SAFE_INTEGER});if(0===n.length)return void this.db.exec("COMMIT");const r=w(a.parse(t(n))),s={executionIds:n.map(e=>e.id),representation:TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence};return this.appendMessagesInTransaction(e,[r],(new Date).toISOString(),s),this.db.exec("COMMIT"),r}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessagesInTransaction(e,t,n,r){const s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),o=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)"),i=this.db.prepare("INSERT INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, ?, ?\n FROM tool_executions\n WHERE thread_id = ?\n AND id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE thread_id = ? AND execution_id = ?\n )"),a=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),c=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let d=a.maxIdx+1,E=c.maxSequence+1;for(const r of t){const t=JSON.stringify(r);s.run(e,d,r.role,t,n),o.run(e,E,r.role,t,v[0],n),d+=1,E+=1}const _=E-1;if(void 0!==r){for(const t of r.executionIds){const s=i.run(r.representation,_,n,e,t,e,t);if(1!==Number(s.changes))throw new Error(`Tool execution "${t}" 不属于 Thread "${e}" 或已被 transcript 覆盖`)}this.enforceToolExecutionRetentionInTransaction(e,n)}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>a.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>a.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const o=w(t);r.run(e,s,o.role,JSON.stringify(o),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=S(t),r=JSON.stringify(n),s=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const o=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===o)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const i=o.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,i,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(i+1,e),void 0!==t.appOutput&&this.insertAppOutput(e,t.id,t.appOutput,new Date(Date.parse(s)+c.maxAgeMs).toISOString()),this.enforceAppOutputRetention(e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}readAppOutputRow(e,t){const n=d.parse(JSON.parse(e));return"available"===n.status&&Date.parse(t)<=this.now().getTime()?{status:"expired"}:n}insertAppOutput(e,t,n,r){const s="available"===n.status&&Date.parse(r)<=this.now().getTime()?{status:"expired"}:R(n),o=JSON.stringify(s);this.db.prepare("INSERT INTO operation_app_outputs\n (thread_id, operation_id, result_json, descriptor_json, byte_length, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e,t,o,JSON.stringify(_(s)),"available"===s.status?Buffer.byteLength(o,"utf8"):0,r)}enforceAppOutputRetention(e){const t=JSON.stringify({status:"expired"});this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND byte_length > 0 AND expires_at <= ?").run(t,t,e,this.now().toISOString()),this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND operation_id IN (\n SELECT operation_id FROM (\n SELECT o.operation_id,\n ROW_NUMBER() OVER (ORDER BY e.sequence DESC) AS rank,\n SUM(o.byte_length) OVER (ORDER BY e.sequence DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS bytes\n FROM operation_app_outputs o JOIN tool_executions e ON e.thread_id = o.thread_id AND e.id = o.operation_id\n WHERE o.thread_id = ? AND o.byte_length > 0\n ) WHERE rank > ? OR bytes > ?\n )").run(t,t,e,e,c.threadRecords,c.threadBytes)}getAppOutputDescriptor(e,t){if(void 0===this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'operation_app_outputs'").get())return void 0===this.db.prepare("SELECT 1 FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t)?void 0:{status:"not_provided"};const n=this.db.prepare("SELECT o.descriptor_json, o.expires_at FROM tool_executions e\n LEFT JOIN operation_app_outputs o ON o.thread_id = e.thread_id AND o.operation_id = e.id\n WHERE e.thread_id = ? AND e.id = ?").get(e,t);if(void 0===n)return;if(null===n.descriptor_json)return{status:"not_provided"};const r=E.parse(JSON.parse(n.descriptor_json));return"available"===r.status&&null!==n.expires_at&&Date.parse(n.expires_at)<=this.now().getTime()?{status:"expired"}:r}getAppOutput(e,t){const n=this.getAppOutputDescriptor(e,t);if(void 0===n||"available"!==n.status)return n;const r=this.db.prepare("SELECT result_json FROM operation_app_outputs WHERE thread_id = ? AND operation_id = ?").get(e,t);return void 0===r?{status:"not_provided"}:d.parse(JSON.parse(r.result_json))}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )").run(e,n);r=Number(s.changes)>0;const o=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(o.length>0){const t=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND sequence = ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )");for(const n of o){const s=t.run(e,n.sequence);r=Number(s.changes)>0||r}}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence}))}listUncoveredToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);return r<=n?[]:this.uncoveredToolExecutionRecords(e,{afterSequence:n,throughSequence:r,toolCallId:t.toolCallId,limit:s})}resolveToolExecutionListRange(e){const t=e.afterSequence??-1,n=e.throughSequence??Number.MAX_SAFE_INTEGER,r=e.limit??W;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>X)throw new Error(`limit 必须是 1-${String(X)} 之间的整数`);if(!Number.isInteger(n)||n<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");return{afterSequence:t,throughSequence:n,limit:r}}uncoveredToolExecutionRecords(e,t){return this.db.prepare("SELECT tool_executions.sequence, tool_executions.record_json\n FROM tool_executions\n LEFT JOIN tool_execution_context_coverage\n ON tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n WHERE tool_executions.thread_id = ?\n AND tool_executions.sequence > ?\n AND tool_executions.sequence <= ?\n AND (? IS NULL OR tool_executions.tool_call_id = ?)\n AND tool_execution_context_coverage.execution_id IS NULL\n ORDER BY tool_executions.sequence ASC\n LIMIT ?").all(e,t.afterSequence,t.throughSequence,t.toolCallId??null,t.toolCallId??null,t.limit).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??W;this.validateRecentPage(n,r,X);const s=this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...N(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===I[0]?I[0]:I[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??j;if(this.validateTranscriptRange(n,r,s,H),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}listRecentTranscriptMessages(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??j;this.validateRecentPage(n,r,H);const s=this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,$(n))}catch(e){if(e instanceof L)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=$(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof L)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,$(n))}catch(e){if(e instanceof L)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:D(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>a.parse(e)),r=t.expectedActiveMessages.map(e=>a.parse(e)),s=D(n);if(s.status!==M.valid){const e=[...s.removedToolCallIds,...s.removedToolResultIds].filter((e,t,n)=>n.indexOf(e)===t).sort((e,t)=>e.localeCompare(t));throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const o=C(t.draft),i=q.parse(t.legacySnapshotTranscriptFragments??[]),c=q.parse(i.map(e=>g(e))).map(e=>a.parse(b(e))),d=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=D(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const i=this.getLatestCheckpoint(e);if(i?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const a=this.getLatestSummaryCheckpoint(e),E=K(o.summary,a),_=this.getTranscriptCompleteness(e),u=C({...o,summary:E,..._===I[1]&&"valid"===o.toolState.integrityStatus?{toolState:{...o.toolState,integrityStatus:"sanitized"}}:{}}),h=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration\n FROM compaction_checkpoints\n WHERE thread_id = ?").get(e),p=this.maxTranscriptMessageSequence(e),T=this.maxToolExecutionSequence(e),l=t.evidenceWatermarks.transcriptMessagesThroughSequence,m=t.evidenceWatermarks.toolExecutionsThroughSequence,N=i?.transcript.messages.throughSequence??-1,S=i?.transcript.toolExecutions.throughSequence??-1,R=2===i?.version?i.semanticEvidence.messagesThroughSequence:-1,g=2===i?.version?i.semanticEvidence.toolExecutionsThroughSequence:-1;if(c.length>0&&1!==i?.version)throw new Error("Legacy snapshot transcript archive requires a V1 parent checkpoint");if(!Number.isInteger(l)||l<N||l>p)throw new Error(`Compaction transcript message watermark ${String(l)} is outside the available range ${String(N)}-${String(p)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<R||t.semanticEvidenceWatermarks.messagesThroughSequence>l)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(R)}-${String(l)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<g||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>m)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(g)}-${String(m)}`);if(!Number.isInteger(m)||m<S||m>T)throw new Error(`Compaction tool execution watermark ${String(m)} is outside the available range ${String(S)}-${String(T)}`);let O=l;if(c.length>0){if(l!==p)throw new Error("Legacy snapshot transcript archive requires the latest message watermark");const t=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");let n=p+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),v[1],d),n+=1;O=n-1}const L={messages:{fromSequenceExclusive:N,throughSequence:O},toolExecutions:{fromSequenceExclusive:S,throughSequence:m},completeness:_},x=A({draft:u,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:h.maxGeneration+1,transcript:L,...void 0!==i?{previousCheckpointId:i.id}:{},createdAt:d});return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version,\n message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence,\n checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(x.id,e,x.generation,x.version,x.transcript.messages.fromSequenceExclusive,x.transcript.messages.throughSequence,x.transcript.toolExecutions.fromSequenceExclusive,x.transcript.toolExecutions.throughSequence,JSON.stringify(x),x.createdAt),this.replaceMessagesInTransaction(e,n,d),this.db.exec("COMMIT"),x}catch(e){throw this.db.exec("ROLLBACK"),e}}readCheckpointTranscript(e,t){if("message"!==t.kind&&"tool_execution"!==t.kind)throw new Error("kind 必须是 message 或 tool_execution");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??j,s="message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,o=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(o,s.throughSequence,r,P);const i="message"===t.kind?this.listTranscriptMessages(e,{afterSequence:o,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:o,throughSequence:s.throughSequence,limit:Math.min(r+1,X)}).map(e=>({kind:"tool_execution",...e})),a=i.length>r,c=a?i.slice(0,r):i,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...a&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=I[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:I[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}validateRecentPage(e,t,n){if(!Number.isSafeInteger(e)||e<0||e>Number.MAX_SAFE_INTEGER)throw new Error("beforeSequence 必须是非负安全整数");if(!Number.isInteger(t)||t<1||t>n)throw new Error(`limit 必须是 1-${String(n)} 之间的整数`)}close(){this.db.close()}}
1
+ import{SemanticLedger as e,SEMANTIC_LEDGER_SQL as t,contextRecoveryNoteSchema as n}from"./semantic-ledger.js";import{chmodSync as r,existsSync as s,mkdirSync as o}from"node:fs";import{homedir as i}from"node:os";import{resolve as a}from"node:path";import{randomUUID as c}from"node:crypto";import{DatabaseSync as d}from"node:sqlite";import{modelMessageSchema as E}from"ai";import{APP_OUTPUT_LIMITS as _,appOutputResultSchema as u,appOutputDescriptorSchema as p,describeAppOutput as h,RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as T,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as l,runtimeDurableEventV16Schema as N,runtimeEventCursorSchema as m,runtimeEventIdSchema as R,timestampSchema as S}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as g,prepareToolExecutionRecordForPersistence as O,prepareAppOutputForPersistence as I,redactSecretText as L}from"../tool-bridge/tool-execution-record.js";import{TASK_SUMMARY_CHECKPOINT_VERSION as v,COMPACTION_TRANSCRIPT_COMPLETENESS as x,TRANSCRIPT_MESSAGE_PROVENANCES as A,UnsupportedCompactionCheckpointVersionError as C,archivedTranscriptMessageSchema as f,createSummaryCheckpoint as b,buildCompactionToolState as q,parseCompactionCheckpoint as M}from"../engine/compaction-checkpoint.js";import{listSemanticArchiveItems as w,semanticArchiveItemSchema as y}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as D,readCompactionSummaryPayload as U}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as F,repairActiveToolProtocol as k}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as j}from"../engine/explicit-skill-context.js";import{threadOriginSchema as X,threadDerivedFromSchema as W,scheduledThreadOriginSchema as H}from"./thread-origin.js";import{acceptedTurnInputSchema as B,turnInputIdSchema as P,turnInputSkillCheckpointSchema as G}from"./turn-input.js";const Y=11,K=100,J=500,$=50,V=500,z=100,Q=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:T,maxRecordsPerThread:l,maxAgeMs:2592e6};export class RuntimeEventCursorExpiredError extends Error{cursor;constructor(e){super("Runtime event cursor is older than the retained event prefix"),this.name="RuntimeEventCursorExpiredError",this.cursor=e}}export class RuntimeEventCursorGapError extends Error{cursor;constructor(e){super("Runtime event cursor does not belong to the current Thread event log"),this.name="RuntimeEventCursorGapError",this.cursor=e}}export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution","legacy_context"];export const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS={rawTranscript:"raw_transcript",recoveryEvidence:"recovery_evidence"};const Z="legacy_assumed";export function expandTilde(e){return e.startsWith("~/")?a(i(),e.slice(2)):e}export function defaultThreadsDir(){return a(i(),".roll-agent","threads")}function ee(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function te(e){if(Number.isInteger(e.schema_version)&&e.schema_version>v)throw new C(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=M(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function ne(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:X.parse(e.origin_json?JSON.parse(e.origin_json):{kind:"interactive"}),...e.derived_from_json?{derivedFrom:W.parse(JSON.parse(e.derived_from_json))}:{}}}function re(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>re(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,re(n,t)])):e}function se(){return R.parse(c())}function oe(e,t,n){return m.parse(`rte1:${e}:${String(t)}:${n}`)}function ie(e){const t=m.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=R.safeParse(n[3]);return void 0===n[1]||!Number.isSafeInteger(r)||r<0||!s.success?void 0:{eventLogId:n[1],threadSequence:r,eventId:s.data}}function ae(e){return new Date(Date.parse(S.parse(e))).toISOString()}function ce(e,t,n){const r=R.parse(n.event_id),s=N.parse(JSON.parse(n.event_json));return{eventId:r,cursor:oe(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:"tool.completed"===s.type&&null!=n.app_output_json?{...s,appOutput:p.parse(JSON.parse(n.app_output_json))}:s}}export class ThreadStore{db;now;readOnly;constructor(e=defaultThreadsDir(),t={}){this.now=t.now??(()=>new Date),this.readOnly=t.readOnly??!1;const n=expandTilde(e);if(t.readOnly){this.db=new d(a(n,"threads.db"),{readOnly:!0});try{this.db.exec(`PRAGMA busy_timeout = ${String(Q)}; PRAGMA query_only = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>Y)throw new Error(`Unsupported ThreadStore schema v${String(e.user_version)}`)}catch(e){throw this.db.close(),e}return}s(n)||o(n,{recursive:!0,mode:448}),"win32"!==process.platform&&r(n,448);const i=a(n,"threads.db");if(this.db=new d(i),"win32"!==process.platform)try{r(i,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(Q)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const n=this.db.prepare("PRAGMA user_version").get();if(n.user_version>Y)throw new Error(`ThreadStore schema v${String(n.user_version)} 高于当前支持的 v${String(Y)}`);this.db.exec("BEGIN IMMEDIATE");try{if(this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),n.user_version<7){const e=this.db.prepare("PRAGMA table_info(threads)").all();for(const t of["origin_json","derived_from_json"])e.some(e=>e.name===t)||this.db.exec(`ALTER TABLE threads ADD COLUMN ${t} TEXT`)}if(ee(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS operation_app_outputs (\n thread_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n result_json TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n byte_length INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, operation_id),\n FOREIGN KEY (thread_id, operation_id) REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_turn_inputs (\n thread_id TEXT NOT NULL,\n input_id TEXT NOT NULL,\n transcript_sequence INTEGER NOT NULL,\n skill_checkpoint_json TEXT,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, input_id),\n UNIQUE (thread_id, transcript_sequence),\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS tool_execution_context_coverage (\n thread_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n representation TEXT NOT NULL\n CHECK (representation IN ('raw_transcript', 'recovery_evidence', 'legacy_assumed')),\n transcript_sequence INTEGER,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, execution_id),\n CHECK (\n (representation = 'legacy_assumed' AND transcript_sequence IS NULL)\n OR (representation <> 'legacy_assumed' AND transcript_sequence IS NOT NULL)\n ),\n FOREIGN KEY (thread_id, execution_id)\n REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE,\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);\n CREATE TABLE IF NOT EXISTS thread_runtime_event_state (\n thread_id TEXT PRIMARY KEY,\n event_log_id TEXT NOT NULL UNIQUE,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n retained_from_sequence INTEGER NOT NULL\n CHECK (retained_from_sequence >= 0 AND retained_from_sequence <= next_sequence),\n retained_predecessor_event_id TEXT,\n latest_event_id TEXT,\n retained_count INTEGER NOT NULL CHECK (retained_count >= 0),\n retained_bytes INTEGER NOT NULL CHECK (retained_bytes >= 0),\n CHECK (\n (next_sequence = 0\n AND retained_from_sequence = 0\n AND retained_predecessor_event_id IS NULL\n AND latest_event_id IS NULL)\n OR (next_sequence > 0 AND latest_event_id IS NOT NULL)\n ),\n CHECK (\n (retained_from_sequence = 0 AND retained_predecessor_event_id IS NULL)\n OR (retained_from_sequence > 0 AND retained_predecessor_event_id IS NOT NULL)\n ),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS runtime_events (\n thread_id TEXT NOT NULL,\n thread_sequence INTEGER NOT NULL CHECK (thread_sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n turn_id TEXT,\n event_timestamp TEXT NOT NULL,\n event_json TEXT NOT NULL,\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, thread_sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_runtime_events_thread_created\n ON runtime_events(thread_id, created_at, thread_sequence);"),this.db.prepare("PRAGMA table_info(runtime_events)").all().some(e=>"app_output_json"===e.name)||this.db.exec("ALTER TABLE runtime_events ADD COLUMN app_output_json TEXT"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),n.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),n.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),n.user_version<6&&this.db.prepare("INSERT OR IGNORE INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, NULL, created_at\n FROM tool_executions").run(Z),this.db.exec(t),n.user_version<9){const t=new e(this.db),n=this.db.prepare("SELECT thread_id, schema_version, checkpoint_json FROM compaction_checkpoints ORDER BY generation").all();for(const e of n){if("string"!=typeof e.thread_id||"string"!=typeof e.checkpoint_json)continue;let n;try{n=te({schema_version:Number(e.schema_version),checkpoint_json:e.checkpoint_json})}catch(t){if(t instanceof C)throw t;this.db.prepare("UPDATE thread_transcript_state SET completeness = 'legacy_snapshot' WHERE thread_id = ?").run(e.thread_id);continue}if(2!==n.version)continue;const r=this.getLatestCheckpoint(e.thread_id);t.append(e.thread_id,n.generation,2===n.version?n.semanticState.scopeId??n.semanticState.goal?.id??"initial":"legacy",n.transcript.toolExecutions.throughSequence,w(n.semanticState),r?.id!==n.id)}}this.initializeMissingRuntimeEventStatesInTransaction();const r=this.now().toISOString();this.enforceAllToolExecutionRetentionInTransaction(r),this.enforceAllRuntimeEventRetentionInTransaction(r),this.db.exec(`PRAGMA user_version = ${String(Y)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=g(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceAppOutputRetention(n),this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=E.parse(JSON.parse(t.message_json)),r=j(e);n.run(JSON.stringify(r),t.storage_rowid)}}}initializeMissingRuntimeEventStatesInTransaction(){const e=this.db.prepare("SELECT threads.id\n FROM threads\n LEFT JOIN thread_runtime_event_state\n ON thread_runtime_event_state.thread_id = threads.id\n WHERE thread_runtime_event_state.thread_id IS NULL").all(),t=this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)");for(const n of e)t.run(n.id,c())}enforceAllRuntimeEventRetentionInTransaction(e){const t=this.db.prepare("SELECT thread_id AS id FROM thread_runtime_event_state").all();for(const n of t)this.enforceRuntimeEventRetentionInTransaction(n.id,e)}createThread(e={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const t=e.id??c(),n=this.now().toISOString(),r=X.parse(e.origin??{kind:"interactive"}),s=void 0===e.derivedFrom?void 0:W.parse(e.derivedFrom);this.db.exec("SAVEPOINT create_thread");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at, origin_json, derived_from_json) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n,JSON.stringify(r),void 0===s?null:JSON.stringify(s)),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,x[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)").run(t,c()),this.db.exec("RELEASE create_thread"),t}catch(e){throw this.db.exec("ROLLBACK TO create_thread; RELEASE create_thread"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?ne(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(ne).filter(t=>void 0===e.origin||t.origin.kind===e.origin)}backfillScheduledOrigin(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=H.parse(t);return this.db.prepare("UPDATE threads SET origin_json = ? WHERE id = ? AND origin_json IS NULL").run(JSON.stringify(n),e).changes>0}readSnapshot(t){this.db.exec("BEGIN");try{const n=this.getThread(t);if(void 0===n)throw new Error(`Thread "${t}" 不存在`);const r=this.now().toISOString(),s=this.getMessages(t),o=e=>void 0!==this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e),i=o("transcript_messages")?this.db.prepare("SELECT sequence, provenance, created_at, message_json FROM transcript_messages WHERE thread_id = ? ORDER BY sequence").all(t).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):s.map((e,t)=>f.parse({sequence:t,message:e,provenance:"legacy_snapshot",createdAt:n.createdAt})),a=o("tool_executions")?this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? ORDER BY sequence").all(t).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence})):[],c=o("compaction_checkpoints")?this.db.prepare("SELECT schema_version, checkpoint_json FROM compaction_checkpoints WHERE thread_id = ? ORDER BY generation").all(t).map(te):[],d=o("tool_execution_context_coverage")?this.db.prepare("SELECT execution_id AS executionId, representation, transcript_sequence AS transcriptSequence, created_at AS createdAt FROM tool_execution_context_coverage WHERE thread_id = ?").all(t):a.map(e=>({executionId:e.id,representation:"legacy_assumed",transcriptSequence:null,createdAt:r})),E=o("thread_tool_execution_state")?this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(t):void 0,_=o("compaction_recovery")?new e(this.db).recovery(t):void 0,u={...o("compaction_semantic_items")?{semanticLedger:new e(this.db).versions(t)}:{},..._?{contextRecovery:_}:{},...o("compaction_recovery_notes")?{contextRecoveryNotes:new e(this.db).notes(t)}:{},thread:n,capturedAt:r,appOutputs:o("operation_app_outputs")?this.db.prepare("SELECT operation_id, result_json, expires_at FROM operation_app_outputs WHERE thread_id = ?").all(t).map(e=>({operationId:e.operation_id,result:this.readAppOutputRow(e.result_json,e.expires_at),expiresAt:e.expires_at})):[],messages:s,transcript:i,turnInputs:o("thread_turn_inputs")?this.listTurnInputs(t):[],toolExecutions:a,checkpoints:c,toolExecutionCoverage:d,nextToolExecutionSequence:E?.next_sequence??(a.at(-1)?.sequence??-1)+1,transcriptCompleteness:o("thread_transcript_state")?this.getTranscriptCompleteness(t):"legacy_snapshot"};return this.db.exec("COMMIT"),u}catch(e){throw this.db.exec("ROLLBACK"),e}}forkSnapshot(t,r={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const s=new Map(t.checkpoints.map(e=>[e.id,c()])),o=t.checkpoints.map(e=>{const t=M(re(e,s));return t.version===v?t:M({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),i=t.messages.map(e=>E.parse(re(e,s))),a=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const d=this.createThread({...void 0===r.id?{}:{id:r.id},...void 0===r.title?void 0===t.thread.title?{}:{title:t.thread.title}:{title:r.title},...void 0===r.model?{}:{model:r.model},origin:{kind:"interactive"},derivedFrom:{threadId:t.thread.id,origin:t.thread.origin,capturedAt:t.capturedAt}});this.replaceMessagesInTransaction(d,k(i).messages,a);const E=this.db.prepare("INSERT INTO transcript_messages (thread_id, sequence, role, message_json, provenance, created_at) VALUES (?, ?, ?, ?, ?, ?)");for(const e of t.transcript){const t=f.parse(e);E.run(d,t.sequence,t.message.role,JSON.stringify(t.message),t.provenance,t.createdAt)}const _=this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, skill_checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?)");for(const e of t.turnInputs??[]){const t=B.parse(e);_.run(d,c(),t.transcriptSequence,void 0===t.skillCheckpoint?null:JSON.stringify(t.skillCheckpoint),t.createdAt)}this.db.prepare("UPDATE thread_transcript_state SET completeness = ? WHERE thread_id = ?").run(t.transcriptCompleteness,d);const u=this.db.prepare("INSERT INTO tool_executions (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");for(const{sequence:e,...n}of t.toolExecutions){const t=g(n);u.run(d,e,t.id,t.toolCallId,t.agentName,t.toolName,JSON.stringify(t),t.createdAt)}for(const e of t.appOutputs??[])this.insertAppOutput(d,e.operationId,e.result,e.expiresAt);this.enforceAppOutputRetention(d),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(t.nextToolExecutionSequence,d);const p=this.db.prepare("INSERT INTO tool_execution_context_coverage (thread_id, execution_id, representation, transcript_sequence, created_at) VALUES (?, ?, ?, ?, ?)");for(const e of t.toolExecutionCoverage)p.run(d,e.executionId,e.representation,e.transcriptSequence,e.createdAt);const h=this.db.prepare("INSERT INTO compaction_checkpoints (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence, tool_from_sequence, tool_through_sequence, checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const e of o)h.run(e.id,d,e.generation,e.version,e.transcript.messages.fromSequenceExclusive,e.transcript.messages.throughSequence,e.transcript.toolExecutions.fromSequenceExclusive,e.transcript.toolExecutions.throughSequence,JSON.stringify(e),e.createdAt);const T=new e(this.db);if(t.semanticLedger)T.restore(d,t.semanticLedger);else for(const e of o)2===e.version&&T.append(d,e.generation,2===e.version?e.semanticState.scopeId??e.semanticState.goal?.id??"initial":"legacy",e.transcript.toolExecutions.throughSequence,w(e.semanticState),e.id!==o.at(-1)?.id);if(t.contextRecovery){const e=s.get(t.contextRecovery.checkpointId);e&&this.db.prepare("INSERT INTO compaction_recovery\n (thread_id,checkpoint_id,required,unextracted,memo) VALUES (?,?,1,?,?)").run(d,e,Number(t.contextRecovery.unextracted),t.contextRecovery.memo)}for(const e of t.contextRecoveryNotes??[]){const t=n.parse(e),r=s.get(t.checkpointId);r&&this.db.prepare("INSERT INTO compaction_recovery_notes (thread_id,checkpoint_id,notes) VALUES (?,?,?)").run(d,r,t.notes)}return this.db.exec("COMMIT"),d}catch(e){throw this.db.exec("ROLLBACK"),e}}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}countTranscriptMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM transcript_messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}updateModel(e,t){this.db.prepare("UPDATE threads SET model = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){const t=N.parse(e.event),n="tool.completed"===t.type?(({appOutput:e,...t})=>t)(t):t,r=JSON.stringify(n),s=ae(e.timestamp),o=Buffer.byteLength(JSON.stringify(t),"utf8")+Buffer.byteLength(s,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(o>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e.threadId);if(void 0===n)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=se(),a=n.next_sequence;return this.db.prepare("INSERT INTO runtime_events\n (thread_id, thread_sequence, event_id, turn_id, event_timestamp,\n event_json, size_bytes, created_at, app_output_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,s,r,o,s,"tool.completed"===t.type?JSON.stringify(t.appOutput):null),this.db.prepare("UPDATE thread_runtime_event_state\n SET next_sequence = ?, latest_event_id = ?,\n retained_count = retained_count + 1,\n retained_bytes = retained_bytes + ?\n WHERE thread_id = ?").run(a+1,i,o,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,s),this.db.exec("COMMIT"),{eventId:i,cursor:oe(n.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:s,event:t}}catch(e){throw this.db.exec("ROLLBACK"),e}}getRuntimeEventCursor(e){const t=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);return 0===t.next_sequence||null===t.latest_event_id?null:oe(t.event_log_id,t.next_sequence-1,R.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:ie(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);this.db.exec("BEGIN IMMEDIATE");try{this.enforceRuntimeEventRetentionInTransaction(e,this.now().toISOString());const r=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===r)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);if(null===t&&r.retained_from_sequence>0)throw new RuntimeEventCursorExpiredError(t);if(null!==t&&void 0!==n&&this.validateRuntimeEventCursor(r,t,n,e),0===r.next_sequence||null===r.latest_event_id)return this.db.exec("COMMIT"),{events:[],throughCursor:null,replayedCount:0};const s=r.next_sequence-1,o=n?.threadSequence??-1,i=this.db.prepare("SELECT *\n FROM runtime_events\n WHERE thread_id = ?\n AND thread_sequence > ?\n AND thread_sequence <= ?\n ORDER BY thread_sequence ASC").all(e,o,s).map(t=>ce(r.event_log_id,e,t)),a=oe(r.event_log_id,s,R.parse(r.latest_event_id));return this.db.exec("COMMIT"),{events:i,throughCursor:a,replayedCount:i.length}}catch(e){throw e instanceof RuntimeEventCursorExpiredError||e instanceof RuntimeEventCursorGapError?this.db.exec("COMMIT"):this.db.exec("ROLLBACK"),e}}countRuntimeEvents(e){return this.db.prepare("SELECT COUNT(*) AS count FROM runtime_events WHERE thread_id = ?").get(e).count}validateRuntimeEventCursor(e,t,n,r){if(n.eventLogId!==e.event_log_id||n.threadSequence>=e.next_sequence)throw new RuntimeEventCursorGapError(t);const s=e.retained_from_sequence-1;if(n.threadSequence<s)throw new RuntimeEventCursorExpiredError(t);if(n.threadSequence===s){if(n.eventId!==e.retained_predecessor_event_id)throw new RuntimeEventCursorGapError(t);return}const o=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(r,n.threadSequence);if(void 0===o||o.event_id!==n.eventId)throw new RuntimeEventCursorGapError(t)}enforceRuntimeEventRetentionInTransaction(e,t){const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===n||0===n.retained_count)return;let r=n.retained_from_sequence;if(n.retained_count>RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread){const t=this.db.prepare("SELECT thread_sequence AS keep_from\n FROM runtime_events\n WHERE thread_id = ?\n ORDER BY thread_sequence DESC\n LIMIT 1 OFFSET ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread-1);r=Math.max(r,t?.keep_from??n.next_sequence)}if(n.retained_bytes>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread){const t=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM (\n SELECT thread_sequence,\n SUM(size_bytes) OVER (\n ORDER BY thread_sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM runtime_events\n WHERE thread_id = ?\n )\n WHERE retained_bytes <= ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread);r=Math.max(r,t.keep_from??n.next_sequence)}const s=new Date(Date.parse(t)-RUNTIME_EVENT_RETENTION_POLICY.maxAgeMs).toISOString(),o=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM runtime_events\n WHERE thread_id = ? AND created_at >= ?").get(e,s);if(r=Math.max(r,o.keep_from??n.next_sequence),r<=n.retained_from_sequence)return;const i=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(e,r-1);if(void 0===i)throw new Error("Runtime event retention boundary is not a continuous retained prefix");const a=this.db.prepare("SELECT COUNT(*) AS removed_count, SUM(size_bytes) AS removed_bytes\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence < ?").get(e,r);this.db.prepare("DELETE FROM runtime_events WHERE thread_id = ? AND thread_sequence < ?").run(e,r),this.db.prepare("UPDATE thread_runtime_event_state\n SET retained_from_sequence = ?, retained_predecessor_event_id = ?,\n retained_count = retained_count - ?,\n retained_bytes = retained_bytes - ?\n WHERE thread_id = ?").run(r,i.event_id,a.removed_count,a.removed_bytes??0,e)}acceptTurnInput(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=P.parse(t),s=j(E.parse(n));if("user"!==s.role)throw new Error("Accepted turn input must be a user message");this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=this.getTurnInput(e,r);if(void 0!==t){const n=this.db.prepare("SELECT message_json FROM transcript_messages WHERE thread_id = ? AND sequence = ?").get(e,t.transcriptSequence);if(n?.message_json!==JSON.stringify(s))throw new Error("Turn input identity conflict");return this.db.exec("COMMIT"),t}const n=this.now().toISOString();this.appendMessagesInTransaction(e,[s],n,void 0);const o=this.db.prepare("SELECT MAX(sequence) AS sequence FROM transcript_messages WHERE thread_id = ?").get(e),i=B.parse({inputId:r,transcriptSequence:o?.sequence,createdAt:n});return this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, created_at) VALUES (?, ?, ?, ?)").run(e,r,i.transcriptSequence,n),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}getTurnInput(e,t){const n=this.db.prepare("SELECT input_id, transcript_sequence, skill_checkpoint_json, created_at FROM thread_turn_inputs WHERE thread_id = ? AND input_id = ?").get(e,P.parse(t));return void 0===n?void 0:B.parse({inputId:n.input_id,transcriptSequence:n.transcript_sequence,createdAt:n.created_at,..."string"==typeof n.skill_checkpoint_json?{skillCheckpoint:JSON.parse(n.skill_checkpoint_json)}:{}})}listTurnInputs(e){return this.db.prepare("SELECT input_id FROM thread_turn_inputs WHERE thread_id = ? ORDER BY transcript_sequence").all(e).map(t=>{const n=this.getTurnInput(e,String(t.input_id));if(void 0===n)throw new Error("Turn input receipt disappeared");return n})}getTurnInputSkillCheckpoint(e,t){const n=this.db.prepare("SELECT skill_checkpoint_json FROM thread_turn_inputs WHERE thread_id = ? AND transcript_sequence = ?").get(e,t);return"string"==typeof n?.skill_checkpoint_json?G.parse(JSON.parse(n.skill_checkpoint_json)):void 0}recordTurnSkillCheckpoint(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=G.parse(n),s=JSON.stringify(r);this.db.exec("BEGIN IMMEDIATE");try{const n=this.getTurnInput(e,t);if(void 0===n)throw new Error("Turn input receipt not found");if(void 0!==n.skillCheckpoint&&JSON.stringify(n.skillCheckpoint)!==s)throw new Error("Turn input Skill snapshot conflict");this.db.prepare("UPDATE thread_turn_inputs SET skill_checkpoint_json = ? WHERE thread_id = ? AND input_id = ?").run(s,e,t),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessages(e,t,n={}){const r=n.toolExecutionCoverage;if(0===t.length){if(void 0!==r&&r.executionIds.length>0)throw new Error("Tool execution coverage 必须与至少一条 transcript message 原子写入");return}if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);if(void 0!==r&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.rawTranscript&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence)throw new Error("Tool execution coverage representation 无效");const s=r?.executionIds??[],o=new Set;for(const e of s){if("string"!=typeof e||0===e.length)throw new Error("Tool execution coverage execution id 无效");if(o.has(e))throw new Error(`Tool execution coverage execution id 重复: ${e}`);o.add(e)}const i=t.map(e=>j(E.parse(e))),a=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.appendMessagesInTransaction(e,i,a,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}recoverUncoveredToolExecutions(e,t){this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=this.uncoveredToolExecutionRecords(e,{afterSequence:-1,throughSequence:Number.MAX_SAFE_INTEGER,toolCallId:void 0,limit:Number.MAX_SAFE_INTEGER});if(0===n.length)return void this.db.exec("COMMIT");const r=j(E.parse(t(n))),s={executionIds:n.map(e=>e.id),representation:TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence};return this.appendMessagesInTransaction(e,[r],(new Date).toISOString(),s),this.db.exec("COMMIT"),r}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessagesInTransaction(e,t,n,r){const s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),o=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)"),i=this.db.prepare("INSERT INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, ?, ?\n FROM tool_executions\n WHERE thread_id = ?\n AND id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE thread_id = ? AND execution_id = ?\n )"),a=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),c=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let d=a.maxIdx+1,E=c.maxSequence+1;for(const r of t){const t=JSON.stringify(r);s.run(e,d,r.role,t,n),o.run(e,E,r.role,t,A[0],n),d+=1,E+=1}const _=E-1;if(void 0!==r){for(const t of r.executionIds){const s=i.run(r.representation,_,n,e,t,e,t);if(1!==Number(s.changes))throw new Error(`Tool execution "${t}" 不属于 Thread "${e}" 或已被 transcript 覆盖`)}this.enforceToolExecutionRetentionInTransaction(e,n)}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>E.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>E.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const o=j(t);r.run(e,s,o.role,JSON.stringify(o),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=O(t),r=JSON.stringify(n),s=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const o=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===o)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const i=o.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,i,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(i+1,e),void 0!==t.appOutput&&this.insertAppOutput(e,t.id,t.appOutput,new Date(Date.parse(s)+_.maxAgeMs).toISOString()),this.enforceAppOutputRetention(e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}readAppOutputRow(e,t){const n=u.parse(JSON.parse(e));return"available"===n.status&&Date.parse(t)<=this.now().getTime()?{status:"expired"}:n}insertAppOutput(e,t,n,r){const s="available"===n.status&&Date.parse(r)<=this.now().getTime()?{status:"expired"}:I(n),o=JSON.stringify(s);this.db.prepare("INSERT INTO operation_app_outputs\n (thread_id, operation_id, result_json, descriptor_json, byte_length, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e,t,o,JSON.stringify(h(s)),"available"===s.status?Buffer.byteLength(o,"utf8"):0,r)}enforceAppOutputRetention(e){const t=JSON.stringify({status:"expired"});this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND byte_length > 0 AND expires_at <= ?").run(t,t,e,this.now().toISOString()),this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND operation_id IN (\n SELECT operation_id FROM (\n SELECT o.operation_id,\n ROW_NUMBER() OVER (ORDER BY e.sequence DESC) AS rank,\n SUM(o.byte_length) OVER (ORDER BY e.sequence DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS bytes\n FROM operation_app_outputs o JOIN tool_executions e ON e.thread_id = o.thread_id AND e.id = o.operation_id\n WHERE o.thread_id = ? AND o.byte_length > 0\n ) WHERE rank > ? OR bytes > ?\n )").run(t,t,e,e,_.threadRecords,_.threadBytes)}getAppOutputDescriptor(e,t){if(void 0===this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'operation_app_outputs'").get())return void 0===this.db.prepare("SELECT 1 FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t)?void 0:{status:"not_provided"};const n=this.db.prepare("SELECT o.descriptor_json, o.expires_at FROM tool_executions e\n LEFT JOIN operation_app_outputs o ON o.thread_id = e.thread_id AND o.operation_id = e.id\n WHERE e.thread_id = ? AND e.id = ?").get(e,t);if(void 0===n)return;if(null===n.descriptor_json)return{status:"not_provided"};const r=p.parse(JSON.parse(n.descriptor_json));return"available"===r.status&&null!==n.expires_at&&Date.parse(n.expires_at)<=this.now().getTime()?{status:"expired"}:r}getAppOutput(e,t){const n=this.getAppOutputDescriptor(e,t);if(void 0===n||"available"!==n.status)return n;const r=this.db.prepare("SELECT result_json FROM operation_app_outputs WHERE thread_id = ? AND operation_id = ?").get(e,t);return void 0===r?{status:"not_provided"}:u.parse(JSON.parse(r.result_json))}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )").run(e,n);r=Number(s.changes)>0;const o=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(o.length>0){const t=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND sequence = ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )");for(const n of o){const s=t.run(e,n.sequence);r=Number(s.changes)>0||r}}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence}))}listUncoveredToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);return r<=n?[]:this.uncoveredToolExecutionRecords(e,{afterSequence:n,throughSequence:r,toolCallId:t.toolCallId,limit:s})}resolveToolExecutionListRange(e){const t=e.afterSequence??-1,n=e.throughSequence??Number.MAX_SAFE_INTEGER,r=e.limit??K;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>J)throw new Error(`limit 必须是 1-${String(J)} 之间的整数`);if(!Number.isInteger(n)||n<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");return{afterSequence:t,throughSequence:n,limit:r}}uncoveredToolExecutionRecords(e,t){return this.db.prepare("SELECT tool_executions.sequence, tool_executions.record_json\n FROM tool_executions\n LEFT JOIN tool_execution_context_coverage\n ON tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n WHERE tool_executions.thread_id = ?\n AND tool_executions.sequence > ?\n AND tool_executions.sequence <= ?\n AND (? IS NULL OR tool_executions.tool_call_id = ?)\n AND tool_execution_context_coverage.execution_id IS NULL\n ORDER BY tool_executions.sequence ASC\n LIMIT ?").all(e,t.afterSequence,t.throughSequence,t.toolCallId??null,t.toolCallId??null,t.limit).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??K;this.validateRecentPage(n,r,J);const s=this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...g(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===x[0]?x[0]:x[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??$;if(this.validateTranscriptRange(n,r,s,V),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}listRecentTranscriptMessages(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??$;this.validateRecentPage(n,r,V);const s=this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,te(n))}catch(e){if(e instanceof C)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=te(n);if(t.version===v||"valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof C)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,te(n))}catch(e){if(e instanceof C)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:k(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>E.parse(e)),r=t.expectedActiveMessages.map(e=>E.parse(e)),s=k(n);if(s.status!==F.valid){const e=[...new Set([...s.removedToolCallIds,...s.removedToolResultIds])].sort();throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const o=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=k(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const i=this.getLatestCheckpoint(e);if(i?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const a=this.legacySummaryFragmentsToArchive(e,s,n),c=this.maxTranscriptMessageSequence(e),d=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration FROM compaction_checkpoints WHERE thread_id = ?").get(e),E={messages:{fromSequenceExclusive:i?.transcript.messages.throughSequence??-1,throughSequence:c+a.length},toolExecutions:{fromSequenceExclusive:i?.transcript.toolExecutions.throughSequence??-1,throughSequence:this.maxToolExecutionSequence(e)},completeness:this.getTranscriptCompleteness(e)},_=new Set(r.flatMap(e=>Array.isArray(e.content)?e.content.flatMap(e=>"tool-call"===e.type?[e.toolCallId]:[]):[])),u=this.listRecentToolExecutions(e,{limit:J}).entries.filter(e=>_.has(e.toolCallId)),p=b({summary:t.summary,generation:d.maxGeneration+1,transcript:E,...void 0===i?{}:{previousCheckpointId:i.id},toolState:q(r,u,32,E.completeness),createdAt:o});t.validateCheckpoint?.(p);for(const[t,n]of a.entries())this.db.prepare("INSERT INTO transcript_messages\n (thread_id,sequence,role,message_json,provenance,created_at) VALUES (?,?,?,?,?,?)").run(e,c+t+1,n.role,JSON.stringify(n),A[1],o);return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence, checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p.id,e,p.generation,p.version,E.messages.fromSequenceExclusive,E.messages.throughSequence,E.toolExecutions.fromSequenceExclusive,E.toolExecutions.throughSequence,JSON.stringify(p),p.createdAt),this.replaceMessagesInTransaction(e,n,o),this.db.exec("COMMIT"),p}catch(e){throw this.db.exec("ROLLBACK"),e}}legacySummaryFragmentsToArchive(e,t,n){const r=e=>"string"==typeof e.content?U(e.content):void 0,s=new Set(n.map(r)),o=new Map,i=this.db.prepare("SELECT 1 FROM transcript_messages WHERE thread_id=? AND json_extract(message_json, '$.content') IN (?, ?) LIMIT 1");for(const n of t){const t=r(n);if(void 0===t||0===t.length||s.has(t))continue;const a=L(t),c=D(a);i.get(e,String(n.content),String(c.content))||o.set(a,c)}return[...o.values()]}getContextRecovery(t){if(this.getLatestCheckpoint(t)?.version===v)return;const n=new e(this.db),r=n.recovery(t);if(!r)return;const s=this.getCheckpoint(t,r.checkpointId);if(!s)throw new Error("Recovery checkpoint is missing");const o=2===s.version?s.semanticState.goal?.id:void 0;return{...r,requiredItems:n.rows(t,s.generation).flatMap(e=>"active"===e.status&&("constraint"===e.category||"goal"===e.category&&e.item_id===o)?[{itemId:e.item_id,version:e.generation,category:e.category}]:[])}}getSemanticLedgerRows(t){return new e(this.db).rows(t,this.getLatestCheckpoint(t)?.generation??0)}readCheckpointTranscript(e,t){if(!TRANSCRIPT_ENTRY_KINDS.includes(t.kind))throw new Error("kind 必须是 message、tool_execution 或 legacy_context");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??$,s="legacy_context"===t.kind?{fromSequenceExclusive:-1,throughSequence:Number.MAX_SAFE_INTEGER}:"message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,o=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(o,s.throughSequence,r,z);const i="legacy_context"===t.kind?this.listLegacyContextEntries(e,n,o,r+1):"message"===t.kind?this.listTranscriptMessages(e,{afterSequence:o,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:o,throughSequence:s.throughSequence,limit:Math.min(r+1,J)}).map(e=>({kind:"tool_execution",...e})),a=i.length>r,c=a?i.slice(0,r):i,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...a&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}listLegacyContextEntries(t,n,r,s){if(n.version===v)return[];const o=new e(this.db),i=[];if(r<0){const e=o.recovery(t),r=this.db.prepare("SELECT notes FROM compaction_recovery_notes WHERE thread_id=? AND checkpoint_id=?").get(t,n.id);i.push({kind:"legacy_context",sequence:0,value:{checkpoint:n,...e?.checkpointId===n.id?{memo:e.memo}:{},..."string"==typeof r?.notes?{notes:r.notes}:{}}})}for(const e of o.page(t,n.generation,r,s-i.length))i.push({kind:"legacy_context",sequence:e.sequence,value:{ledgerItem:y.parse({category:e.category,item:JSON.parse(e.item_json)}),generation:e.generation,status:e.status,scope:e.scope,resolution:e.resolution_json??null}});return i}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=x[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:x[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}validateRecentPage(e,t,n){if(!Number.isSafeInteger(e)||e<0||e>Number.MAX_SAFE_INTEGER)throw new Error("beforeSequence 必须是非负安全整数");if(!Number.isInteger(t)||t<1||t>n)throw new Error(`limit 必须是 1-${String(n)} 之间的整数`)}close(){this.db.close()}}
@@ -0,0 +1,161 @@
1
+ import { z } from "zod";
2
+ import type { ModelMessage } from "ai";
3
+ /** A receipt is an internal persistence identity, not permission to replay a turn. */
4
+ export declare const turnInputIdSchema: z.ZodString;
5
+ export declare const turnInputSkillCheckpointSchema: z.ZodReadonly<z.ZodObject<{
6
+ version: z.ZodLiteral<1>;
7
+ kind: z.ZodLiteral<"explicit-skill">;
8
+ } & {
9
+ snapshot: z.ZodReadonly<z.ZodObject<Pick<{
10
+ userPrompt: z.ZodString;
11
+ skillNames: z.ZodArray<z.ZodString, "many">;
12
+ skills: z.ZodOptional<z.ZodArray<z.ZodReadonly<z.ZodObject<{
13
+ name: z.ZodString;
14
+ source: z.ZodString;
15
+ contentSha256: z.ZodString;
16
+ }, "strip", z.ZodTypeAny, {
17
+ name: string;
18
+ source: string;
19
+ contentSha256: string;
20
+ }, {
21
+ name: string;
22
+ source: string;
23
+ contentSha256: string;
24
+ }>>, "many">>;
25
+ }, "skills" | "skillNames">, "strip", z.ZodTypeAny, {
26
+ skillNames: string[];
27
+ skills?: Readonly<{
28
+ name: string;
29
+ source: string;
30
+ contentSha256: string;
31
+ }>[] | undefined;
32
+ }, {
33
+ skillNames: string[];
34
+ skills?: Readonly<{
35
+ name: string;
36
+ source: string;
37
+ contentSha256: string;
38
+ }>[] | undefined;
39
+ }>>;
40
+ }, "strip", z.ZodTypeAny, {
41
+ kind: "explicit-skill";
42
+ version: 1;
43
+ snapshot: Readonly<{
44
+ skillNames: string[];
45
+ skills?: Readonly<{
46
+ name: string;
47
+ source: string;
48
+ contentSha256: string;
49
+ }>[] | undefined;
50
+ }>;
51
+ }, {
52
+ kind: "explicit-skill";
53
+ version: 1;
54
+ snapshot: Readonly<{
55
+ skillNames: string[];
56
+ skills?: Readonly<{
57
+ name: string;
58
+ source: string;
59
+ contentSha256: string;
60
+ }>[] | undefined;
61
+ }>;
62
+ }>>;
63
+ export type TurnInputSkillCheckpoint = z.infer<typeof turnInputSkillCheckpointSchema>;
64
+ export declare const acceptedTurnInputSchema: z.ZodReadonly<z.ZodObject<{
65
+ inputId: z.ZodString;
66
+ transcriptSequence: z.ZodNumber;
67
+ createdAt: z.ZodString;
68
+ skillCheckpoint: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
69
+ version: z.ZodLiteral<1>;
70
+ kind: z.ZodLiteral<"explicit-skill">;
71
+ } & {
72
+ snapshot: z.ZodReadonly<z.ZodObject<Pick<{
73
+ userPrompt: z.ZodString;
74
+ skillNames: z.ZodArray<z.ZodString, "many">;
75
+ skills: z.ZodOptional<z.ZodArray<z.ZodReadonly<z.ZodObject<{
76
+ name: z.ZodString;
77
+ source: z.ZodString;
78
+ contentSha256: z.ZodString;
79
+ }, "strip", z.ZodTypeAny, {
80
+ name: string;
81
+ source: string;
82
+ contentSha256: string;
83
+ }, {
84
+ name: string;
85
+ source: string;
86
+ contentSha256: string;
87
+ }>>, "many">>;
88
+ }, "skills" | "skillNames">, "strip", z.ZodTypeAny, {
89
+ skillNames: string[];
90
+ skills?: Readonly<{
91
+ name: string;
92
+ source: string;
93
+ contentSha256: string;
94
+ }>[] | undefined;
95
+ }, {
96
+ skillNames: string[];
97
+ skills?: Readonly<{
98
+ name: string;
99
+ source: string;
100
+ contentSha256: string;
101
+ }>[] | undefined;
102
+ }>>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ kind: "explicit-skill";
105
+ version: 1;
106
+ snapshot: Readonly<{
107
+ skillNames: string[];
108
+ skills?: Readonly<{
109
+ name: string;
110
+ source: string;
111
+ contentSha256: string;
112
+ }>[] | undefined;
113
+ }>;
114
+ }, {
115
+ kind: "explicit-skill";
116
+ version: 1;
117
+ snapshot: Readonly<{
118
+ skillNames: string[];
119
+ skills?: Readonly<{
120
+ name: string;
121
+ source: string;
122
+ contentSha256: string;
123
+ }>[] | undefined;
124
+ }>;
125
+ }>>>;
126
+ }, "strip", z.ZodTypeAny, {
127
+ createdAt: string;
128
+ inputId: string;
129
+ transcriptSequence: number;
130
+ skillCheckpoint?: Readonly<{
131
+ kind: "explicit-skill";
132
+ version: 1;
133
+ snapshot: Readonly<{
134
+ skillNames: string[];
135
+ skills?: Readonly<{
136
+ name: string;
137
+ source: string;
138
+ contentSha256: string;
139
+ }>[] | undefined;
140
+ }>;
141
+ }> | undefined;
142
+ }, {
143
+ createdAt: string;
144
+ inputId: string;
145
+ transcriptSequence: number;
146
+ skillCheckpoint?: Readonly<{
147
+ kind: "explicit-skill";
148
+ version: 1;
149
+ snapshot: Readonly<{
150
+ skillNames: string[];
151
+ skills?: Readonly<{
152
+ name: string;
153
+ source: string;
154
+ contentSha256: string;
155
+ }>[] | undefined;
156
+ }>;
157
+ }> | undefined;
158
+ }>>;
159
+ export type AcceptedTurnInput = z.infer<typeof acceptedTurnInputSchema>;
160
+ /** A transient evidence projection only. Never rewrite raw transcript or load Skill bodies. */
161
+ export declare function projectTurnInputSkillCheckpoint(message: ModelMessage, checkpoint: TurnInputSkillCheckpoint | undefined): ModelMessage;
@@ -0,0 +1 @@
1
+ import{z as t}from"zod";import{SKILL_INVOCATION_PARSE_KINDS as n,parseSkillInvocationResult as e}from"@roll-agent/core/skills/invocation";import{explicitSkillCheckpointV1Schema as o}from"../engine/explicit-skill-context.js";export const turnInputIdSchema=t.string().min(1).max(128);export const turnInputSkillCheckpointSchema=o.unwrap().extend({snapshot:o.unwrap().shape.snapshot.unwrap().pick({skillNames:!0,skills:!0}).readonly()}).readonly();export const acceptedTurnInputSchema=t.object({inputId:turnInputIdSchema,transcriptSequence:t.number().int().nonnegative(),createdAt:t.string().datetime(),skillCheckpoint:turnInputSkillCheckpointSchema.optional()}).readonly();export function projectTurnInputSkillCheckpoint(t,o){if(void 0===o||"user"!==t.role)return t;const r="string"==typeof t.content?t.content:t.content.filter(t=>"text"===t.type).map(t=>t.text).join("\n"),i=e(r,o.snapshot.skillNames.map(t=>({name:t,description:"",source:"project"}))),p=i.kind===n.valid?i.invocation.prompt:r;return{...t,providerOptions:{...t.providerOptions,rollHarness:{...t.providerOptions?.rollHarness,explicitSkillCheckpoint:{...o,snapshot:{...o.snapshot,userPrompt:p}}}}}}
@@ -36,6 +36,7 @@ export interface ApprovalRequest {
36
36
  readonly explanation?: string;
37
37
  readonly sessionGrantLabel?: string;
38
38
  readonly diff?: FileChangeDiff;
39
+ readonly fullDiff?: string;
39
40
  }
40
41
  export interface ToolBridgeContext {
41
42
  readonly policy?: ToolPolicy;
@@ -50,6 +51,7 @@ interface ApprovalDisplayOptions {
50
51
  readonly memoryKey?: string;
51
52
  readonly sessionGrantLabel?: string;
52
53
  readonly diff?: FileChangeDiff;
54
+ readonly fullDiff?: string;
53
55
  }
54
56
  export interface BuiltToolset {
55
57
  readonly tools: ToolSet;
@@ -1 +1 @@
1
- import{lstatSync as o,readdirSync as t,readlinkSync as e,realpathSync as n,statSync as r}from"node:fs";import{inlineAcyclicLocalJsonSchemaReferences as i}from"@roll-agent/core/tool-runtime/json-schema-refs";import{basename as s,dirname as a,isAbsolute as c,resolve as u}from"node:path";import{jsonSchema as l,tool as f}from"ai";import{preflightToolCall as p}from"@roll-agent/core/tool-runtime/preflight";import{readExecutionTimeoutMs as m,ROLL_EXECUTION_TIMEOUT_META_KEY as d}from"@roll-agent/core/tool-runtime/execution-timeout";import{ToolRegistry as v}from"./naming.js";import{TOOL_OUTCOME_KINDS as g,failedToolResult as y,toolResultToModelOutput as h}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as S,TOOL_RESOURCE_HINT_KINDS as b,executeCoordinatedTool as $}from"./tool-execution-coordinator.js";import{executeWithToolApproval as w}from"./tool-approval-continuation.js";import{currentObservationModelOutput as O}from"../engine/observation-projection.js";export{readExecutionTimeoutMs,ROLL_EXECUTION_TIMEOUT_META_KEY}from"@roll-agent/core/tool-runtime/execution-timeout";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function T(o,t){const e=new Map;for(const n of[...o??[],...t])e.set(`${n.path}\0${n.ref}`,n);return[...e.values()]}function C(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function x(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}function N(o){return!0===o?.readOnlyHint&&!0!==o.destructiveHint?S.read:S.write}function E(o){return"string"==typeof o||"number"==typeof o?[o]:Array.isArray(o)&&0!==o.length&&o.every(o=>"string"==typeof o||"number"==typeof o)?o:void 0}const j=256,k=new Map;function A(o,t){for(let e=0;e<o.length;e+=1){const n=o.charCodeAt(e),r=n>=65&&n<=90?String.fromCharCode(n+32):n>=97&&n<=122?String.fromCharCode(n-32):void 0;if(void 0===r)continue;const i=`${o.slice(0,e)}${r}${o.slice(e+1)}`;if(!t.has(i))return i}}function I(o){return"object"==typeof o&&null!==o&&"code"in o?o.code:void 0}function M(t,e,n){const r=A(e,n);if(void 0!==r)try{const n=o(u(t,e)),i=o(u(t,r));return n.dev===i.dev&&n.ino===i.ino}catch(o){const t=I(o);return"ENOENT"!==t&&"ENOTDIR"!==t&&void 0}}function L(o,t){if(k.size>=j){const o=k.keys().next().value;void 0!==o&&k.delete(o)}return k.set(o,t),t}function R(e){const n=k.get(e);if(void 0!==n)return n;let r;try{r=o(e).dev}catch{return L(e,!0)}let i=e;for(;;){const n=k.get(i);if(void 0!==n)return L(e,n);try{const o=t(i),n=new Set(o);for(const t of o){const o=M(i,t,n);if(void 0!==o)return L(i,o),L(e,o)}}catch{}const s=a(i);if(s===i)break;try{if(o(s).dev!==r)break}catch{break}i=s}return L(e,!0)}function _(o){let t=o.normalize("NFC");for(let o=0;o<8;o+=1){const o=t.toUpperCase().toLowerCase().normalize("NFC");if(o===t)return t;t=o}return t}function K(o,t){try{const e=r(o,{bigint:!0}),n=t.map(o=>_(o));return`file-unresolved-alias:${e.dev.toString()}:${e.ino.toString()}:${JSON.stringify(n)}`}catch{return}}function z(t,r=new Set){let i=t;const c=[];for(;;)try{const o=n.native(i),t=c.reverse().map(o=>o.normalize("NFC"));if(0===t.length)return{path:o};const e=R(o),r=u(o,...e?t.map(o=>o.toLowerCase()):t),s=e?K(o,t):void 0;return{path:r,...void 0===s?{}:{unresolvedAliasKey:s}}}catch{try{if(o(i).isSymbolicLink()&&!r.has(i)){const o=new Set(r);o.add(i);const t=e(i);return z(u(a(i),t,...[...c].reverse()),o)}}catch{}const n=a(i);if(n===i)return{path:t};c.push(s(i)),i=n}}function H(o){try{const t=r(o,{bigint:!0});return`file-inode:${t.dev.toString()}:${t.ino.toString()}`}catch{return}}function G(o,t,e){const n=String(t).trim();if(0!==n.length)switch(o.kind){case b.file:{if(!c(n)&&void 0===e)return;const o=z(u(e??"/",n)),t=H(o.path);return[`file:${o.path}`,...void 0===o.unresolvedAliasKey?[]:[o.unresolvedAliasKey],...void 0===t?[]:[t]]}case b.browserSession:return[`browser-session:${n}`];case b.conversation:return[`conversation:${n}`];case b.custom:{const t=o.namespace?.trim();return t?[`${t}:${n}`]:void 0}}}function U(o,t,e,n,r){const i=N(n),s=C(e),a=[{key:`agent:${o}`,mode:i}],c=[];for(const o of r??[]){if(!Object.hasOwn(s,o.field))continue;const e=E(s[o.field]);if(void 0===e)return a;for(const n of e){const e=G(o,n,t);if(void 0===e||0===e.length)return a;c.push(...e.map(t=>({key:t,mode:o.mode??i})))}}return 0===c.length?a:[{key:`agent:${o}`,mode:S.read},...c]}export async function gateToolCall(o,t,e,n,r,i){if(!o.policy)return;const s=o.policy.check({agentName:t,toolName:e,input:n,...r?{annotations:r}:{}});if("deny"===s.action)return y(g.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&o.approvalMemory?.isGranted(r))return;const a=await o.requestApproval({agentName:t,toolName:e,input:n,reason:!1===i?.includePolicyReason?void 0:s.reason,...void 0!==i?.explanation?{explanation:i.explanation}:{},...void 0!==i?.diff?{diff:i.diff}:{},...void 0!==r&&void 0!==i?.sessionGrantLabel?{sessionGrantLabel:i.sessionGrantLabel}:{}});if(!a.approved)return y(g.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&o.approvalMemory?.grant(r)}}export function buildAgentToolset(o,t,e=new v){const n={},r={},s=new Map;for(const a of o){const{client:o,agentName:c,agentSource:u,transport:v,runtimeOwnership:S,resourceBaseDir:b}=a;for(const{tool:N,annotations:E,resourceHints:j,observationRetention:k}of a.tools){const a=i(N.inputSchema),A=T(N.schemaIssues,a.unresolved),I={...N,inputSchema:a.schema,...A.length>0?{schemaIssues:A}:{}},M=e.register(c,I.name,{...u?{agentSource:u}:{},...v?{transport:v}:{},...S?{runtimeOwnership:S}:{},...E?{annotations:E}:{},...I.appOutput?{appOutput:I.appOutput}:{}});A.length>0&&(r[M]=A),k&&s.set(M,k);const L={prepare:async o=>{const e=C(o),n=p(I,e);return n.ok?gateToolCall(t,c,I.name,e,E):y(g.invalidInput,x(n.issues),{raw:n.issues})},resources:o=>U(c,b,o,E,j)};t.coordinator?.register(M,L),n[M]=f({description:I.description??`${I.name} (via ${c})`,inputSchema:l(I.inputSchema),toModelOutput:({output:o})=>o.outcome.kind===g.success?O(o.raw,k)??h(o):h(o),execute:async(e,n)=>{const r=C(e);return $(t.coordinator,L,M,n.toolCallId,r,n.abortSignal,async()=>{const e=m({[d]:I.executionTimeoutMs}),i=n.abortSignal||void 0!==e?{...n.abortSignal?{signal:n.abortSignal}:{},...void 0===e?{}:{timeout:e}}:void 0;return w({input:r,agentName:c,agentTool:I,annotations:E,ctx:t,signal:n.abortSignal,call:async t=>await o.callTool({name:I.name,arguments:t},void 0,i)})})}})}}return{tools:n,registry:e,schemaIssuesByToolId:r,observationRetentionByToolId:s}}
1
+ import{lstatSync as o,readdirSync as t,readlinkSync as e,realpathSync as n,statSync as r}from"node:fs";import{inlineAcyclicLocalJsonSchemaReferences as i}from"@roll-agent/core/tool-runtime/json-schema-refs";import{basename as s,dirname as a,isAbsolute as c,resolve as u}from"node:path";import{jsonSchema as l,tool as f}from"ai";import{preflightToolCall as p}from"@roll-agent/core/tool-runtime/preflight";import{readExecutionTimeoutMs as d,ROLL_EXECUTION_TIMEOUT_META_KEY as m}from"@roll-agent/core/tool-runtime/execution-timeout";import{ToolRegistry as v}from"./naming.js";import{TOOL_OUTCOME_KINDS as g,failedToolResult as y,toolResultToModelOutput as h}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as S,TOOL_RESOURCE_HINT_KINDS as b,executeCoordinatedTool as $}from"./tool-execution-coordinator.js";import{executeWithToolApproval as w}from"./tool-approval-continuation.js";import{currentObservationModelOutput as O}from"../engine/observation-projection.js";export{readExecutionTimeoutMs,ROLL_EXECUTION_TIMEOUT_META_KEY}from"@roll-agent/core/tool-runtime/execution-timeout";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function T(o,t){const e=new Map;for(const n of[...o??[],...t])e.set(`${n.path}\0${n.ref}`,n);return[...e.values()]}function C(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function x(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}function N(o){return!0===o?.readOnlyHint&&!0!==o.destructiveHint?S.read:S.write}function E(o){return"string"==typeof o||"number"==typeof o?[o]:Array.isArray(o)&&0!==o.length&&o.every(o=>"string"==typeof o||"number"==typeof o)?o:void 0}const j=256,k=new Map;function A(o,t){for(let e=0;e<o.length;e+=1){const n=o.charCodeAt(e),r=n>=65&&n<=90?String.fromCharCode(n+32):n>=97&&n<=122?String.fromCharCode(n-32):void 0;if(void 0===r)continue;const i=`${o.slice(0,e)}${r}${o.slice(e+1)}`;if(!t.has(i))return i}}function I(o){return"object"==typeof o&&null!==o&&"code"in o?o.code:void 0}function M(t,e,n){const r=A(e,n);if(void 0!==r)try{const n=o(u(t,e)),i=o(u(t,r));return n.dev===i.dev&&n.ino===i.ino}catch(o){const t=I(o);return"ENOENT"!==t&&"ENOTDIR"!==t&&void 0}}function L(o,t){if(k.size>=j){const o=k.keys().next().value;void 0!==o&&k.delete(o)}return k.set(o,t),t}function R(e){const n=k.get(e);if(void 0!==n)return n;let r;try{r=o(e).dev}catch{return L(e,!0)}let i=e;for(;;){const n=k.get(i);if(void 0!==n)return L(e,n);try{const o=t(i),n=new Set(o);for(const t of o){const o=M(i,t,n);if(void 0!==o)return L(i,o),L(e,o)}}catch{}const s=a(i);if(s===i)break;try{if(o(s).dev!==r)break}catch{break}i=s}return L(e,!0)}function _(o){let t=o.normalize("NFC");for(let o=0;o<8;o+=1){const o=t.toUpperCase().toLowerCase().normalize("NFC");if(o===t)return t;t=o}return t}function D(o,t){try{const e=r(o,{bigint:!0}),n=t.map(o=>_(o));return`file-unresolved-alias:${e.dev.toString()}:${e.ino.toString()}:${JSON.stringify(n)}`}catch{return}}function K(t,r=new Set){let i=t;const c=[];for(;;)try{const o=n.native(i),t=c.reverse().map(o=>o.normalize("NFC"));if(0===t.length)return{path:o};const e=R(o),r=u(o,...e?t.map(o=>o.toLowerCase()):t),s=e?D(o,t):void 0;return{path:r,...void 0===s?{}:{unresolvedAliasKey:s}}}catch{try{if(o(i).isSymbolicLink()&&!r.has(i)){const o=new Set(r);o.add(i);const t=e(i);return K(u(a(i),t,...[...c].reverse()),o)}}catch{}const n=a(i);if(n===i)return{path:t};c.push(s(i)),i=n}}function z(o){try{const t=r(o,{bigint:!0});return`file-inode:${t.dev.toString()}:${t.ino.toString()}`}catch{return}}function H(o,t,e){const n=String(t).trim();if(0!==n.length)switch(o.kind){case b.file:{if(!c(n)&&void 0===e)return;const o=K(u(e??"/",n)),t=z(o.path);return[`file:${o.path}`,...void 0===o.unresolvedAliasKey?[]:[o.unresolvedAliasKey],...void 0===t?[]:[t]]}case b.browserSession:return[`browser-session:${n}`];case b.conversation:return[`conversation:${n}`];case b.custom:{const t=o.namespace?.trim();return t?[`${t}:${n}`]:void 0}}}function G(o,t,e,n,r){const i=N(n),s=C(e),a=[{key:`agent:${o}`,mode:i}],c=[];for(const o of r??[]){if(!Object.hasOwn(s,o.field))continue;const e=E(s[o.field]);if(void 0===e)return a;for(const n of e){const e=H(o,n,t);if(void 0===e||0===e.length)return a;c.push(...e.map(t=>({key:t,mode:o.mode??i})))}}return 0===c.length?a:[{key:`agent:${o}`,mode:S.read},...c]}export async function gateToolCall(o,t,e,n,r,i){if(!o.policy)return;const s=o.policy.check({agentName:t,toolName:e,input:n,...r?{annotations:r}:{}});if("deny"===s.action)return y(g.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&o.approvalMemory?.isGranted(r))return;const a=await o.requestApproval({agentName:t,toolName:e,input:n,reason:!1===i?.includePolicyReason?void 0:s.reason,...void 0!==i?.explanation?{explanation:i.explanation}:{},...void 0!==i?.diff?{diff:i.diff}:{},...void 0!==i?.fullDiff?{fullDiff:i.fullDiff}:{},...void 0!==r&&void 0!==i?.sessionGrantLabel?{sessionGrantLabel:i.sessionGrantLabel}:{}});if(!a.approved)return y(g.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&o.approvalMemory?.grant(r)}}export function buildAgentToolset(o,t,e=new v){const n={},r={},s=new Map;for(const a of o){const{client:o,agentName:c,agentSource:u,transport:v,runtimeOwnership:S,resourceBaseDir:b}=a;for(const{tool:N,annotations:E,resourceHints:j,observationRetention:k}of a.tools){const a=i(N.inputSchema),A=T(N.schemaIssues,a.unresolved),I={...N,inputSchema:a.schema,...A.length>0?{schemaIssues:A}:{}},M=e.register(c,I.name,{...u?{agentSource:u}:{},...v?{transport:v}:{},...S?{runtimeOwnership:S}:{},...E?{annotations:E}:{},...I.appOutput?{appOutput:I.appOutput}:{}});A.length>0&&(r[M]=A),k&&s.set(M,k);const L={prepare:async o=>{const e=C(o),n=p(I,e);return n.ok?gateToolCall(t,c,I.name,e,E):y(g.invalidInput,x(n.issues),{raw:n.issues})},resources:o=>G(c,b,o,E,j)};t.coordinator?.register(M,L),n[M]=f({description:I.description??`${I.name} (via ${c})`,inputSchema:l(I.inputSchema),toModelOutput:({output:o})=>o.outcome.kind===g.success?O(o.raw,k)??h(o):h(o),execute:async(e,n)=>{const r=C(e);return $(t.coordinator,L,M,n.toolCallId,r,n.abortSignal,async()=>{const e=d({[m]:I.executionTimeoutMs}),i=n.abortSignal||void 0!==e?{...n.abortSignal?{signal:n.abortSignal}:{},...void 0===e?{}:{timeout:e}}:void 0;return w({input:r,agentName:c,agentTool:I,annotations:E,ctx:t,signal:n.abortSignal,call:async t=>await o.callTool({name:I.name,arguments:t},void 0,i)})})}})}}return{tools:n,registry:e,schemaIssuesByToolId:r,observationRetentionByToolId:s}}