@roll-agent/runtime 0.23.1 → 0.24.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.
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/capability-manifest.d.ts +2 -0
- package/dist/engine/capability-manifest.js +1 -1
- package/dist/engine/compaction-checkpoint.d.ts +244 -244
- package/dist/engine/compaction-semantic-state.d.ts +124 -124
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/server/runtime-protocol-adapter.js +1 -1
- package/dist/service/runtime-service.d.ts +5 -4
- package/dist/service/runtime-service.js +1 -1
- package/dist/store/thread-store.d.ts +14 -3
- package/dist/store/thread-store.js +1 -1
- package/dist/tool-bridge/app-output-content.d.ts +2 -0
- package/dist/tool-bridge/app-output-content.js +1 -0
- package/dist/tool-bridge/build-tools.js +1 -1
- package/dist/tool-bridge/naming.d.ts +2 -0
- package/dist/tool-bridge/naming.js +1 -1
- package/dist/tool-bridge/normalize-app-output.d.ts +5 -0
- package/dist/tool-bridge/normalize-app-output.js +1 -0
- package/dist/tool-bridge/normalize-result.d.ts +3 -1
- package/dist/tool-bridge/normalize-result.js +1 -1
- package/dist/tool-bridge/tool-approval-continuation.js +1 -1
- package/dist/tool-bridge/tool-execution-record.d.ts +5 -0
- package/dist/tool-bridge/tool-execution-record.js +1 -1
- package/dist/types/events.d.ts +2 -1
- package/package.json +3 -3
|
@@ -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 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
|
+
import{chmodSync as e,existsSync as t,mkdirSync as n}from"node:fs";import{homedir as r}from"node:os";import{resolve as s}from"node:path";import{randomUUID as o}from"node:crypto";import{DatabaseSync as i}from"node:sqlite";import{modelMessageSchema as a}from"ai";import{APP_OUTPUT_LIMITS as c,appOutputResultSchema as d,appOutputDescriptorSchema as E,describeAppOutput as _,RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as u,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as h,runtimeDurableEventV15Schema as p,runtimeEventCursorSchema as T,runtimeEventIdSchema as l,timestampSchema as m}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as N,prepareToolExecutionRecordForPersistence as S,prepareAppOutputForPersistence as R,redactSecretText as g}from"../tool-bridge/tool-execution-record.js";import{COMPACTION_CHECKPOINT_VERSION as O,COMPACTION_TRANSCRIPT_COMPLETENESS as I,TRANSCRIPT_MESSAGE_PROVENANCES as v,UnsupportedCompactionCheckpointVersionError as L,archivedTranscriptMessageSchema as x,createCompactionCheckpoint as A,createCompactionCheckpointDraft as C,parseCompactionCheckpoint as f}from"../engine/compaction-checkpoint.js";import{legacyCompactionTranscriptFragmentsSchema as q}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as b}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as M,repairActiveToolProtocol as D}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as w}from"../engine/explicit-skill-context.js";import{threadOriginSchema as U,threadDerivedFromSchema as y,scheduledThreadOriginSchema as F}from"./thread-origin.js";const k=7,W=100,X=500,j=50,H=500,P=100,B=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:u,maxRecordsPerThread:h,maxAgeMs:2592e6};export class RuntimeEventCursorExpiredError extends Error{cursor;constructor(e){super("Runtime event cursor is older than the retained event prefix"),this.name="RuntimeEventCursorExpiredError",this.cursor=e}}export class RuntimeEventCursorGapError extends Error{cursor;constructor(e){super("Runtime event cursor does not belong to the current Thread event log"),this.name="RuntimeEventCursorGapError",this.cursor=e}}export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution"];export const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS={rawTranscript:"raw_transcript",recoveryEvidence:"recovery_evidence"};const G="legacy_assumed";export function expandTilde(e){return e.startsWith("~/")?s(r(),e.slice(2)):e}export function defaultThreadsDir(){return s(r(),".roll-agent","threads")}function Y(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function K(e,t){return"valid"===e.status?e:"fallback"===e.status?{status:"fallback",reason:e.reason,...void 0!==t?{lastValidCheckpointId:t.id}:{}}:{status:"skipped",...void 0!==t?{lastValidCheckpointId:t.id}:{}}}function $(e){if(Number.isInteger(e.schema_version)&&e.schema_version>O)throw new L(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=f(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function J(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:U.parse(e.origin_json?JSON.parse(e.origin_json):{kind:"interactive"}),...e.derived_from_json?{derivedFrom:y.parse(JSON.parse(e.derived_from_json))}:{}}}function V(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>V(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,V(n,t)])):e}function z(){return l.parse(o())}function Q(e,t,n){return T.parse(`rte1:${e}:${String(t)}:${n}`)}function Z(e){const t=T.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=l.safeParse(n[3]);return void 0===n[1]||!Number.isSafeInteger(r)||r<0||!s.success?void 0:{eventLogId:n[1],threadSequence:r,eventId:s.data}}function ee(e){return new Date(Date.parse(m.parse(e))).toISOString()}function te(e,t,n){const r=l.parse(n.event_id),s=p.parse(JSON.parse(n.event_json));return{eventId:r,cursor:Q(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:"tool.completed"===s.type&&null!=n.app_output_json?{...s,appOutput:E.parse(JSON.parse(n.app_output_json))}:s}}export class ThreadStore{db;now;readOnly;constructor(r=defaultThreadsDir(),o={}){this.now=o.now??(()=>new Date),this.readOnly=o.readOnly??!1;const a=expandTilde(r);if(o.readOnly){this.db=new i(s(a,"threads.db"),{readOnly:!0});try{this.db.exec(`PRAGMA busy_timeout = ${String(B)}; PRAGMA query_only = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>7)throw new Error(`Unsupported ThreadStore schema v${String(e.user_version)}`)}catch(e){throw this.db.close(),e}return}t(a)||n(a,{recursive:!0,mode:448}),"win32"!==process.platform&&e(a,448);const c=s(a,"threads.db");if(this.db=new i(c),"win32"!==process.platform)try{e(c,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(B)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>7)throw new Error(`ThreadStore schema v${String(e.user_version)} 高于当前支持的 v${String(7)}`);this.db.exec("BEGIN IMMEDIATE");try{if(this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),e.user_version<7){const e=this.db.prepare("PRAGMA table_info(threads)").all();for(const t of["origin_json","derived_from_json"])e.some(e=>e.name===t)||this.db.exec(`ALTER TABLE threads ADD COLUMN ${t} TEXT`)}Y(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS operation_app_outputs (\n thread_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n result_json TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n byte_length INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, operation_id),\n FOREIGN KEY (thread_id, operation_id) REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS tool_execution_context_coverage (\n thread_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n representation TEXT NOT NULL\n CHECK (representation IN ('raw_transcript', 'recovery_evidence', 'legacy_assumed')),\n transcript_sequence INTEGER,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, execution_id),\n CHECK (\n (representation = 'legacy_assumed' AND transcript_sequence IS NULL)\n OR (representation <> 'legacy_assumed' AND transcript_sequence IS NOT NULL)\n ),\n FOREIGN KEY (thread_id, execution_id)\n REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE,\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);\n CREATE TABLE IF NOT EXISTS thread_runtime_event_state (\n thread_id TEXT PRIMARY KEY,\n event_log_id TEXT NOT NULL UNIQUE,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n retained_from_sequence INTEGER NOT NULL\n CHECK (retained_from_sequence >= 0 AND retained_from_sequence <= next_sequence),\n retained_predecessor_event_id TEXT,\n latest_event_id TEXT,\n retained_count INTEGER NOT NULL CHECK (retained_count >= 0),\n retained_bytes INTEGER NOT NULL CHECK (retained_bytes >= 0),\n CHECK (\n (next_sequence = 0\n AND retained_from_sequence = 0\n AND retained_predecessor_event_id IS NULL\n AND latest_event_id IS NULL)\n OR (next_sequence > 0 AND latest_event_id IS NOT NULL)\n ),\n CHECK (\n (retained_from_sequence = 0 AND retained_predecessor_event_id IS NULL)\n OR (retained_from_sequence > 0 AND retained_predecessor_event_id IS NOT NULL)\n ),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS runtime_events (\n thread_id TEXT NOT NULL,\n thread_sequence INTEGER NOT NULL CHECK (thread_sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n turn_id TEXT,\n event_timestamp TEXT NOT NULL,\n event_json TEXT NOT NULL,\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, thread_sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_runtime_events_thread_created\n ON runtime_events(thread_id, created_at, thread_sequence);"),this.db.prepare("PRAGMA table_info(runtime_events)").all().some(e=>"app_output_json"===e.name)||this.db.exec("ALTER TABLE runtime_events ADD COLUMN app_output_json TEXT"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),e.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),e.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),e.user_version<6&&this.db.prepare("INSERT OR IGNORE INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, NULL, created_at\n FROM tool_executions").run(G),this.initializeMissingRuntimeEventStatesInTransaction();const t=this.now().toISOString();this.enforceAllToolExecutionRetentionInTransaction(t),this.enforceAllRuntimeEventRetentionInTransaction(t),this.db.exec(`PRAGMA user_version = ${String(7)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=N(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceAppOutputRetention(n),this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=a.parse(JSON.parse(t.message_json)),r=w(e);n.run(JSON.stringify(r),t.storage_rowid)}}}initializeMissingRuntimeEventStatesInTransaction(){const e=this.db.prepare("SELECT threads.id\n FROM threads\n LEFT JOIN thread_runtime_event_state\n ON thread_runtime_event_state.thread_id = threads.id\n WHERE thread_runtime_event_state.thread_id IS NULL").all(),t=this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)");for(const n of e)t.run(n.id,o())}enforceAllRuntimeEventRetentionInTransaction(e){const t=this.db.prepare("SELECT thread_id AS id FROM thread_runtime_event_state").all();for(const n of t)this.enforceRuntimeEventRetentionInTransaction(n.id,e)}createThread(e={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const t=e.id??o(),n=this.now().toISOString(),r=U.parse(e.origin??{kind:"interactive"}),s=void 0===e.derivedFrom?void 0:y.parse(e.derivedFrom);this.db.exec("SAVEPOINT create_thread");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at, origin_json, derived_from_json) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n,JSON.stringify(r),void 0===s?null:JSON.stringify(s)),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,I[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)").run(t,o()),this.db.exec("RELEASE create_thread"),t}catch(e){throw this.db.exec("ROLLBACK TO create_thread; RELEASE create_thread"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?J(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(J).filter(t=>void 0===e.origin||t.origin.kind===e.origin)}backfillScheduledOrigin(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=F.parse(t);return this.db.prepare("UPDATE threads SET origin_json = ? WHERE id = ? AND origin_json IS NULL").run(JSON.stringify(n),e).changes>0}readSnapshot(e){this.db.exec("BEGIN");try{const t=this.getThread(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在`);const n=this.now().toISOString(),r=this.getMessages(e),s=e=>void 0!==this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e),o=s("transcript_messages")?this.db.prepare("SELECT sequence, provenance, created_at, message_json FROM transcript_messages WHERE thread_id = ? ORDER BY sequence").all(e).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):r.map((e,n)=>x.parse({sequence:n,message:e,provenance:"legacy_snapshot",createdAt:t.createdAt})),i=s("tool_executions")?this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? ORDER BY sequence").all(e).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence})):[],a=s("compaction_checkpoints")?this.db.prepare("SELECT schema_version, checkpoint_json FROM compaction_checkpoints WHERE thread_id = ? ORDER BY generation").all(e).map($):[],c=s("tool_execution_context_coverage")?this.db.prepare("SELECT execution_id AS executionId, representation, transcript_sequence AS transcriptSequence, created_at AS createdAt FROM tool_execution_context_coverage WHERE thread_id = ?").all(e):i.map(e=>({executionId:e.id,representation:"legacy_assumed",transcriptSequence:null,createdAt:n})),d=s("thread_tool_execution_state")?this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e):void 0,E={thread:t,capturedAt:n,appOutputs:s("operation_app_outputs")?this.db.prepare("SELECT operation_id, result_json, expires_at FROM operation_app_outputs WHERE thread_id = ?").all(e).map(e=>({operationId:e.operation_id,result:this.readAppOutputRow(e.result_json,e.expires_at),expiresAt:e.expires_at})):[],messages:r,transcript:o,toolExecutions:i,checkpoints:a,toolExecutionCoverage:c,nextToolExecutionSequence:d?.next_sequence??(i.at(-1)?.sequence??-1)+1,transcriptCompleteness:s("thread_transcript_state")?this.getTranscriptCompleteness(e):"legacy_snapshot"};return this.db.exec("COMMIT"),E}catch(e){throw this.db.exec("ROLLBACK"),e}}forkSnapshot(e,t={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=new Map(e.checkpoints.map(e=>[e.id,o()])),r=e.checkpoints.map(e=>{const t=f(V(e,n));return f({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),s=e.messages.map(e=>a.parse(V(e,n))),i=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const n=this.createThread({...void 0===t.id?{}:{id:t.id},...void 0===t.title?void 0===e.thread.title?{}:{title:e.thread.title}:{title:t.title},...void 0===t.model?{}:{model:t.model},origin:{kind:"interactive"},derivedFrom:{threadId:e.thread.id,origin:e.thread.origin,capturedAt:e.capturedAt}});this.replaceMessagesInTransaction(n,D(s).messages,i);const o=this.db.prepare("INSERT INTO transcript_messages (thread_id, sequence, role, message_json, provenance, created_at) VALUES (?, ?, ?, ?, ?, ?)");for(const t of e.transcript){const e=x.parse(t);o.run(n,e.sequence,e.message.role,JSON.stringify(e.message),e.provenance,e.createdAt)}this.db.prepare("UPDATE thread_transcript_state SET completeness = ? WHERE thread_id = ?").run(e.transcriptCompleteness,n);const a=this.db.prepare("INSERT INTO tool_executions (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");for(const{sequence:t,...r}of e.toolExecutions){const e=N(r);a.run(n,t,e.id,e.toolCallId,e.agentName,e.toolName,JSON.stringify(e),e.createdAt)}for(const t of e.appOutputs??[])this.insertAppOutput(n,t.operationId,t.result,t.expiresAt);this.enforceAppOutputRetention(n),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(e.nextToolExecutionSequence,n);const c=this.db.prepare("INSERT INTO tool_execution_context_coverage (thread_id, execution_id, representation, transcript_sequence, created_at) VALUES (?, ?, ?, ?, ?)");for(const t of e.toolExecutionCoverage)c.run(n,t.executionId,t.representation,t.transcriptSequence,t.createdAt);const d=this.db.prepare("INSERT INTO compaction_checkpoints (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence, tool_from_sequence, tool_through_sequence, checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const e of r)d.run(e.id,n,e.generation,e.version,e.transcript.messages.fromSequenceExclusive,e.transcript.messages.throughSequence,e.transcript.toolExecutions.fromSequenceExclusive,e.transcript.toolExecutions.throughSequence,JSON.stringify(e),e.createdAt);return this.db.exec("COMMIT"),n}catch(e){throw this.db.exec("ROLLBACK"),e}}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}countTranscriptMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM transcript_messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}updateModel(e,t){this.db.prepare("UPDATE threads SET model = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){const t=p.parse(e.event),n="tool.completed"===t.type?(({appOutput:e,...t})=>t)(t):t,r=JSON.stringify(n),s=ee(e.timestamp),o=Buffer.byteLength(JSON.stringify(t),"utf8")+Buffer.byteLength(s,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(o>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e.threadId);if(void 0===n)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=z(),a=n.next_sequence;return this.db.prepare("INSERT INTO runtime_events\n (thread_id, thread_sequence, event_id, turn_id, event_timestamp,\n event_json, size_bytes, created_at, app_output_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,s,r,o,s,"tool.completed"===t.type?JSON.stringify(t.appOutput):null),this.db.prepare("UPDATE thread_runtime_event_state\n SET next_sequence = ?, latest_event_id = ?,\n retained_count = retained_count + 1,\n retained_bytes = retained_bytes + ?\n WHERE thread_id = ?").run(a+1,i,o,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,s),this.db.exec("COMMIT"),{eventId:i,cursor:Q(n.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:s,event:t}}catch(e){throw this.db.exec("ROLLBACK"),e}}getRuntimeEventCursor(e){const t=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);return 0===t.next_sequence||null===t.latest_event_id?null:Q(t.event_log_id,t.next_sequence-1,l.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:Z(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);this.db.exec("BEGIN IMMEDIATE");try{this.enforceRuntimeEventRetentionInTransaction(e,this.now().toISOString());const r=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===r)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);if(null===t&&r.retained_from_sequence>0)throw new RuntimeEventCursorExpiredError(t);if(null!==t&&void 0!==n&&this.validateRuntimeEventCursor(r,t,n,e),0===r.next_sequence||null===r.latest_event_id)return this.db.exec("COMMIT"),{events:[],throughCursor:null,replayedCount:0};const s=r.next_sequence-1,o=n?.threadSequence??-1,i=this.db.prepare("SELECT *\n FROM runtime_events\n WHERE thread_id = ?\n AND thread_sequence > ?\n AND thread_sequence <= ?\n ORDER BY thread_sequence ASC").all(e,o,s).map(t=>te(r.event_log_id,e,t)),a=Q(r.event_log_id,s,l.parse(r.latest_event_id));return this.db.exec("COMMIT"),{events:i,throughCursor:a,replayedCount:i.length}}catch(e){throw e instanceof RuntimeEventCursorExpiredError||e instanceof RuntimeEventCursorGapError?this.db.exec("COMMIT"):this.db.exec("ROLLBACK"),e}}countRuntimeEvents(e){return this.db.prepare("SELECT COUNT(*) AS count FROM runtime_events WHERE thread_id = ?").get(e).count}validateRuntimeEventCursor(e,t,n,r){if(n.eventLogId!==e.event_log_id||n.threadSequence>=e.next_sequence)throw new RuntimeEventCursorGapError(t);const s=e.retained_from_sequence-1;if(n.threadSequence<s)throw new RuntimeEventCursorExpiredError(t);if(n.threadSequence===s){if(n.eventId!==e.retained_predecessor_event_id)throw new RuntimeEventCursorGapError(t);return}const o=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(r,n.threadSequence);if(void 0===o||o.event_id!==n.eventId)throw new RuntimeEventCursorGapError(t)}enforceRuntimeEventRetentionInTransaction(e,t){const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===n||0===n.retained_count)return;let r=n.retained_from_sequence;if(n.retained_count>RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread){const t=this.db.prepare("SELECT thread_sequence AS keep_from\n FROM runtime_events\n WHERE thread_id = ?\n ORDER BY thread_sequence DESC\n LIMIT 1 OFFSET ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread-1);r=Math.max(r,t?.keep_from??n.next_sequence)}if(n.retained_bytes>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread){const t=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM (\n SELECT thread_sequence,\n SUM(size_bytes) OVER (\n ORDER BY thread_sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM runtime_events\n WHERE thread_id = ?\n )\n WHERE retained_bytes <= ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread);r=Math.max(r,t.keep_from??n.next_sequence)}const s=new Date(Date.parse(t)-RUNTIME_EVENT_RETENTION_POLICY.maxAgeMs).toISOString(),o=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM runtime_events\n WHERE thread_id = ? AND created_at >= ?").get(e,s);if(r=Math.max(r,o.keep_from??n.next_sequence),r<=n.retained_from_sequence)return;const i=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(e,r-1);if(void 0===i)throw new Error("Runtime event retention boundary is not a continuous retained prefix");const a=this.db.prepare("SELECT COUNT(*) AS removed_count, SUM(size_bytes) AS removed_bytes\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence < ?").get(e,r);this.db.prepare("DELETE FROM runtime_events WHERE thread_id = ? AND thread_sequence < ?").run(e,r),this.db.prepare("UPDATE thread_runtime_event_state\n SET retained_from_sequence = ?, retained_predecessor_event_id = ?,\n retained_count = retained_count - ?,\n retained_bytes = retained_bytes - ?\n WHERE thread_id = ?").run(r,i.event_id,a.removed_count,a.removed_bytes??0,e)}appendMessages(e,t,n={}){const r=n.toolExecutionCoverage;if(0===t.length){if(void 0!==r&&r.executionIds.length>0)throw new Error("Tool execution coverage 必须与至少一条 transcript message 原子写入");return}if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);if(void 0!==r&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.rawTranscript&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence)throw new Error("Tool execution coverage representation 无效");const s=r?.executionIds??[],o=new Set;for(const e of s){if("string"!=typeof e||0===e.length)throw new Error("Tool execution coverage execution id 无效");if(o.has(e))throw new Error(`Tool execution coverage execution id 重复: ${e}`);o.add(e)}const i=t.map(e=>w(a.parse(e))),c=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.appendMessagesInTransaction(e,i,c,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}recoverUncoveredToolExecutions(e,t){this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=this.uncoveredToolExecutionRecords(e,{afterSequence:-1,throughSequence:Number.MAX_SAFE_INTEGER,toolCallId:void 0,limit:Number.MAX_SAFE_INTEGER});if(0===n.length)return void this.db.exec("COMMIT");const r=w(a.parse(t(n))),s={executionIds:n.map(e=>e.id),representation:TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence};return this.appendMessagesInTransaction(e,[r],(new Date).toISOString(),s),this.db.exec("COMMIT"),r}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessagesInTransaction(e,t,n,r){const s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),o=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)"),i=this.db.prepare("INSERT INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, ?, ?\n FROM tool_executions\n WHERE thread_id = ?\n AND id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE thread_id = ? AND execution_id = ?\n )"),a=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),c=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let d=a.maxIdx+1,E=c.maxSequence+1;for(const r of t){const t=JSON.stringify(r);s.run(e,d,r.role,t,n),o.run(e,E,r.role,t,v[0],n),d+=1,E+=1}const _=E-1;if(void 0!==r){for(const t of r.executionIds){const s=i.run(r.representation,_,n,e,t,e,t);if(1!==Number(s.changes))throw new Error(`Tool execution "${t}" 不属于 Thread "${e}" 或已被 transcript 覆盖`)}this.enforceToolExecutionRetentionInTransaction(e,n)}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>a.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>a.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const o=w(t);r.run(e,s,o.role,JSON.stringify(o),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=S(t),r=JSON.stringify(n),s=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const o=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===o)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const i=o.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,i,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(i+1,e),void 0!==t.appOutput&&this.insertAppOutput(e,t.id,t.appOutput,new Date(Date.parse(s)+c.maxAgeMs).toISOString()),this.enforceAppOutputRetention(e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}readAppOutputRow(e,t){const n=d.parse(JSON.parse(e));return"available"===n.status&&Date.parse(t)<=this.now().getTime()?{status:"expired"}:n}insertAppOutput(e,t,n,r){const s="available"===n.status&&Date.parse(r)<=this.now().getTime()?{status:"expired"}:R(n),o=JSON.stringify(s);this.db.prepare("INSERT INTO operation_app_outputs\n (thread_id, operation_id, result_json, descriptor_json, byte_length, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e,t,o,JSON.stringify(_(s)),"available"===s.status?Buffer.byteLength(o,"utf8"):0,r)}enforceAppOutputRetention(e){const t=JSON.stringify({status:"expired"});this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND byte_length > 0 AND expires_at <= ?").run(t,t,e,this.now().toISOString()),this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND operation_id IN (\n SELECT operation_id FROM (\n SELECT o.operation_id,\n ROW_NUMBER() OVER (ORDER BY e.sequence DESC) AS rank,\n SUM(o.byte_length) OVER (ORDER BY e.sequence DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS bytes\n FROM operation_app_outputs o JOIN tool_executions e ON e.thread_id = o.thread_id AND e.id = o.operation_id\n WHERE o.thread_id = ? AND o.byte_length > 0\n ) WHERE rank > ? OR bytes > ?\n )").run(t,t,e,e,c.threadRecords,c.threadBytes)}getAppOutputDescriptor(e,t){if(void 0===this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'operation_app_outputs'").get())return void 0===this.db.prepare("SELECT 1 FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t)?void 0:{status:"not_provided"};const n=this.db.prepare("SELECT o.descriptor_json, o.expires_at FROM tool_executions e\n LEFT JOIN operation_app_outputs o ON o.thread_id = e.thread_id AND o.operation_id = e.id\n WHERE e.thread_id = ? AND e.id = ?").get(e,t);if(void 0===n)return;if(null===n.descriptor_json)return{status:"not_provided"};const r=E.parse(JSON.parse(n.descriptor_json));return"available"===r.status&&null!==n.expires_at&&Date.parse(n.expires_at)<=this.now().getTime()?{status:"expired"}:r}getAppOutput(e,t){const n=this.getAppOutputDescriptor(e,t);if(void 0===n||"available"!==n.status)return n;const r=this.db.prepare("SELECT result_json FROM operation_app_outputs WHERE thread_id = ? AND operation_id = ?").get(e,t);return void 0===r?{status:"not_provided"}:d.parse(JSON.parse(r.result_json))}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )").run(e,n);r=Number(s.changes)>0;const o=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(o.length>0){const t=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND sequence = ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )");for(const n of o){const s=t.run(e,n.sequence);r=Number(s.changes)>0||r}}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence}))}listUncoveredToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);return r<=n?[]:this.uncoveredToolExecutionRecords(e,{afterSequence:n,throughSequence:r,toolCallId:t.toolCallId,limit:s})}resolveToolExecutionListRange(e){const t=e.afterSequence??-1,n=e.throughSequence??Number.MAX_SAFE_INTEGER,r=e.limit??W;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>X)throw new Error(`limit 必须是 1-${String(X)} 之间的整数`);if(!Number.isInteger(n)||n<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");return{afterSequence:t,throughSequence:n,limit:r}}uncoveredToolExecutionRecords(e,t){return this.db.prepare("SELECT tool_executions.sequence, tool_executions.record_json\n FROM tool_executions\n LEFT JOIN tool_execution_context_coverage\n ON tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n WHERE tool_executions.thread_id = ?\n AND tool_executions.sequence > ?\n AND tool_executions.sequence <= ?\n AND (? IS NULL OR tool_executions.tool_call_id = ?)\n AND tool_execution_context_coverage.execution_id IS NULL\n ORDER BY tool_executions.sequence ASC\n LIMIT ?").all(e,t.afterSequence,t.throughSequence,t.toolCallId??null,t.toolCallId??null,t.limit).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??W;this.validateRecentPage(n,r,X);const s=this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>({...N(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...N(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===I[0]?I[0]:I[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??j;if(this.validateTranscriptRange(n,r,s,H),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}listRecentTranscriptMessages(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??j;this.validateRecentPage(n,r,H);const s=this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>x.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,$(n))}catch(e){if(e instanceof L)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=$(n);if("valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof L)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,$(n))}catch(e){if(e instanceof L)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:D(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>a.parse(e)),r=t.expectedActiveMessages.map(e=>a.parse(e)),s=D(n);if(s.status!==M.valid){const e=[...s.removedToolCallIds,...s.removedToolResultIds].filter((e,t,n)=>n.indexOf(e)===t).sort((e,t)=>e.localeCompare(t));throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const o=C(t.draft),i=q.parse(t.legacySnapshotTranscriptFragments??[]),c=q.parse(i.map(e=>g(e))).map(e=>a.parse(b(e))),d=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=D(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const i=this.getLatestCheckpoint(e);if(i?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const a=this.getLatestSummaryCheckpoint(e),E=K(o.summary,a),_=this.getTranscriptCompleteness(e),u=C({...o,summary:E,..._===I[1]&&"valid"===o.toolState.integrityStatus?{toolState:{...o.toolState,integrityStatus:"sanitized"}}:{}}),h=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration\n FROM compaction_checkpoints\n WHERE thread_id = ?").get(e),p=this.maxTranscriptMessageSequence(e),T=this.maxToolExecutionSequence(e),l=t.evidenceWatermarks.transcriptMessagesThroughSequence,m=t.evidenceWatermarks.toolExecutionsThroughSequence,N=i?.transcript.messages.throughSequence??-1,S=i?.transcript.toolExecutions.throughSequence??-1,R=2===i?.version?i.semanticEvidence.messagesThroughSequence:-1,g=2===i?.version?i.semanticEvidence.toolExecutionsThroughSequence:-1;if(c.length>0&&1!==i?.version)throw new Error("Legacy snapshot transcript archive requires a V1 parent checkpoint");if(!Number.isInteger(l)||l<N||l>p)throw new Error(`Compaction transcript message watermark ${String(l)} is outside the available range ${String(N)}-${String(p)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.messagesThroughSequence)||t.semanticEvidenceWatermarks.messagesThroughSequence<R||t.semanticEvidenceWatermarks.messagesThroughSequence>l)throw new Error(`Compaction semantic message watermark ${String(t.semanticEvidenceWatermarks.messagesThroughSequence)} is outside the available range ${String(R)}-${String(l)}`);if(!Number.isInteger(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence<g||t.semanticEvidenceWatermarks.toolExecutionsThroughSequence>m)throw new Error(`Compaction semantic Tool watermark ${String(t.semanticEvidenceWatermarks.toolExecutionsThroughSequence)} is outside the available range ${String(g)}-${String(m)}`);if(!Number.isInteger(m)||m<S||m>T)throw new Error(`Compaction tool execution watermark ${String(m)} is outside the available range ${String(S)}-${String(T)}`);let O=l;if(c.length>0){if(l!==p)throw new Error("Legacy snapshot transcript archive requires the latest message watermark");const t=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)");let n=p+1;for(const r of c)t.run(e,n,r.role,JSON.stringify(r),v[1],d),n+=1;O=n-1}const L={messages:{fromSequenceExclusive:N,throughSequence:O},toolExecutions:{fromSequenceExclusive:S,throughSequence:m},completeness:_},x=A({draft:u,semanticState:t.semanticState,semanticEvidence:t.semanticEvidenceWatermarks,generation:h.maxGeneration+1,transcript:L,...void 0!==i?{previousCheckpointId:i.id}:{},createdAt:d});return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version,\n message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence,\n checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(x.id,e,x.generation,x.version,x.transcript.messages.fromSequenceExclusive,x.transcript.messages.throughSequence,x.transcript.toolExecutions.fromSequenceExclusive,x.transcript.toolExecutions.throughSequence,JSON.stringify(x),x.createdAt),this.replaceMessagesInTransaction(e,n,d),this.db.exec("COMMIT"),x}catch(e){throw this.db.exec("ROLLBACK"),e}}readCheckpointTranscript(e,t){if("message"!==t.kind&&"tool_execution"!==t.kind)throw new Error("kind 必须是 message 或 tool_execution");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??j,s="message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,o=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(o,s.throughSequence,r,P);const i="message"===t.kind?this.listTranscriptMessages(e,{afterSequence:o,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:o,throughSequence:s.throughSequence,limit:Math.min(r+1,X)}).map(e=>({kind:"tool_execution",...e})),a=i.length>r,c=a?i.slice(0,r):i,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...a&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=I[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:I[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}validateRecentPage(e,t,n){if(!Number.isSafeInteger(e)||e<0||e>Number.MAX_SAFE_INTEGER)throw new Error("beforeSequence 必须是非负安全整数");if(!Number.isInteger(t)||t<1||t>n)throw new Error(`limit 必须是 1-${String(n)} 之间的整数`)}close(){this.db.close()}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=new Set(["password","passwd","apikey","apisecret","clientsecret","privatekey","secret","devicecredential","signingkey","sessionkey","密码","口令","密钥","私钥","authorization","cookie","setcookie","accesstoken","refreshtoken","authtoken"]),t=/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|\bBearer\s+[A-Za-z0-9._~+/-]{24,}=*|\b(?:sk|rk|pk)-(?:proj-)?[A-Za-z0-9_-]{32,}\b|\b(?:github_pat_[A-Za-z0-9_]{22,}|gh[pousr]_[A-Za-z0-9]{36,})\b|\bxox[baprs]-[A-Za-z0-9-]{24,}\b|\bAKIA[0-9A-Z]{16}\b|\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}\b/u;export function inspectAppOutputContent(s){if("string"==typeof s)return t.test(s)?{status:"rejected",reason:"credential_value"}:void 0;if(Array.isArray(s))for(const e of s){const t=inspectAppOutputContent(e);if(t)return t}else if("object"==typeof s&&null!==s)for(const[t,r]of Object.entries(s)){const s=t.toLowerCase().replace(/[-_\s]/gu,"");if(e.has(s)&&null!==r&&""!==r)return{status:"rejected",reason:"credential_field",field:s};const n=inspectAppOutputContent(r);if(n)return n}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{lstatSync as
|
|
1
|
+
import{lstatSync as t,readdirSync as e,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 p}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as d}from"./naming.js";import{TOOL_OUTCOME_KINDS as m,failedToolResult as v,toolResultToModelOutput as g}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as h,TOOL_RESOURCE_HINT_KINDS as y,executeCoordinatedTool as S}from"./tool-execution-coordinator.js";import{executeWithToolApproval as $}from"./tool-approval-continuation.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function b(t,e){const n=new Map;for(const o of[...t??[],...e])n.set(`${o.path}\0${o.ref}`,o);return[...n.values()]}function w(t){return"object"!=typeof t||null===t||Array.isArray(t)?{}:t}function C(t){return`参数校验失败: ${t.map(t=>t.message).join("; ")}`}function N(t){return!0===t?.readOnlyHint&&!0!==t.destructiveHint?h.read:h.write}function O(t){return"string"==typeof t||"number"==typeof t?[t]:Array.isArray(t)&&0!==t.length&&t.every(t=>"string"==typeof t||"number"==typeof t)?t:void 0}const k=256,A=new Map;function j(t,e){for(let n=0;n<t.length;n+=1){const o=t.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=`${t.slice(0,n)}${r}${t.slice(n+1)}`;if(!e.has(i))return i}}function x(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}function T(e,n,o){const r=j(n,o);if(void 0!==r)try{const o=t(u(e,n)),i=t(u(e,r));return o.dev===i.dev&&o.ino===i.ino}catch(t){const e=x(t);return"ENOENT"!==e&&"ENOTDIR"!==e&&void 0}}function I(t,e){if(A.size>=k){const t=A.keys().next().value;void 0!==t&&A.delete(t)}return A.set(t,e),e}function L(n){const o=A.get(n);if(void 0!==o)return o;let r;try{r=t(n).dev}catch{return I(n,!0)}let i=n;for(;;){const o=A.get(i);if(void 0!==o)return I(n,o);try{const t=e(i),o=new Set(t);for(const e of t){const t=T(i,e,o);if(void 0!==t)return I(i,t),I(n,t)}}catch{}const s=a(i);if(s===i)break;try{if(t(s).dev!==r)break}catch{break}i=s}return I(n,!0)}function E(t){let e=t.normalize("NFC");for(let t=0;t<8;t+=1){const t=e.toUpperCase().toLowerCase().normalize("NFC");if(t===e)return e;e=t}return e}function M(t,e){try{const n=r(t,{bigint:!0}),o=e.map(t=>E(t));return`file-unresolved-alias:${n.dev.toString()}:${n.ino.toString()}:${JSON.stringify(o)}`}catch{return}}function R(e,r=new Set){let i=e;const c=[];for(;;)try{const t=o.native(i),e=c.reverse().map(t=>t.normalize("NFC"));if(0===e.length)return{path:t};const n=L(t),r=u(t,...n?e.map(t=>t.toLowerCase()):e),s=n?M(t,e):void 0;return{path:r,...void 0===s?{}:{unresolvedAliasKey:s}}}catch{try{if(t(i).isSymbolicLink()&&!r.has(i)){const t=new Set(r);t.add(i);const e=n(i);return R(u(a(i),e,...[...c].reverse()),t)}}catch{}const o=a(i);if(o===i)return{path:e};c.push(s(i)),i=o}}function z(t){try{const e=r(t,{bigint:!0});return`file-inode:${e.dev.toString()}:${e.ino.toString()}`}catch{return}}function H(t,e,n){const o=String(e).trim();if(0!==o.length)switch(t.kind){case y.file:{if(!c(o)&&void 0===n)return;const t=R(u(n??"/",o)),e=z(t.path);return[`file:${t.path}`,...void 0===t.unresolvedAliasKey?[]:[t.unresolvedAliasKey],...void 0===e?[]:[e]]}case y.browserSession:return[`browser-session:${o}`];case y.conversation:return[`conversation:${o}`];case y.custom:{const e=t.namespace?.trim();return e?[`${e}:${o}`]:void 0}}}function K(t,e,n,o,r){const i=N(o),s=w(n),a=[{key:`agent:${t}`,mode:i}],c=[];for(const t of r??[]){if(!Object.hasOwn(s,t.field))continue;const n=O(s[t.field]);if(void 0===n)return a;for(const o of n){const n=H(t,o,e);if(void 0===n||0===n.length)return a;c.push(...n.map(e=>({key:e,mode:t.mode??i})))}}return 0===c.length?a:[{key:`agent:${t}`,mode:h.read},...c]}export async function gateToolCall(t,e,n,o,r,i){if(!t.policy)return;const s=t.policy.check({agentName:e,toolName:n,input:o,...r?{annotations:r}:{}});if("deny"===s.action)return v(m.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&t.approvalMemory?.isGranted(r))return;const a=await t.requestApproval({agentName:e,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(m.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&t.approvalMemory?.grant(r)}}export function buildAgentToolset(t,e,n=new d){const o={},r={};for(const s of t){const{client:t,agentName:a,agentSource:c,transport:u,runtimeOwnership:d,resourceBaseDir:h}=s;for(const{tool:y,annotations:N,resourceHints:O}of s.tools){const s=i(y.inputSchema),k=b(y.schemaIssues,s.unresolved),A={...y,inputSchema:s.schema,...k.length>0?{schemaIssues:k}:{}},j=n.register(a,A.name,{...c?{agentSource:c}:{},...u?{transport:u}:{},...d?{runtimeOwnership:d}:{},...N?{annotations:N}:{},...A.appOutput?{appOutput:A.appOutput}:{}});k.length>0&&(r[j]=k);const x={prepare:async t=>{const n=w(t),o=p(A,n);return o.ok?gateToolCall(e,a,A.name,n,N):v(m.invalidInput,C(o.issues),{raw:o.issues})},resources:t=>K(a,h,t,N,O)};e.coordinator?.register(j,x),o[j]=f({description:A.description??`${A.name} (via ${a})`,inputSchema:l(A.inputSchema),toModelOutput:({output:t})=>g(t),execute:async(n,o)=>{const r=w(n);return S(e.coordinator,x,j,o.toolCallId,r,o.abortSignal,async()=>{const n=o.abortSignal?{signal:o.abortSignal}:void 0;return $({input:r,agentName:a,agentTool:A,annotations:N,ctx:e,signal:o.abortSignal,call:async e=>await t.callTool({name:A.name,arguments:e},void 0,n)})})}})}}return{tools:o,registry:n,schemaIssuesByToolId:r}}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AppOutputContract } from "@roll-agent/protocol/app-output";
|
|
1
2
|
import type { AgentRuntimeOwnership, AgentSourceType, AgentTransport } from "@roll-agent/core/types/agent";
|
|
2
3
|
import type { ToolAnnotations } from "../types/policy.ts";
|
|
3
4
|
export interface ToolRouteMetadata {
|
|
@@ -5,6 +6,7 @@ export interface ToolRouteMetadata {
|
|
|
5
6
|
readonly transport?: AgentTransport["type"];
|
|
6
7
|
readonly runtimeOwnership?: AgentRuntimeOwnership;
|
|
7
8
|
readonly annotations?: ToolAnnotations;
|
|
9
|
+
readonly appOutput?: AppOutputContract;
|
|
8
10
|
}
|
|
9
11
|
export interface ToolRoute extends ToolRouteMetadata {
|
|
10
12
|
readonly agentName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=/[^a-zA-Z0-9_-]/g;function e(e){return e.replace(t,"_")}export class ToolRegistry{routes=new Map;register(t,r,n={}){const s=`${e(t)}__${e(r)}`;let o=s,
|
|
1
|
+
const t=/[^a-zA-Z0-9_-]/g;function e(e){return e.replace(t,"_")}export class ToolRegistry{routes=new Map;register(t,r,n={}){const s=`${e(t)}__${e(r)}`;let o=s,u=1;for(;this.routes.has(o);)o=`${s}_${String(u)}`,u+=1;return this.routes.set(o,{agentName:t,toolName:r,...n.agentSource?{agentSource:n.agentSource}:{},...n.transport?{transport:n.transport}:{},...n.runtimeOwnership?{runtimeOwnership:n.runtimeOwnership}:{},...n.appOutput?{appOutput:structuredClone(n.appOutput)}:{},...n.annotations?{annotations:{...n.annotations}}:{}}),o}resolve(t){return this.routes.get(t)}size(){return this.routes.size}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AppOutputContract, type AppOutputResult } from "@roll-agent/protocol";
|
|
2
|
+
/** The SDK uses isError to bypass MCP automatic validation after execution has completed. */
|
|
3
|
+
export declare function hasCompletedAppOutputMarker(value: unknown): boolean;
|
|
4
|
+
/** Only structuredContent from an opted-in tool can become application data. */
|
|
5
|
+
export declare function normalizeAppOutput(value: unknown, contract: AppOutputContract): AppOutputResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AjvJsonSchemaValidator as t}from"@modelcontextprotocol/sdk/validation/ajv";import{APP_OUTPUT_LIMITS as e,getCompletedAppOutputStatus as r,APP_OUTPUT_STATUS_META_KEY as a,appOutputResultSchema as o,validateAppOutputContract as n}from"@roll-agent/protocol";function i(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}export function hasCompletedAppOutputMarker(t){return void 0!==r(t)}export function normalizeAppOutput(r,s){try{if(!i(r))return{status:"invalid"};if(hasCompletedAppOutputMarker(r)&&i(r._meta))return{status:"too_large"===r._meta[a]?"too_large":"invalid"};if(!0===r.isError||!i(r.structuredContent))return{status:"invalid"};n(s);const u=Array.isArray(r.content)?r.content.filter(i).filter(t=>"text"===t.type&&"string"==typeof t.text).map(t=>t.text).join("\n").slice(0,4096):"Tool execution completed.",l=o.safeParse({status:"available",schemaId:s.schemaId,schemaVersion:s.schemaVersion,remoteReadable:s.remoteReadable,data:r.structuredContent,fallbackText:u});if(!l.success)return{status:"invalid"};if(Buffer.byteLength(JSON.stringify(l.data),"utf8")>e.resultBytes)return{status:"too_large"};return(new t).getValidator(s.outputSchema)(r.structuredContent).valid?l.data:{status:"invalid"}}catch{return{status:"invalid"}}}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AppOutputContract, AppOutputResult } from "@roll-agent/protocol";
|
|
1
2
|
import type { JSONValue } from "@ai-sdk/provider";
|
|
2
3
|
export declare const TOOL_OUTCOME_KINDS: {
|
|
3
4
|
readonly success: "success";
|
|
@@ -70,6 +71,7 @@ export type ToolModelOutput = {
|
|
|
70
71
|
* `output` and `isError` are compatibility aliases for callers that have not migrated yet.
|
|
71
72
|
*/
|
|
72
73
|
export interface NormalizedToolResult {
|
|
74
|
+
readonly appOutput?: AppOutputResult;
|
|
73
75
|
readonly raw: unknown;
|
|
74
76
|
readonly model: ToolModelOutput;
|
|
75
77
|
readonly display: unknown;
|
|
@@ -85,7 +87,7 @@ export interface ToolResultOptions {
|
|
|
85
87
|
export declare function createToolResult(outcome: ToolOutcome, display: unknown, options?: ToolResultOptions): NormalizedToolResult;
|
|
86
88
|
export declare function successfulToolResult(display: unknown, options?: Omit<ToolResultOptions, "reason">): NormalizedToolResult;
|
|
87
89
|
export declare function failedToolResult(kind: Exclude<ToolOutcomeKind, typeof TOOL_OUTCOME_KINDS.success>, display: unknown, options?: ToolResultOptions): NormalizedToolResult;
|
|
88
|
-
export declare function normalizeToolResult(result: unknown): NormalizedToolResult;
|
|
90
|
+
export declare function normalizeToolResult(result: unknown, appOutput?: AppOutputContract): NormalizedToolResult;
|
|
89
91
|
export declare function isToolCancellationExecutionState(value: unknown): value is ToolCancellationExecutionState;
|
|
90
92
|
export declare function isNormalizedToolResult(value: unknown): value is NormalizedToolResult;
|
|
91
93
|
export declare function readToolOutcome(value: unknown): ToolOutcome;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{normalizeAppOutput as t,hasCompletedAppOutputMarker as e}from"./normalize-app-output.js";import{extractTextContent as n,isToolErrorResult as o}from"@roll-agent/core/cli/utils/tool-results";export const TOOL_OUTCOME_KINDS={success:"success",userRejected:"user_rejected",policyDenied:"policy_denied",invalidInput:"invalid_input",cancelled:"cancelled",toolFailed:"tool_failed"};export const TOOL_CANCELLATION_EXECUTION_STATES={notExecuted:"not_executed",outcomeUnknown:"outcome_unknown"};const r=16e3,i=64,u=6e4,s=12e6,l=128,c="\n\n[工具模型内容已截断;完整结果仍保留在 raw 视图]";function a(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function p(t){if(null===t||"string"==typeof t||"boolean"==typeof t||"number"==typeof t&&Number.isFinite(t))return t;if(Array.isArray(t))return t.map(p);if(a(t)){const e={};for(const[n,o]of Object.entries(t))e[n]=p(o);return e}return void 0===t?null:String(t)}function d(t){if(!t)return!1;const e=t.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(t=>t.length>0);return 1===e.length&&["data","image","audio"].includes(e[0]??"")||e.some(t=>["base64","blob","bytes"].includes(t))||e.includes("data")&&e.some(t=>["image","audio","binary"].includes(t))}function f(t){return`[binary content omitted: ${String(t.length)} chars]`}function y(t,e,n){return t.length<=e?t:e<=n.length?n.slice(0,e):`${t.slice(0,e-n.length)}${n}`}function O(t,e,n=0){if(n>16)return"[nested value omitted]";if("string"==typeof t)return d(e)||/^data:[^,]+;base64,/iu.test(t)?f(t):y(t,u,c);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,l).map(t=>O(t,void 0,n+1));return t.length>l&&e.push(`[${String(t.length-l)} items omitted]`),e}if(a(t)){const e={},o=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,r]of o.slice(0,l))e[t]=O(r,t,n+1);return o.length>l&&(e["[omitted]"]=`${String(o.length-l)} fields`),e}return void 0===t?null:String(t)}function m(t,e,n=0){if(n>12)return"[nested value omitted]";if("string"==typeof t)return d(e)||/^data:[^,]+;base64,/iu.test(t)?f(t):t.length<=r?t:T(t);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,i).map(t=>m(t,void 0,n+1));return t.length>i&&e.push(`[${String(t.length-i)} items omitted]`),e}if(a(t)){const e={},o=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,r]of o.slice(0,i))e[t]=m(r,t,n+1);return o.length>i&&(e["[omitted]"]=`${String(o.length-i)} fields`),e}return void 0===t?null:String(t)}function g(t){return"string"==typeof t?t:JSON.stringify(p(t),null,2)}function T(t){return t.length<=r?t:`${t.slice(0,r)}\n\n[工具展示内容已截断]`}function x(t,e=!1){const n=O(t),o=g(n);if(o.length<=u)return e?{type:"error-json",value:n}:{type:"json",value:n};const r=y(o,u,c);return e?{type:"error-text",value:r}:{type:"text",value:r}}function _(t,e=!1){const n=y(t,u,c);return e?{type:"error-text",value:n}:{type:"text",value:n}}function h(t){const e=[];let n=u,o=s;for(const r of t.slice(0,l)){if(n<=0&&o<=0)break;if("text"===r.type){if(n<=0)continue;const t=y(r.text,n,c);e.push({type:"text",text:t}),n-=t.length;continue}const t=r.data.data.length;if(t<=o){e.push(r),o-=t;continue}if(n<=0)continue;const i=y(`[${r.mediaType} file omitted from model projection: ${String(t)} chars]`,n,c);e.push({type:"text",text:i}),n-=i.length}if(t.length>l&&n>0){const o=y(`[${String(t.length-l)} model content parts omitted]`,n,c);e.push({type:"text",text:o})}return e}function v(t,e){return e.kind===TOOL_OUTCOME_KINDS.userRejected?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:e.kind===TOOL_OUTCOME_KINDS.success?"string"==typeof t?_(t):x(t):x({code:e.kind,message:"string"==typeof t?t:g(t),...void 0!==e.reason?{reason:e.reason}:{}},!0)}export function createToolResult(t,e,n={}){const o=t.kind!==TOOL_OUTCOME_KINDS.success;return{raw:Object.prototype.hasOwnProperty.call(n,"raw")?n.raw:e,model:n.model??v(e,t),display:e,outcome:t,output:e,isError:o}}export function successfulToolResult(t,e={}){return createToolResult({kind:TOOL_OUTCOME_KINDS.success},t,e)}export function failedToolResult(t,e,n={}){return createToolResult({kind:t,...void 0!==n.reason?{reason:n.reason}:{}},e,n)}function S(t){return a(t)?t.content:void 0}function b(t){return a(t)?t.structuredContent:void 0}function E(t){return a(t)&&"toolResult"in t?{value:t.toolResult,wrapped:!0}:{value:t,wrapped:!1}}function N(t){if("string"==typeof t.data&&"string"==typeof t.mimeType)return{type:"file",data:{type:"data",data:t.data},mediaType:t.mimeType}}function C(t){const e=a(t.resource)?t.resource:void 0;if(!e)return;const n="string"==typeof e.uri?e.uri:"embedded resource";return"string"==typeof e.text?{type:"text",text:`[${n}]\n${e.text}`}:"string"==typeof e.blob?{type:"file",data:{type:"data",data:e.blob},mediaType:"string"==typeof e.mimeType?e.mimeType:"application/octet-stream",filename:n}:void 0}function $(t){if("string"!=typeof t.uri)return;const e="string"==typeof t.name?t.name:"resource",n="string"==typeof t.description?` — ${t.description}`:"";return{type:"text",text:`[${e}] ${t.uri}${n}`}}function L(t){if(a(t)&&"string"==typeof t.type)return"text"===t.type&&"string"==typeof t.text?{type:"text",text:t.text}:"image"===t.type||"audio"===t.type?N(t):"resource"===t.type?C(t):"resource_link"===t.type?$(t):void 0}function k(t,e,n,o){if(n)return v(e,{kind:TOOL_OUTCOME_KINDS.toolFailed});const r=S(t),i=Array.isArray(r)?r.flatMap(t=>{const e=L(t);return void 0===e?[]:[e]}):[],u=b(t);return void 0!==u&&i.length>0&&i.push({type:"text",text:`[structuredContent]\n${g(O(u))}`}),i.length>0?{type:"content",value:h(i)}:void 0!==u?x(u):o?"string"==typeof t?_(t):x(t):v(e,{kind:TOOL_OUTCOME_KINDS.success})}function I(t,e,o){const u=n(S(t));if(u.length>0)return T(u.join("\n"));const s=b(t);if(void 0!==s){const t=m(s),e=g(t);return e.length<=r?t:T(e)}const l=S(t);if(Array.isArray(l)&&l.length>0){const t=l.slice(0,i).flatMap(t=>{if(!a(t)||"string"!=typeof t.type)return[];const e="string"==typeof t.mimeType?` ${t.mimeType}`:"";return[`[${t.type}${e}]`]});if(t.length>0){const e=l.length-Math.min(l.length,i);return`${t.join("\n")}${e>0?`\n[另有 ${String(e)} 项未展示]`:""}`}}if(o){const e=m(t),n=g(e);return n.length<=r?e:T(n)}return e?"工具执行失败(无可展示的文本结果)":"工具执行完成(无可展示的文本结果)"}export function normalizeToolResult(n,r){const i=E(n),u=o(i.value)&&!(void 0!==r&&e(i.value)),s=I(i.value,u,i.wrapped);return{...createToolResult(u?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success},s,{raw:n,model:k(i.value,s,u,i.wrapped)}),...void 0===r?{}:{appOutput:t(i.value,r)}}}export function isToolCancellationExecutionState(t){return Object.values(TOOL_CANCELLATION_EXECUTION_STATES).some(e=>e===t)}function A(t){return!(!a(t)||"string"!=typeof t.kind)&&(!!Object.values(TOOL_OUTCOME_KINDS).some(e=>e===t.kind)&&((void 0===t.reason||"string"==typeof t.reason)&&(t.kind!==TOOL_OUTCOME_KINDS.cancelled?void 0===t.executionState:void 0===t.executionState||isToolCancellationExecutionState(t.executionState))))}export function isNormalizedToolResult(t){return a(t)&&"raw"in t&&"model"in t&&"display"in t&&A(t.outcome)}export function readToolOutcome(t){return isNormalizedToolResult(t)?t.outcome:a(t)&&"execution-denied"===t.type?{kind:TOOL_OUTCOME_KINDS.userRejected,..."string"==typeof t.reason?{reason:t.reason}:{}}:a(t)&&"string"==typeof t.type&&t.type.startsWith("error")||M(t)?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success}}function M(t){return a(t)&&!0===t.isError}export function readIsError(t){return readToolOutcome(t).kind!==TOOL_OUTCOME_KINDS.success}export function readDisplayOutput(t){return isNormalizedToolResult(t)?t.display:a(t)&&"output"in t?t.output:t}export function toolResultToModelOutput(t){return t.model}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as t}from"zod";import{preflightToolCall as e}from"@roll-agent/core/tool-runtime/preflight";import{createToolResult as
|
|
1
|
+
import{z as t}from"zod";import{preflightToolCall as e}from"@roll-agent/core/tool-runtime/preflight";import{createToolResult as o,failedToolResult as r,normalizeToolResult as n,TOOL_OUTCOME_KINDS as a}from"./normalize-result.js";const i=t.object({id:t.string().min(1).max(256)}).strict(),s=t.object({code:t.literal("needs_confirmation"),details:t.object({executionState:t.literal("not_executed"),approvalRequest:t.object({id:t.string().min(1).max(256),tool:t.string().min(1).max(256),expiresAt:t.string().datetime(),summary:t.string().max(2e3).optional(),retryInput:t.union([t.object({scriptApproval:i}).strict(),t.object({toolActionApproval:i}).strict()])})})});function l(e,o){const r=t.object({isError:t.literal(!0),content:t.array(t.object({type:t.literal("text"),text:t.string().max(65536)})).length(1)}).safeParse(e);if(!r.success)return;let n;try{n=JSON.parse(r.data.content[0].text)}catch{return}const a=s.safeParse(n);if(!a.success)return;const i=a.data.details.approvalRequest,l="scriptApproval"in i.retryInput?i.retryInput.scriptApproval:i.retryInput.toolActionApproval;return i.tool!==o||l.id!==i.id||Date.parse(i.expiresAt)<=Date.now()?void 0:i}function c(){return o({kind:a.cancelled,executionState:"not_executed"},"已取消审批;工具未执行操作")}async function p(t,e){if(void 0===e)return t;if(e.aborted)return;let o=()=>{};const r=new Promise(t=>{o=()=>t(void 0),e.addEventListener("abort",o,{once:!0})});try{return await Promise.race([t,r])}finally{e.removeEventListener("abort",o)}}export async function executeWithToolApproval(t){if(t.signal?.aborted)return c();const o=structuredClone(t.input),i=await t.call(structuredClone(o)),s=l(i,t.agentTool.name);if(void 0===s)return n(i,t.agentTool.appOutput);if(t.signal?.aborted)return c();const u={...structuredClone(o),...s.retryInput};if(!e(t.agentTool,u).ok)return n(i,t.agentTool.appOutput);const d=structuredClone(o);delete d.scriptApproval,delete d.toolActionApproval;const m=await p(t.ctx.requestApproval({agentName:t.agentName,toolName:t.agentTool.name,input:d,reason:"tool_requested_confirmation",explanation:s.summary??"工具尚未执行操作;仅批准本次完整参数,不自动重放已执行步骤。"}),t.signal);if(t.signal?.aborted||void 0===m)return c();if(!m.approved)return r(a.userRejected,"用户拒绝本次操作;工具未执行操作");if(Date.parse(s.expiresAt)<=Date.now())return n(i,t.agentTool.appOutput);const g=t.ctx.policy?.check({agentName:t.agentName,toolName:t.agentTool.name,input:structuredClone(u),...void 0===t.annotations?{}:{annotations:t.annotations}});return"deny"===g?.action?r(a.policyDenied,"审批期间策略已改变,未执行操作"):t.signal?.aborted?c():n(await t.call(u),t.agentTool.appOutput)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AppOutputResult } from "@roll-agent/protocol";
|
|
1
2
|
import type { JSONValue } from "@ai-sdk/provider";
|
|
2
3
|
import type { NormalizedToolResult, ToolModelOutput, ToolOutcome } from "./normalize-result.ts";
|
|
3
4
|
export declare const TOOL_EXECUTION_RECORD_VERSION: 1;
|
|
@@ -66,6 +67,8 @@ export interface RedactedToolExecutionValueEnvelope {
|
|
|
66
67
|
export type PersistedToolExecutionValueEnvelope = JsonToolExecutionValueEnvelope | DiagnosticToolExecutionValueEnvelope;
|
|
67
68
|
export type ToolExecutionValueEnvelope = PersistedToolExecutionValueEnvelope | RedactedToolExecutionValueEnvelope;
|
|
68
69
|
export interface ToolExecutionRecord {
|
|
70
|
+
/** Transient hand-off to the dedicated result table; never included in record_json. */
|
|
71
|
+
readonly appOutput?: AppOutputResult;
|
|
69
72
|
readonly version: ToolExecutionRecordVersion;
|
|
70
73
|
readonly id: ToolExecutionRecordId;
|
|
71
74
|
readonly toolCallId: string;
|
|
@@ -145,4 +148,6 @@ export declare function parseToolExecutionRecord(value: unknown): ToolExecutionR
|
|
|
145
148
|
export declare function encodeToolExecutionValue(value: unknown): PersistedToolExecutionValueEnvelope;
|
|
146
149
|
export declare function createToolExecutionRecord(input: CreateToolExecutionRecordInput): ToolExecutionRecord;
|
|
147
150
|
export declare function toRedactedToolExecutionRecordSummary(record: ToolExecutionRecord): RedactedToolExecutionRecordSummary;
|
|
151
|
+
/** Reject complete payloads requiring redaction; never silently change a business DTO. */
|
|
152
|
+
export declare function prepareAppOutputForPersistence(value: AppOutputResult): AppOutputResult;
|
|
148
153
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{TOOL_OUTCOME_KINDS as n,isToolCancellationExecutionState as o}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,i=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),a="[redacted]",s=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,c=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),u=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,d=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,l=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,p=/^[a-f0-9]{64}$/u;function E(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function f(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>f(e,t+1)):E(e)&&Object.values(e).every(e=>f(e,t+1))))}function O(e){return E(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function y(e){return!(!E(e)||1!==e.version||!f(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(O))}function I(e){return!!E(e)&&(e.kind===n.success?void 0===e.reason&&void 0===e.executionState:!(!Object.values(n).includes(e.kind)||void 0!==e.reason&&"string"!=typeof e.reason)&&(e.kind!==n.cancelled?void 0===e.executionState:void 0===e.executionState||o(e.executionState)))}function N(e){return!!E(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&E(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function _(e){return!(!E(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?f(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(N))}function m(e,t){return`${e}[${JSON.stringify(t)}]`}function T(e,t){return`${e}[${String(t)}]`}function g(e,t){return`${e}[${String(t)}]`}function S(e){try{return String(e)}catch{return"[value could not be stringified]"}}function v(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function x(e,t){return e.diagnostics.push(t),v(t)}function L(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function C(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function b(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function h(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=T(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:S(e)}));continue}o.push(R(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;x(n,C("symbol"==typeof o?g(t,o):m(t,o),"Non-index array properties are not representable in JSON"))}return o}function A(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?g(t,r):m(t,r);if("symbol"==typeof r){x(n,C(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?b(o,r,"value"in a?R(a.value,i,n):x(n,C(i,"Accessor properties are not read during persistence"))):x(n,C(i,"Non-enumerable properties are not representable in JSON")):x(n,C(i,"Property descriptor disappeared while encoding"))}return o}function U(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return x(n,L(e,t));if(Array.isArray(e))return h(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?x(n,C(t,"Only plain objects and arrays are losslessly representable in JSON",S(e))):A(e,t,n)}catch(e){return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:S(e)})}finally{n.ancestors.delete(e)}}function R(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:S(e)}):void 0===e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?x(n,C(t,"Symbols are not JSON values",S(e))):"function"==typeof e?x(n,C(t,"Functions are not JSON values",e.name||"anonymous")):U(e,t,n)}function k(e){return encodeToolExecutionValue(e).value}function D(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function X(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:k(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(D)}}function V(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function P(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(s.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(c.has(n))return!0;const o=P(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function $(e){if(void 0===e)return!1;const t=P(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function j(e){return`[binary content omitted: ${String(e.length)} chars]`}function B(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function w(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return B(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function z(e,t){let n=0,o="";for(const r of e.matchAll(u)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=w(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=a){return z(e,t).replace(l,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function G(e,t){return $(t)||/^data:[^,\s]+;base64,/iu.test(e)?j(e):redactSecretText(e).replace(d,e=>j(e))}function M(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return G(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>M(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?a:M(r,t,n+1);return o}function K(e){return{kind:e.kind,path:e.path,message:G(e.message),...void 0!==e.detail?{detail:G(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function J(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:M(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:M(e.value),diagnostics:e.diagnostics.map(K)}}function F(e){return"text"===e.type?{type:"text",text:G(e.text)}:{type:"file",data:{type:"data",data:j(e.data.data)},mediaType:G(e.mediaType),...void 0!==e.filename?{filename:G(e.filename)}:{}}}function Z(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:G(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:M(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:G(e.reason)}:{}}:{type:"content",value:e.value.map(F)}}function q(e){return e.kind===n.success?{kind:n.success}:{kind:e.kind,...void 0!==e.reason?{reason:G(e.reason)}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function Y(e){return JSON.stringify(e)}function W(e){return Buffer.byteLength(e,"utf8")}function H(t){return e("sha256").update(t,"utf8").digest("hex")}function Q(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function ee(e,t,n,o){return{originalByteLength:W(e),storedByteLength:W(n),redactedSha256:H(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function te(e,t,n){const o=Y(e),r=J(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:ee(o,i,i)};const s=Q(H(i),a);return{value:s,metadata:ee(o,i,Y(s),a)}}function ne(e,t,n){const o=G(e);if(W(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${H(o)}]`,i=W(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&W(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function oe(e,t,n){const o=Y(e),r=Z(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${H(i)}]`};return{value:s,metadata:ee(o,i,Y(s),a)}}function re(e,t,o){const r=Y(e),i=q(e),a=Y(i),s=o??(W(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),c=void 0===s||i.kind===n.success?i:{kind:i.kind,reason:`[durable outcome detail omitted; sha256:${H(a)}]`,...i.kind===n.cancelled&&void 0!==i.executionState?{executionState:i.executionState}:{}};return{value:c,metadata:ee(r,a,Y(c),s)}}function ie(e){return E(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&p.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ae(e){return E(e)&&1===e.version&&E(e.fields)&&ie(e.fields.input)&&ie(e.fields.raw)&&ie(e.fields.model)&&ie(e.fields.display)&&ie(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&E(e)&&ae(e.persistence)}function se(e,t){const n=te(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=te(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=oe(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=te(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=re(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:ne(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:ne(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:ne(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:ne(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=se(e);return W(Y(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:se(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&r.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(t())}export function isToolExecutionRecord(e){return E(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&y(e.input)&&y(e.raw)&&_(e.model)&&y(e.display)&&I(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=R(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:X(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:V(e.result.outcome)}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:i,raw:i,model:Z(e.model),display:J(e.display),outcome:q(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}
|
|
1
|
+
import{inspectAppOutputContent as e}from"./app-output-content.js";import{createHash as t,randomUUID as n}from"node:crypto";import{APP_OUTPUT_LIMITS as o,appOutputResultSchema as r}from"@roll-agent/protocol";import{TOOL_OUTCOME_KINDS as i,isToolCancellationExecutionState as a}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const s=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,u=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),c="[redacted]",d=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,l=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),p=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,f=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,E=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,O=/^[a-f0-9]{64}$/u;function y(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function I(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>I(e,t+1)):y(e)&&Object.values(e).every(e=>I(e,t+1))))}function m(e){return y(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function N(e){return!(!y(e)||1!==e.version||!I(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(m))}function _(e){return!!y(e)&&(e.kind===i.success?void 0===e.reason&&void 0===e.executionState:!(!Object.values(i).includes(e.kind)||void 0!==e.reason&&"string"!=typeof e.reason)&&(e.kind!==i.cancelled?void 0===e.executionState:void 0===e.executionState||a(e.executionState)))}function T(e){return!!y(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&y(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function g(e){return!(!y(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?I(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(T))}function S(e,t){return`${e}[${JSON.stringify(t)}]`}function v(e,t){return`${e}[${String(t)}]`}function x(e,t){return`${e}[${String(t)}]`}function L(e){try{return String(e)}catch{return"[value could not be stringified]"}}function b(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function C(e,t){return e.diagnostics.push(t),b(t)}function h(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function A(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function U(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function R(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=v(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:L(e)}));continue}o.push(X(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;C(n,A("symbol"==typeof o?x(t,o):S(t,o),"Non-index array properties are not representable in JSON"))}return o}function k(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?x(t,r):S(t,r);if("symbol"==typeof r){C(n,A(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?U(o,r,"value"in a?X(a.value,i,n):C(n,A(i,"Accessor properties are not read during persistence"))):C(n,A(i,"Non-enumerable properties are not representable in JSON")):C(n,A(i,"Property descriptor disappeared while encoding"))}return o}function D(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return C(n,h(e,t));if(Array.isArray(e))return R(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?C(n,A(t,"Only plain objects and arrays are losslessly representable in JSON",L(e))):k(e,t,n)}catch(e){return C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:L(e)})}finally{n.ancestors.delete(e)}}function X(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:L(e)}):void 0===e?C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?C(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?C(n,A(t,"Symbols are not JSON values",L(e))):"function"==typeof e?C(n,A(t,"Functions are not JSON values",e.name||"anonymous")):D(e,t,n)}function V(e){return encodeToolExecutionValue(e).value}function P(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function $(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:V(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(P)}}function j(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{},...e.kind===i.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function B(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(d.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(l.has(n))return!0;const o=B(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function w(e){if(void 0===e)return!1;const t=B(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function z(e){return`[binary content omitted: ${String(e.length)} chars]`}function G(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function M(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return G(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function K(e,t){let n=0,o="";for(const r of e.matchAll(p)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=M(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=c){return K(e,t).replace(E,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function J(e,t){return w(t)||/^data:[^,\s]+;base64,/iu.test(e)?z(e):redactSecretText(e).replace(f,e=>z(e))}function F(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return J(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>F(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?c:F(r,t,n+1);return o}function Z(e){return{kind:e.kind,path:e.path,message:J(e.message),...void 0!==e.detail?{detail:J(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function q(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:F(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:F(e.value),diagnostics:e.diagnostics.map(Z)}}function Y(e){return"text"===e.type?{type:"text",text:J(e.text)}:{type:"file",data:{type:"data",data:z(e.data.data)},mediaType:J(e.mediaType),...void 0!==e.filename?{filename:J(e.filename)}:{}}}function W(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:J(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:F(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:J(e.reason)}:{}}:{type:"content",value:e.value.map(Y)}}function H(e){return e.kind===i.success?{kind:i.success}:{kind:e.kind,...void 0!==e.reason?{reason:J(e.reason)}:{},...e.kind===i.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function Q(e){return JSON.stringify(e)}function ee(e){return Buffer.byteLength(e,"utf8")}function te(e){return t("sha256").update(e,"utf8").digest("hex")}function ne(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function oe(e,t,n,o){return{originalByteLength:ee(e),storedByteLength:ee(n),redactedSha256:te(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function re(e,t,n){const o=Q(e),r=q(e),i=Q(r),a=n??(ee(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:oe(o,i,i)};const s=ne(te(i),a);return{value:s,metadata:oe(o,i,Q(s),a)}}function ie(e,t,n){const o=J(e);if(ee(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${te(o)}]`,i=ee(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&ee(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function ae(e,t,n){const o=Q(e),r=W(e),i=Q(r),a=n??(ee(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${te(i)}]`};return{value:s,metadata:oe(o,i,Q(s),a)}}function se(e,t,n){const o=Q(e),r=H(e),a=Q(r),s=n??(ee(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),u=void 0===s||r.kind===i.success?r:{kind:r.kind,reason:`[durable outcome detail omitted; sha256:${te(a)}]`,...r.kind===i.cancelled&&void 0!==r.executionState?{executionState:r.executionState}:{}};return{value:u,metadata:oe(o,a,Q(u),s)}}function ue(e){return y(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&O.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ce(e){return y(e)&&1===e.version&&y(e.fields)&&ue(e.fields.input)&&ue(e.fields.raw)&&ue(e.fields.model)&&ue(e.fields.display)&&ue(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&y(e)&&ce(e.persistence)}function de(e,t){const n=re(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=re(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=ae(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=re(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=se(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:ie(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:ie(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:ie(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:ie(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=de(e);return ee(Q(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:de(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&s.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(n())}export function isToolExecutionRecord(e){return y(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&N(e.input)&&N(e.raw)&&g(e.model)&&N(e.display)&&_(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=X(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:$(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:j(e.result.outcome),...void 0===e.result.appOutput?{}:{appOutput:prepareAppOutputForPersistence(e.result.appOutput)}}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:u,raw:u,model:W(e.model),display:q(e.display),outcome:H(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}export function prepareAppOutputForPersistence(t){try{const n=r.safeParse(t);if(!n.success)return{status:"invalid"};const i=n.data,a=JSON.stringify(i);if(Buffer.byteLength(a,"utf8")>o.resultBytes)return{status:"too_large"};if("available"!==i.status)return i;const s=e(i.data)??e(i.fallbackText)??e(i.schemaId);return s||i}catch{return{status:"invalid"}}}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionCancellationReason } from "./cancellation.ts";
|
|
2
2
|
import type { ToolOutcome } from "../tool-bridge/normalize-result.ts";
|
|
3
|
-
import type { FileChangeDiff, UserInputForm } from "@roll-agent/protocol";
|
|
3
|
+
import type { AppOutputDescriptor, FileChangeDiff, UserInputForm } from "@roll-agent/protocol";
|
|
4
4
|
import type { SessionUserInputRequestId } from "../interaction/user-input-interaction-manager.ts";
|
|
5
5
|
export interface SessionTokenUsage {
|
|
6
6
|
readonly inputTokens?: number;
|
|
@@ -48,6 +48,7 @@ export type SessionEvent = {
|
|
|
48
48
|
readonly toolName: string;
|
|
49
49
|
/** Stable key for querying the typed, append-only execution record. */
|
|
50
50
|
readonly executionId?: string;
|
|
51
|
+
readonly appOutput?: AppOutputDescriptor;
|
|
51
52
|
readonly outcome?: ToolOutcome;
|
|
52
53
|
readonly display?: unknown;
|
|
53
54
|
/** @deprecated Use `display`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@ai-sdk/provider": "^4.0.7",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
31
|
-
"@roll-agent/protocol": "0.
|
|
31
|
+
"@roll-agent/protocol": "0.7.0",
|
|
32
32
|
"@vscode/ripgrep": "1.18.0",
|
|
33
33
|
"ai": "^7.0.65",
|
|
34
34
|
"yaml": "2.8.3",
|
|
35
35
|
"zod": "^3.25.76"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@roll-agent/core": "0.
|
|
38
|
+
"@roll-agent/core": "0.41.0",
|
|
39
39
|
"@types/node": "^22.0.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|