@roll-agent/runtime 0.19.1 → 0.20.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 (32) hide show
  1. package/dist/engine/agent-session.d.ts +35 -4
  2. package/dist/engine/agent-session.js +1 -1
  3. package/dist/engine/compaction-checkpoint.d.ts +16 -16
  4. package/dist/engine/compaction-semantic-state.d.ts +2 -2
  5. package/dist/engine/context-window.d.ts +18 -0
  6. package/dist/engine/context-window.js +1 -1
  7. package/dist/engine/conversation-engine.d.ts +28 -4
  8. package/dist/engine/conversation-engine.js +1 -1
  9. package/dist/engine/model-catalog-default.d.ts +2 -0
  10. package/dist/engine/model-catalog-default.js +1 -0
  11. package/dist/engine/model-catalog-snapshot.d.ts +2 -0
  12. package/dist/engine/model-catalog-snapshot.js +1 -0
  13. package/dist/engine/model-catalog.d.ts +72 -0
  14. package/dist/engine/model-catalog.js +1 -0
  15. package/dist/engine/system-prompt.d.ts +2 -0
  16. package/dist/engine/system-prompt.js +1 -1
  17. package/dist/index.d.ts +15 -3
  18. package/dist/index.js +1 -1
  19. package/dist/scheduler/schedule-store.d.ts +24 -1
  20. package/dist/scheduler/schedule-store.js +1 -1
  21. package/dist/scheduler/types.d.ts +38 -0
  22. package/dist/server/protocol.d.ts +2 -2
  23. package/dist/service/runtime-service.js +1 -1
  24. package/dist/store/thread-origin.d.ts +135 -0
  25. package/dist/store/thread-origin.js +1 -0
  26. package/dist/store/thread-store.d.ts +44 -2
  27. package/dist/store/thread-store.js +1 -1
  28. package/dist/tool-bridge/build-tools.d.ts +2 -0
  29. package/dist/tool-bridge/build-tools.js +1 -1
  30. package/dist/tool-bridge/file-tools/read-file-tool.d.ts +2 -2
  31. package/dist/tool-bridge/transcript-tool.d.ts +2 -2
  32. package/package.json +2 -2
@@ -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{RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as c,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as d,runtimeDurableEventV13Schema as E,runtimeEventCursorSchema as _,runtimeEventIdSchema as u,timestampSchema as h}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as T,prepareToolExecutionRecordForPersistence as p,redactSecretText as l}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as m,COMPACTION_TRANSCRIPT_COMPLETENESS as N,TRANSCRIPT_MESSAGE_PROVENANCES as R,UnsupportedCompactionCheckpointVersionError as S,archivedTranscriptMessageSchema as g,createCompactionCheckpoint as I,createCompactionCheckpointDraft as O,parseCompactionCheckpoint as L}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as v}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as x}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as C,repairActiveToolProtocol as f}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as A}from"../engine/explicit-skill-context.js";const q=6,b=100,M=500,w=50,D=500,U=100,F=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:c,maxRecordsPerThread:d,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 y="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 X(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 W(e){if(Number.isInteger(e.schema_version)&&e.schema_version>m)throw new S(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=L(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 H(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at}}function P(){return u.parse(o())}function G(e,t,n){return _.parse(`rte1:${e}:${String(t)}:${n}`)}function B(e){const t=_.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=u.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 Y(e){return new Date(Date.parse(h.parse(e))).toISOString()}function j(e,t,n){const r=u.parse(n.event_id);return{eventId:r,cursor:G(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:E.parse(JSON.parse(n.event_json))}}export class ThreadStore{db;constructor(r=defaultThreadsDir()){const o=expandTilde(r);t(o)||n(o,{recursive:!0,mode:448}),"win32"!==process.platform&&e(o,448);const a=s(o,"threads.db");if(this.db=new i(a),"win32"!==process.platform)try{e(a,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(F)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>6)throw new Error(`ThreadStore schema v${String(e.user_version)} 高于当前支持的 v${String(6)}`);this.db.exec("BEGIN IMMEDIATE");try{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 );"),X(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 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.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(y),this.initializeMissingRuntimeEventStatesInTransaction(),this.enforceAllToolExecutionRetentionInTransaction((new Date).toISOString()),this.enforceAllRuntimeEventRetentionInTransaction((new Date).toISOString()),this.db.exec(`PRAGMA user_version = ${String(6)};`),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=T(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.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=A(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={}){const t=e.id??o(),n=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,N[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("COMMIT"),t}catch(e){throw this.db.exec("ROLLBACK"),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?H(t):void 0}listThreads(){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(H)}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)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){const t=E.parse(e.event),n=JSON.stringify(t),r=Y(e.timestamp),s=Buffer.byteLength(n,"utf8")+Buffer.byteLength(r,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(s>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const o=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===o)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=P(),a=o.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)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,r,n,s,r),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,s,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,r),this.db.exec("COMMIT"),{eventId:i,cursor:G(o.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:r,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:G(t.event_log_id,t.next_sequence-1,u.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:B(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);this.db.exec("BEGIN IMMEDIATE");try{this.enforceRuntimeEventRetentionInTransaction(e,(new Date).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 thread_sequence, event_id, turn_id, event_timestamp, event_json\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=>j(r.event_log_id,e,t)),a=G(r.event_log_id,s,u.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=>A(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=A(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,R[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=A(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=p(t),r=JSON.stringify(n),s=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const o=t.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,o,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(o+1,e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),o}catch(e){throw this.db.exec("ROLLBACK"),e}}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=>({...T(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??b;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>M)throw new Error(`limit 必须是 1-${String(M)} 之间的整数`);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=>({...T(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??b;this.validateRecentPage(n,r,M);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=>({...T(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{...T(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===N[0]?N[0]:N[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??w;if(this.validateTranscriptRange(n,r,s,D),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=>g.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??w;this.validateRecentPage(n,r,D);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=>g.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,W(n))}catch(e){if(e instanceof S)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=W(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof S)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,W(n))}catch(e){if(e instanceof S)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:f(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=f(n);if(s.status!==C.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=O(t.draft),i=v.parse(t.legacySnapshotTranscriptFragments??[]),c=v.parse(i.map(e=>l(e))).map(e=>a.parse(x(e))),d=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=f(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=O({...o,summary:E,..._===N[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),T=this.maxTranscriptMessageSequence(e),p=this.maxToolExecutionSequence(e),l=t.evidenceWatermarks.transcriptMessagesThroughSequence,m=t.evidenceWatermarks.toolExecutionsThroughSequence,S=i?.transcript.messages.throughSequence??-1,g=i?.transcript.toolExecutions.throughSequence??-1,L=2===i?.version?i.semanticEvidence.messagesThroughSequence:-1,v=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<S||l>T)throw new Error(`Compaction transcript message watermark ${String(l)} is outside the available range ${String(S)}-${String(T)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<L||t.semanticEvidenceWatermarks.messagesThroughSequence>l)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(L)}-${String(l)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<v||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>m)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(v)}-${String(m)}`);if(!Number.isInteger(m)||m<g||m>p)throw new Error(`Compaction tool execution watermark ${String(m)} is outside the available range ${String(g)}-${String(p)}`);let x=l;if(c.length>0){if(l!==T)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=T+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),R[1],d),n+=1;x=n-1}const C={messages:{fromSequenceExclusive:S,throughSequence:x},toolExecutions:{fromSequenceExclusive:g,throughSequence:m},completeness:_},A=I({draft:u,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:h.maxGeneration+1,transcript:C,...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(A.id,e,A.generation,A.version,A.transcript.messages.fromSequenceExclusive,A.transcript.messages.throughSequence,A.transcript.toolExecutions.fromSequenceExclusive,A.transcript.toolExecutions.throughSequence,JSON.stringify(A),A.createdAt),this.replaceMessagesInTransaction(e,n,d),this.db.exec("COMMIT"),A}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??w,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,U);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,M)}).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=N[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:N[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{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{RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as c,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as d,runtimeDurableEventV13Schema as E,runtimeEventCursorSchema as _,runtimeEventIdSchema as u,timestampSchema as h}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as T,prepareToolExecutionRecordForPersistence as p,redactSecretText as l}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as m,COMPACTION_TRANSCRIPT_COMPLETENESS as N,TRANSCRIPT_MESSAGE_PROVENANCES as S,UnsupportedCompactionCheckpointVersionError as R,archivedTranscriptMessageSchema as g,createCompactionCheckpoint as O,createCompactionCheckpointDraft as I,parseCompactionCheckpoint as v}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as x}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as L}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as C,repairActiveToolProtocol as A}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as f}from"../engine/explicit-skill-context.js";import{threadOriginSchema as q,threadDerivedFromSchema as b,scheduledThreadOriginSchema as M}from"./thread-origin.js";const w=7,D=100,U=500,y=50,F=500,k=100,X=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:c,maxRecordsPerThread:d,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 W="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 H(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 j(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 P(e){if(Number.isInteger(e.schema_version)&&e.schema_version>m)throw new R(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=v(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 B(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:q.parse(e.origin_json?JSON.parse(e.origin_json):{kind:"interactive"}),...e.derived_from_json?{derivedFrom:b.parse(JSON.parse(e.derived_from_json))}:{}}}function G(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>G(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,G(n,t)])):e}function Y(){return u.parse(o())}function K(e,t,n){return _.parse(`rte1:${e}:${String(t)}:${n}`)}function $(e){const t=_.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=u.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 J(e){return new Date(Date.parse(h.parse(e))).toISOString()}function V(e,t,n){const r=u.parse(n.event_id);return{eventId:r,cursor:K(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:E.parse(JSON.parse(n.event_json))}}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(X)}; 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(X)};\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`)}H(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 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.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(W),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=T(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.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=f(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=q.parse(e.origin??{kind:"interactive"}),s=void 0===e.derivedFrom?void 0:b.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,N[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?B(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(B).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=M.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=>g.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):r.map((e,n)=>g.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=>({...T(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(P):[],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,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=v(G(e,n));return v({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),s=e.messages.map(e=>a.parse(G(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,A(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=g.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=T(r);a.run(n,t,e.id,e.toolCallId,e.agentName,e.toolName,JSON.stringify(e),e.createdAt)}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=E.parse(e.event),n=JSON.stringify(t),r=J(e.timestamp),s=Buffer.byteLength(n,"utf8")+Buffer.byteLength(r,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(s>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const o=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===o)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=Y(),a=o.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)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,r,n,s,r),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,s,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,r),this.db.exec("COMMIT"),{eventId:i,cursor:K(o.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:r,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:K(t.event_log_id,t.next_sequence-1,u.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:$(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 thread_sequence, event_id, turn_id, event_timestamp, event_json\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=>V(r.event_log_id,e,t)),a=K(r.event_log_id,s,u.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=>f(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=f(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,S[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=f(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=p(t),r=JSON.stringify(n),s=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const o=t.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,o,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(o+1,e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),o}catch(e){throw this.db.exec("ROLLBACK"),e}}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=>({...T(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??D;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>U)throw new Error(`limit 必须是 1-${String(U)} 之间的整数`);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=>({...T(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??D;this.validateRecentPage(n,r,U);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=>({...T(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{...T(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===N[0]?N[0]:N[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??y;if(this.validateTranscriptRange(n,r,s,F),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=>g.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??y;this.validateRecentPage(n,r,F);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=>g.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,P(n))}catch(e){if(e instanceof R)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=P(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof R)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,P(n))}catch(e){if(e instanceof R)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:A(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=A(n);if(s.status!==C.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=I(t.draft),i=x.parse(t.legacySnapshotTranscriptFragments??[]),c=x.parse(i.map(e=>l(e))).map(e=>a.parse(L(e))),d=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=A(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=j(o.summary,a),_=this.getTranscriptCompleteness(e),u=I({...o,summary:E,..._===N[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),T=this.maxTranscriptMessageSequence(e),p=this.maxToolExecutionSequence(e),l=t.evidenceWatermarks.transcriptMessagesThroughSequence,m=t.evidenceWatermarks.toolExecutionsThroughSequence,R=i?.transcript.messages.throughSequence??-1,g=i?.transcript.toolExecutions.throughSequence??-1,v=2===i?.version?i.semanticEvidence.messagesThroughSequence:-1,x=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<R||l>T)throw new Error(`Compaction transcript message watermark ${String(l)} is outside the available range ${String(R)}-${String(T)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<v||t.semanticEvidenceWatermarks.messagesThroughSequence>l)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(v)}-${String(l)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<x||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>m)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(x)}-${String(m)}`);if(!Number.isInteger(m)||m<g||m>p)throw new Error(`Compaction tool execution watermark ${String(m)} is outside the available range ${String(g)}-${String(p)}`);let L=l;if(c.length>0){if(l!==T)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=T+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),S[1],d),n+=1;L=n-1}const C={messages:{fromSequenceExclusive:R,throughSequence:L},toolExecutions:{fromSequenceExclusive:g,throughSequence:m},completeness:_},f=O({draft:u,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:h.maxGeneration+1,transcript:C,...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(f.id,e,f.generation,f.version,f.transcript.messages.fromSequenceExclusive,f.transcript.messages.throughSequence,f.transcript.toolExecutions.fromSequenceExclusive,f.transcript.toolExecutions.throughSequence,JSON.stringify(f),f.createdAt),this.replaceMessagesInTransaction(e,n,d),this.db.exec("COMMIT"),f}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??y,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,k);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,U)}).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=N[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:N[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,3 +1,4 @@
1
+ import { type JsonSchemaRefIssue } from "@roll-agent/core/tool-runtime/json-schema-refs";
1
2
  import { type ToolSet } from "ai";
2
3
  import type { FileChangeDiff } from "@roll-agent/protocol";
3
4
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -50,6 +51,7 @@ interface ApprovalDisplayOptions {
50
51
  export interface BuiltToolset {
51
52
  readonly tools: ToolSet;
52
53
  readonly registry: ToolRegistry;
54
+ readonly schemaIssuesByToolId: Readonly<Record<string, readonly JsonSchemaRefIssue[]>>;
53
55
  }
54
56
  export declare function gateToolCall(ctx: ToolBridgeContext, agentName: string, toolName: string, input: Record<string, unknown>, annotations: ToolAnnotations | undefined, display?: ApprovalDisplayOptions): Promise<NormalizedToolResult | undefined>;
55
57
  export declare function buildAgentToolset(sources: readonly AgentToolSource[], ctx: ToolBridgeContext, registry?: ToolRegistry): BuiltToolset;
@@ -1 +1 @@
1
- import{lstatSync as e,readdirSync as t,readlinkSync as n,realpathSync as o,statSync as r}from"node:fs";import{basename as i,dirname as s,isAbsolute as a,resolve as c}from"node:path";import{jsonSchema as u,tool as l}from"ai";import{preflightToolCall as f}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as d}from"./naming.js";import{TOOL_OUTCOME_KINDS as p,failedToolResult as m,normalizeToolResult as v,toolResultToModelOutput as g}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as y,TOOL_RESOURCE_HINT_KINDS as h,executeCoordinatedTool as S}from"./tool-execution-coordinator.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function b(e){return"object"!=typeof e||null===e||Array.isArray(e)?{}:e}function $(e){return`参数校验失败: ${e.map(e=>e.message).join("; ")}`}function w(e){return!0===e?.readOnlyHint&&!0!==e.destructiveHint?y.read:y.write}function C(e){return"string"==typeof e||"number"==typeof e?[e]:Array.isArray(e)&&0!==e.length&&e.every(e=>"string"==typeof e||"number"==typeof e)?e:void 0}const N=256,k=new Map;function A(e,t){for(let n=0;n<e.length;n+=1){const o=e.charCodeAt(n),r=o>=65&&o<=90?String.fromCharCode(o+32):o>=97&&o<=122?String.fromCharCode(o-32):void 0;if(void 0===r)continue;const i=`${e.slice(0,n)}${r}${e.slice(n+1)}`;if(!t.has(i))return i}}function O(e){return"object"==typeof e&&null!==e&&"code"in e?e.code:void 0}function x(t,n,o){const r=A(n,o);if(void 0!==r)try{const o=e(c(t,n)),i=e(c(t,r));return o.dev===i.dev&&o.ino===i.ino}catch(e){const t=O(e);return"ENOENT"!==t&&"ENOTDIR"!==t&&void 0}}function j(e,t){if(k.size>=N){const e=k.keys().next().value;void 0!==e&&k.delete(e)}return k.set(e,t),t}function L(n){const o=k.get(n);if(void 0!==o)return o;let r;try{r=e(n).dev}catch{return j(n,!0)}let i=n;for(;;){const o=k.get(i);if(void 0!==o)return j(n,o);try{const e=t(i),o=new Set(e);for(const t of e){const e=x(i,t,o);if(void 0!==e)return j(i,e),j(n,e)}}catch{}const a=s(i);if(a===i)break;try{if(e(a).dev!==r)break}catch{break}i=a}return j(n,!0)}function T(e){let t=e.normalize("NFC");for(let e=0;e<8;e+=1){const e=t.toUpperCase().toLowerCase().normalize("NFC");if(e===t)return t;t=e}return t}function E(e,t){try{const n=r(e,{bigint:!0}),o=t.map(e=>T(e));return`file-unresolved-alias:${n.dev.toString()}:${n.ino.toString()}:${JSON.stringify(o)}`}catch{return}}function R(t,r=new Set){let a=t;const u=[];for(;;)try{const e=o.native(a),t=u.reverse().map(e=>e.normalize("NFC"));if(0===t.length)return{path:e};const n=L(e),r=c(e,...n?t.map(e=>e.toLowerCase()):t),i=n?E(e,t):void 0;return{path:r,...void 0===i?{}:{unresolvedAliasKey:i}}}catch{try{if(e(a).isSymbolicLink()&&!r.has(a)){const e=new Set(r);e.add(a);const t=n(a);return R(c(s(a),t,...[...u].reverse()),e)}}catch{}const o=s(a);if(o===a)return{path:t};u.push(i(a)),a=o}}function z(e){try{const t=r(e,{bigint:!0});return`file-inode:${t.dev.toString()}:${t.ino.toString()}`}catch{return}}function H(e,t,n){const o=String(t).trim();if(0!==o.length)switch(e.kind){case h.file:{if(!a(o)&&void 0===n)return;const e=R(c(n??"/",o)),t=z(e.path);return[`file:${e.path}`,...void 0===e.unresolvedAliasKey?[]:[e.unresolvedAliasKey],...void 0===t?[]:[t]]}case h.browserSession:return[`browser-session:${o}`];case h.conversation:return[`conversation:${o}`];case h.custom:{const t=e.namespace?.trim();return t?[`${t}:${o}`]:void 0}}}function K(e,t,n,o,r){const i=w(o),s=b(n),a=[{key:`agent:${e}`,mode:i}],c=[];for(const e of r??[]){if(!Object.hasOwn(s,e.field))continue;const n=C(s[e.field]);if(void 0===n)return a;for(const o of n){const n=H(e,o,t);if(void 0===n||0===n.length)return a;c.push(...n.map(t=>({key:t,mode:e.mode??i})))}}return 0===c.length?a:[{key:`agent:${e}`,mode:y.read},...c]}export async function gateToolCall(e,t,n,o,r,i){if(!e.policy)return;const s=e.policy.check({agentName:t,toolName:n,input:o,...r?{annotations:r}:{}});if("deny"===s.action)return m(p.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&e.approvalMemory?.isGranted(r))return;const a=await e.requestApproval({agentName:t,toolName:n,input:o,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 m(p.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&e.approvalMemory?.grant(r)}}export function buildAgentToolset(e,t,n=new d){const o={};for(const r of e){const{client:e,agentName:i,agentSource:s,transport:a,runtimeOwnership:c,resourceBaseDir:d}=r;for(const{tool:y,annotations:h,resourceHints:w}of r.tools){const r=n.register(i,y.name,{...s?{agentSource:s}:{},...a?{transport:a}:{},...c?{runtimeOwnership:c}:{},...h?{annotations:h}:{}}),C={prepare:async e=>{const n=b(e),o=f(y,n);return o.ok?gateToolCall(t,i,y.name,n,h):m(p.invalidInput,$(o.issues),{raw:o.issues})},resources:e=>K(i,d,e,h,w)};t.coordinator?.register(r,C),o[r]=l({description:y.description??`${y.name} (via ${i})`,inputSchema:u(y.inputSchema),toModelOutput:({output:e})=>g(e),execute:async(n,o)=>{const i=b(n);return S(t.coordinator,C,r,o.toolCallId,i,o.abortSignal,async()=>{const t=o.abortSignal?{signal:o.abortSignal}:void 0,n=await e.callTool({name:y.name,arguments:i},void 0,t);return v(n)})}})}}return{tools:o,registry:n}}
1
+ import{lstatSync as e,readdirSync as t,readlinkSync as n,realpathSync as o,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 d}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as m}from"./naming.js";import{TOOL_OUTCOME_KINDS as p,failedToolResult as v,normalizeToolResult as g,toolResultToModelOutput as h}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as y,TOOL_RESOURCE_HINT_KINDS as S,executeCoordinatedTool as $}from"./tool-execution-coordinator.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function b(e,t){const n=new Map;for(const o of[...e??[],...t])n.set(`${o.path}\0${o.ref}`,o);return[...n.values()]}function w(e){return"object"!=typeof e||null===e||Array.isArray(e)?{}:e}function C(e){return`参数校验失败: ${e.map(e=>e.message).join("; ")}`}function N(e){return!0===e?.readOnlyHint&&!0!==e.destructiveHint?y.read:y.write}function k(e){return"string"==typeof e||"number"==typeof e?[e]:Array.isArray(e)&&0!==e.length&&e.every(e=>"string"==typeof e||"number"==typeof e)?e:void 0}const A=256,O=new Map;function j(e,t){for(let n=0;n<e.length;n+=1){const o=e.charCodeAt(n),r=o>=65&&o<=90?String.fromCharCode(o+32):o>=97&&o<=122?String.fromCharCode(o-32):void 0;if(void 0===r)continue;const i=`${e.slice(0,n)}${r}${e.slice(n+1)}`;if(!t.has(i))return i}}function x(e){return"object"==typeof e&&null!==e&&"code"in e?e.code:void 0}function T(t,n,o){const r=j(n,o);if(void 0!==r)try{const o=e(u(t,n)),i=e(u(t,r));return o.dev===i.dev&&o.ino===i.ino}catch(e){const t=x(e);return"ENOENT"!==t&&"ENOTDIR"!==t&&void 0}}function I(e,t){if(O.size>=A){const e=O.keys().next().value;void 0!==e&&O.delete(e)}return O.set(e,t),t}function L(n){const o=O.get(n);if(void 0!==o)return o;let r;try{r=e(n).dev}catch{return I(n,!0)}let i=n;for(;;){const o=O.get(i);if(void 0!==o)return I(n,o);try{const e=t(i),o=new Set(e);for(const t of e){const e=T(i,t,o);if(void 0!==e)return I(i,e),I(n,e)}}catch{}const s=a(i);if(s===i)break;try{if(e(s).dev!==r)break}catch{break}i=s}return I(n,!0)}function E(e){let t=e.normalize("NFC");for(let e=0;e<8;e+=1){const e=t.toUpperCase().toLowerCase().normalize("NFC");if(e===t)return t;t=e}return t}function M(e,t){try{const n=r(e,{bigint:!0}),o=t.map(e=>E(e));return`file-unresolved-alias:${n.dev.toString()}:${n.ino.toString()}:${JSON.stringify(o)}`}catch{return}}function R(t,r=new Set){let i=t;const c=[];for(;;)try{const e=o.native(i),t=c.reverse().map(e=>e.normalize("NFC"));if(0===t.length)return{path:e};const n=L(e),r=u(e,...n?t.map(e=>e.toLowerCase()):t),s=n?M(e,t):void 0;return{path:r,...void 0===s?{}:{unresolvedAliasKey:s}}}catch{try{if(e(i).isSymbolicLink()&&!r.has(i)){const e=new Set(r);e.add(i);const t=n(i);return R(u(a(i),t,...[...c].reverse()),e)}}catch{}const o=a(i);if(o===i)return{path:t};c.push(s(i)),i=o}}function z(e){try{const t=r(e,{bigint:!0});return`file-inode:${t.dev.toString()}:${t.ino.toString()}`}catch{return}}function H(e,t,n){const o=String(t).trim();if(0!==o.length)switch(e.kind){case S.file:{if(!c(o)&&void 0===n)return;const e=R(u(n??"/",o)),t=z(e.path);return[`file:${e.path}`,...void 0===e.unresolvedAliasKey?[]:[e.unresolvedAliasKey],...void 0===t?[]:[t]]}case S.browserSession:return[`browser-session:${o}`];case S.conversation:return[`conversation:${o}`];case S.custom:{const t=e.namespace?.trim();return t?[`${t}:${o}`]:void 0}}}function K(e,t,n,o,r){const i=N(o),s=w(n),a=[{key:`agent:${e}`,mode:i}],c=[];for(const e of r??[]){if(!Object.hasOwn(s,e.field))continue;const n=k(s[e.field]);if(void 0===n)return a;for(const o of n){const n=H(e,o,t);if(void 0===n||0===n.length)return a;c.push(...n.map(t=>({key:t,mode:e.mode??i})))}}return 0===c.length?a:[{key:`agent:${e}`,mode:y.read},...c]}export async function gateToolCall(e,t,n,o,r,i){if(!e.policy)return;const s=e.policy.check({agentName:t,toolName:n,input:o,...r?{annotations:r}:{}});if("deny"===s.action)return v(p.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&e.approvalMemory?.isGranted(r))return;const a=await e.requestApproval({agentName:t,toolName:n,input:o,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 v(p.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&e.approvalMemory?.grant(r)}}export function buildAgentToolset(e,t,n=new m){const o={},r={};for(const s of e){const{client:e,agentName:a,agentSource:c,transport:u,runtimeOwnership:m,resourceBaseDir:y}=s;for(const{tool:S,annotations:N,resourceHints:k}of s.tools){const s=i(S.inputSchema),A=b(S.schemaIssues,s.unresolved),O={...S,inputSchema:s.schema,...A.length>0?{schemaIssues:A}:{}},j=n.register(a,O.name,{...c?{agentSource:c}:{},...u?{transport:u}:{},...m?{runtimeOwnership:m}:{},...N?{annotations:N}:{}});A.length>0&&(r[j]=A);const x={prepare:async e=>{const n=w(e),o=d(O,n);return o.ok?gateToolCall(t,a,O.name,n,N):v(p.invalidInput,C(o.issues),{raw:o.issues})},resources:e=>K(a,y,e,N,k)};t.coordinator?.register(j,x),o[j]=f({description:O.description??`${O.name} (via ${a})`,inputSchema:l(O.inputSchema),toModelOutput:({output:e})=>h(e),execute:async(n,o)=>{const r=w(n);return $(t.coordinator,x,j,o.toolCallId,r,o.abortSignal,async()=>{const t=o.abortSignal?{signal:o.abortSignal}:void 0,n=await e.callTool({name:O.name,arguments:r},void 0,t);return g(n)})}})}}return{tools:o,registry:n,schemaIssuesByToolId:r}}
@@ -12,12 +12,12 @@ declare const readFileInputSchema: z.ZodObject<{
12
12
  limit: z.ZodOptional<z.ZodNumber>;
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  path: string;
15
- offset?: number | undefined;
16
15
  limit?: number | undefined;
16
+ offset?: number | undefined;
17
17
  }, {
18
18
  path: string;
19
- offset?: number | undefined;
20
19
  limit?: number | undefined;
20
+ offset?: number | undefined;
21
21
  }>;
22
22
  export type ReadFileInput = z.infer<typeof readFileInputSchema>;
23
23
  export declare function executeReadFile(settings: ResolvedFileToolsSettings, tracker: FileStateTracker, input: ReadFileInput): NormalizedToolResult;
@@ -14,13 +14,13 @@ declare const transcriptToolInputSchema: z.ZodObject<{
14
14
  limit: z.ZodOptional<z.ZodNumber>;
15
15
  }, "strict", z.ZodTypeAny, {
16
16
  checkpointId: string;
17
- kind?: "message" | "tool_execution" | undefined;
18
17
  limit?: number | undefined;
18
+ kind?: "message" | "tool_execution" | undefined;
19
19
  afterSequence?: number | undefined;
20
20
  }, {
21
21
  checkpointId: string;
22
- kind?: "message" | "tool_execution" | undefined;
23
22
  limit?: number | undefined;
23
+ kind?: "message" | "tool_execution" | undefined;
24
24
  afterSequence?: number | undefined;
25
25
  }>;
26
26
  export type TranscriptToolInput = z.infer<typeof transcriptToolInputSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,7 @@
35
35
  "zod": "^3.25.76"
36
36
  },
37
37
  "devDependencies": {
38
- "@roll-agent/core": "0.36.0",
38
+ "@roll-agent/core": "0.37.0",
39
39
  "@types/node": "^22.0.0"
40
40
  },
41
41
  "scripts": {