@roll-agent/runtime 1.1.0 → 1.3.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.
@@ -1 +1 @@
1
- import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as u,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as h,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as c,RUNTIME_V14_MAX_TURN_ATTACHMENTS as l,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as v,normalizeUserInputResultForForm as I,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV16Schema as T,runtimeInstanceIdSchema as A,runtimeProtocolVersionSchema as w,streamIdSchema as S,threadIdSchema as q,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as b}from"../engine/capability-manifest.js";import{TurnReasoningDisplay as E}from"./turn-reasoning-display.js";import{isSensitiveFieldName as O,redactSecretText as P,toRedactedToolExecutionRecordSummary as C}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as U,RuntimeEventCursorGapError as N,ThreadStore as F}from"../store/thread-store.js";const k=4194304,j=500,B=1e4,M=16e3,D=64,V=128,$=16,L=new Set(["_meta","authorization","cookie","password","provideroptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function _(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=_(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>f(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function z(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function W(e,t,r=0){if(void 0!==t&&(L.has(t.toLowerCase())||O(t)))return"[redacted]";if(r>=$)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=P(e);return t.length<=M?t:`${t.slice(0,M)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,D).map(e=>W(e,void 0,r+1));return e.length>D&&t.push(`[${String(e.length-D)} items omitted]`),t}if(z(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,V))void 0!==n&&(t[s]=W(n,s,r+1));return Object.keys(e).length>V&&(t._omitted=`${String(Object.keys(e).length-V)} keys omitted`),t}return void 0===e?null:String(e)}function G(e){return e instanceof Error?e.message:String(e)}function J(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>z(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function H(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function K(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>z(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:H(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:H(e.image)}]:[]:[]):[]}function Q(e){return q.parse(e)}function X(e,t){return{id:Q(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function Y(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=P(J(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...K(e.message)]}}function Z(e,t={status:"not_provided"}){const r=C(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:W(r.display,void 0),appOutput:t}}function ee(e,t){const s=W(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(P(t.explanation)),a=void 0===t.diff?void 0:W(t.diff,void 0),o=z(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:v.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:P(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;providerReasoning;reasoningTextSubscription;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??k,this.idempotencyCacheEntries=s.idempotencyCacheEntries??B,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.providerReasoning=s.providerReasoning,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=A.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=w.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||"summary"===this.providerReasoning||void 0===this.providerReasoning&&void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:j,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??u,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??h,maxTurnAttachments:l,maxStagedAttachments:e?.maxStagedAttachments??c}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}subscribeReasoningText(e){if(this.assertOpen(),"text"!==this.providerReasoning||this.reasoningTextSubscription)return;const t={deliver:e};return this.reasoningTextSubscription=t,()=>{this.reasoningTextSubscription===t&&(this.reasoningTextSubscription=void 0)}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>X(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:X(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:X(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Y(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof U)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof N)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:X(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.sessions.get(e.threadId);return void 0!==t&&(await t.close(),this.sessions.delete(e.threadId)),this.attachmentStore?.releaseThread(e.threadId),this.store.deleteThread(e.threadId),{deleted:!0}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))return{detached:!1};const t=this.sessions.get(e.threadId);return void 0===t?{detached:!1}:(await t.close(),this.sessions.delete(e.threadId),{detached:!0})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:W(b(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const n=this.resolveTurnAttachments(e),a={threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0},o=this.createTurnReasoningDisplay(a);this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}return this.driveTurn(a,e.input.text,n,o).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertOpen(),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertOpen();const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertOpen();const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertOpen();return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=I(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}async close(){if(this.closing)return;this.closing=!0,this.setUserInputAvailable(!1);for(const e of this.activeTurns.values())e.session.cancel();const e=[...this.sessions.values()];this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.reasoningTextSubscription=void 0,this.attachmentStore?.close(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear(),this.fatalErrorListeners.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertOpen(),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t;const r=await this.engine.resumeSession(e);return this.rememberSession(r),r}rememberSession(e){e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r){const s=(new Date).toISOString(),n={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:s,threadId:e,...void 0!==t?{turnId:t}:{}},i=y.safeParse(r),a=i.success?(()=>{let r;try{r=this.store.appendRuntimeEvent({threadId:e,...void 0===t?{}:{turnId:t},timestamp:s,event:i.data})}catch(e){throw this.notifyFatalError(e),e}return{...n,durability:"durable",eventId:r.eventId,cursor:r.cursor,event:i.data}})():{...n,durability:"ephemeral",event:T.parse(r)};this.sequence+=1;const o="reasoning.text.delta"===r.type?this.reasoningTextSubscription?[this.reasoningTextSubscription.deliver]:[]:this.listeners;for(const e of o)try{e(a)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}createTurnReasoningDisplay(e){const t=this.providerReasoning,r=this.reasoningSummaryProjector;if(void 0===t&&!r)return;const s=this.reasoningTextSubscription;if("text"===t&&!s)return;const n="text"===t?"reasoning.text.delta":"reasoning.summary.delta",i=()=>"text"!==t||s===this.reasoningTextSubscription;return new E(e=>{if(i())return void 0===t?r?.(e.delta):e.presentation===t?e.delta:void 0},(t,r)=>{i()&&this.emit(e.threadId,e.turnId,{type:n,reasoningId:t,delta:r})},void 0===t)}async driveTurn(e,t,r,s){const n={reasoning:s,streamId:void 0,text:"",terminalEvent:void 0},i=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let a;try{for await(const t of e.session.send(i))this.projectSessionEvent(e,n,t);a=n.terminalEvent??{type:"turn.completed"}}catch(e){n.reasoning?.reset(),a=n.terminalEvent??{type:"turn.failed",stage:"execute",message:P(G(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==n.streamId&&this.projectSessionEvent(e,n,{type:"message-finish",text:n.text}),this.emitTurnTerminal(e,a)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{if(r.reasoning?.reset(),void 0!==r.streamId)return;const s=S.parse(t());return r.streamId=s,r.text="",void this.emit(e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??S.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{r.reasoning?.finish();const n=r.streamId??S.parse(t());return void 0===r.streamId&&this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:P(r.text||s.text)}),r.streamId=void 0,void(r.text="")}case"reasoning-start":return void r.reasoning?.start(s.reasoningId);case"reasoning-delta":return void r.reasoning?.append(s);case"reasoning-end":return void r.reasoning?.end(s.reasoningId);case"tool-call":return void this.emit(e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:W(s.input,void 0)});case"tool-output-delta":return void this.emit(e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:P(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:W(s.display,void 0)});case"confirmation-required":{const t=ee(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:P(s.fullDiff)}),void this.emit(e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.reasoning?.reset(),r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:P(s.message)}:{type:"turn.failed",stage:"execute",message:P(e.interactionFailure)};break;case"error":r.reasoning?.reset(),r.terminalEvent??={type:"turn.failed",stage:s.stage,message:P(s.message)};break;case"context-recovery":this.emit(e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state});break;case"model-step-start":case"model-recovery":r.reasoning?.reset();break;case"step-finish":r.reasoning?.finish()}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:P(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:P(t.reason)}:t;this.emit(e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.threadId,e.turnId,t)}}
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as h,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as u,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as c,RUNTIME_V14_MAX_TURN_ATTACHMENTS as l,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as I,normalizeUserInputResultForForm as v,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV16Schema as T,runtimeInstanceIdSchema as w,runtimeProtocolVersionSchema as S,streamIdSchema as A,threadIdSchema as q,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as b}from"../engine/capability-manifest.js";import{TurnReasoningDisplay as O}from"./turn-reasoning-display.js";import{isSensitiveFieldName as E,redactSecretText as P,toRedactedToolExecutionRecordSummary as C}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as U,RuntimeEventCursorGapError as N,ThreadStore as F}from"../store/thread-store.js";const k=4194304,B=500,j=1e4,M=16e3,D=64,V=128,$=16,J=new Set(["_meta","authorization","cookie","password","provideroptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function L(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=L(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>f(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}async settlePending(){await Promise.allSettled([...this.inFlight.values()].map(e=>e.response))}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function _(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(e,t,r=0){if(void 0!==t&&(J.has(t.toLowerCase())||E(t)))return"[redacted]";if(r>=$)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=P(e);return t.length<=M?t:`${t.slice(0,M)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,D).map(e=>z(e,void 0,r+1));return e.length>D&&t.push(`[${String(e.length-D)} items omitted]`),t}if(_(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,V))void 0!==n&&(t[s]=z(n,s,r+1));return Object.keys(e).length>V&&(t._omitted=`${String(Object.keys(e).length-V)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function G(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>_(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function H(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function K(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>_(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:H(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:H(e.image)}]:[]:[]):[]}function Q(e){return q.parse(e)}function X(e,t){return{id:Q(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function Y(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=P(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...K(e.message)]}}function Z(e,t={status:"not_provided"}){const r=C(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:z(r.display,void 0),appOutput:t}}function ee(e,t){const s=z(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(P(t.explanation)),a=void 0===t.diff?void 0:z(t.diff,void 0),o=_(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:I.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:P(t.reason)}:{}}}export class RuntimeService{closePromise;openingSessions=new Map;closingThreads=new Set;ownership=new Map;turnJobs=new Map;runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;providerReasoning;reasoningTextSubscription;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??k,this.idempotencyCacheEntries=s.idempotencyCacheEntries??j,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.providerReasoning=s.providerReasoning,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=w.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=S.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||"summary"===this.providerReasoning||void 0===this.providerReasoning&&void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:B,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??h,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??u,maxTurnAttachments:l,maxStagedAttachments:e?.maxStagedAttachments??c}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}subscribeReasoningText(e){if(this.assertOpen(),"text"!==this.providerReasoning||this.reasoningTextSubscription)return;const t={deliver:e};return this.reasoningTextSubscription=t,()=>{this.reasoningTextSubscription===t&&(this.reasoningTextSubscription=void 0)}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>X(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:X(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:X(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Y(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof U)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof N)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:X(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.store.retainThread(e.threadId);this.closingThreads.add(e.threadId);let r=!1;try{await Promise.allSettled([this.openingSessions.get(e.threadId)]);const s=this.sessions.get(e.threadId);return await(s?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),r=!0,t?.assert(),this.store.deleteThread(e.threadId),{deleted:!0}}finally{r&&(t?.release(),this.closingThreads.delete(e.threadId))}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.closingThreads.has(e.threadId))return{detached:!1};this.closingThreads.add(e.threadId),await Promise.allSettled([this.openingSessions.get(e.threadId)]);const t=this.sessions.get(e.threadId);return void 0!==t||this.ownership.has(e.threadId)?(await(t?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),this.closingThreads.delete(e.threadId),{detached:!0}):(this.closingThreads.delete(e.threadId),{detached:!1})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:z(b(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});this.assertThreadOpen(e.threadId);const n=this.resolveTurnAttachments(e),a={ownership:this.ownership.get(e.threadId),threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0},o=this.createTurnReasoningDisplay(a);this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(a.ownership,e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}const d=this.driveTurn(a,e.input.text,n,o);return this.turnJobs.set(e.threadId,d),d.then(()=>{this.turnJobs.get(e.threadId)===d&&this.turnJobs.delete(e.threadId)},()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=v(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}close(){if(void 0!==this.closePromise)return this.closePromise;this.closing=!0,this.setUserInputAvailable(!1);for(const e of this.activeTurns.values())e.session.cancel();return this.closePromise=(async()=>{await this.mutationRequests.settlePending(),await Promise.allSettled(this.openingSessions.values());const e=(await Promise.allSettled([...this.sessions.values()].map(e=>e.close()))).filter(e=>"rejected"===e.status);if(e.length>0)throw new AggregateError(e.map(e=>e.reason),"Runtime session cleanup failed");await Promise.all(this.turnJobs.values());for(const e of this.ownership.keys())this.finishThread(e);this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.reasoningTextSubscription=void 0,this.attachmentStore?.close(),this.mutationRequests.clear(),this.fatalErrorListeners.clear()})(),this.closePromise}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertThreadOpen(e),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t.assertWritable?.(),this.ownership.get(e)?.assert(),t;const r=this.openingSessions.get(e);if(void 0!==r)return r;const s=this.engine.resumeSession(e).then(t=>(this.rememberSession(t),this.assertThreadOpen(e),t)).finally(()=>this.openingSessions.delete(e));return this.openingSessions.set(e,s),s}assertThreadOpen(e){if(this.assertOpen(),this.closingThreads.has(e))throw new RuntimeServiceError(i.threadBusy,"会话正在关闭,请等待清理完成",{retryable:!0})}retainOwnership(e){this.assertThreadOpen(e);const t=this.ownership.get(e);if(void 0!==t)return void t.assert();const r=this.store.retainThread(e);void 0!==r&&this.ownership.set(e,r)}finishThread(e){this.ownership.get(e)?.assert(),this.attachmentStore?.releaseThread(q.parse(e)),this.ownership.get(e)?.release(),this.ownership.delete(e),this.sessions.delete(e)}rememberSession(e){if(this.sessions.get(e.id)===e)return;const t=this.ownership.get(e.id)??this.store.retainThread(e.id);void 0!==t&&(t.assert(),this.ownership.set(e.id,t)),e.addCloseBarrier?.(async()=>{if(await this.turnJobs.get(e.id),t?.assert(),this.sessions.get(e.id)!==e)throw new RuntimeServiceError(i.threadBusy,"会话已关闭,不能释放后续会话的占用");this.finishThread(e.id)}),e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r,s){e?.assert();const n=(new Date).toISOString(),i={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:n,threadId:t,...void 0!==r?{turnId:r}:{}},a=y.safeParse(s),o=a.success?(()=>{let e;try{e=this.store.appendRuntimeEvent({threadId:t,...void 0===r?{}:{turnId:r},timestamp:n,event:a.data})}catch(e){throw this.notifyFatalError(e),e}return{...i,durability:"durable",eventId:e.eventId,cursor:e.cursor,event:a.data}})():{...i,durability:"ephemeral",event:T.parse(s)};this.sequence+=1;const h="reasoning.text.delta"===s.type?this.reasoningTextSubscription?[this.reasoningTextSubscription.deliver]:[]:this.listeners;for(const e of h)try{e(o)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}createTurnReasoningDisplay(e){const t=this.providerReasoning,r=this.reasoningSummaryProjector;if(void 0===t&&!r)return;const s=this.reasoningTextSubscription;if("text"===t&&!s)return;const n="text"===t?"reasoning.text.delta":"reasoning.summary.delta",i=()=>"text"!==t||s===this.reasoningTextSubscription;return new O(e=>{if(i())return void 0===t?r?.(e.delta):e.presentation===t?e.delta:void 0},(t,r)=>{i()&&this.emit(e.ownership,e.threadId,e.turnId,{type:n,reasoningId:t,delta:r})},void 0===t)}async driveTurn(e,t,r,s){const n={reasoning:s,streamId:void 0,text:"",terminalEvent:void 0},i=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let a;try{for await(const t of e.session.send(i))this.projectSessionEvent(e,n,t);a=n.terminalEvent??{type:"turn.completed"}}catch(e){n.reasoning?.reset(),a=n.terminalEvent??{type:"turn.failed",stage:"execute",message:P(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==n.streamId&&this.projectSessionEvent(e,n,{type:"message-finish",text:n.text}),this.emitTurnTerminal(e,a)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{if(r.reasoning?.reset(),void 0!==r.streamId)return;const s=A.parse(t());return r.streamId=s,r.text="",void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??A.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{r.reasoning?.finish();const n=r.streamId??A.parse(t());return void 0===r.streamId&&this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.ownership,e.threadId,e.turnId,{type:"message.completed",streamId:n,text:P(r.text||s.text)}),r.streamId=void 0,void(r.text="")}case"reasoning-start":return void r.reasoning?.start(s.reasoningId);case"reasoning-delta":return void r.reasoning?.append(s);case"reasoning-end":return void r.reasoning?.end(s.reasoningId);case"tool-call":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:z(s.input,void 0)});case"tool-output-delta":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:P(s.delta)});case"tool-result":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:z(s.display,void 0)});case"confirmation-required":{const t=ee(e.turnId,s);return this.pendingApprovals.set(t.id,{ownership:e.ownership,threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:P(s.fullDiff)}),void this.emit(e.ownership,e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.reasoning?.reset(),r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:P(s.message)}:{type:"turn.failed",stage:"execute",message:P(e.interactionFailure)};break;case"error":r.reasoning?.reset(),r.terminalEvent??={type:"turn.failed",stage:s.stage,message:P(s.message)};break;case"context-recovery":this.emit(e.ownership,e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state});break;case"model-step-start":case"model-recovery":r.reasoning?.reset();break;case"step-finish":r.reasoning?.finish()}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:P(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:P(t.reason)}:t;this.emit(e.ownership,e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.ownership,e.threadId,e.turnId,t)}}
@@ -1,3 +1,5 @@
1
+ import { type SessionClaim } from "../ownership/session-claims.ts";
2
+ import type { SessionOwnershipHost } from "../host-contracts/session-ownership.ts";
1
3
  import { type ContextRecoveryNote, type ContextRecoveryState, type SemanticLedgerRow } from "./semantic-ledger.ts";
2
4
  import { type ModelMessage, type UserModelMessage } from "ai";
3
5
  import { type AppOutputResult, type AppOutputDescriptor, type RuntimeDurableEventV13, type RuntimeDurableEventV16, type RuntimeEventCursor, type RuntimeEventId } from "@roll-agent/protocol";
@@ -7,6 +9,7 @@ import { type SemanticArchiveItem } from "../engine/compaction-semantic-state.ts
7
9
  import { type ThreadOrigin, type ScheduledThreadOrigin, type ThreadDerivedFrom } from "./thread-origin.ts";
8
10
  import { type AcceptedTurnInput, type TurnInputSkillCheckpoint } from "./turn-input.ts";
9
11
  import type { ExplicitSkillCheckpointV1 } from "../engine/explicit-skill-context.ts";
12
+ export declare const THREAD_STORE_SCHEMA_VERSION = 11;
10
13
  export declare const TOOL_EXECUTION_RETENTION_POLICY: {
11
14
  readonly maxBytesPerThread: number;
12
15
  readonly maxRecordsPerThread: 2000;
@@ -199,11 +202,14 @@ export declare function defaultThreadsDir(): string;
199
202
  export interface ThreadStoreOptions {
200
203
  readonly now?: () => Date;
201
204
  readonly readOnly?: boolean;
205
+ readonly ownership?: SessionOwnershipHost;
202
206
  }
203
207
  export declare class ThreadStore {
204
208
  private readonly db;
205
209
  private readonly now;
206
210
  private readonly readOnly;
211
+ private readonly claims;
212
+ private readonly sessionReservations;
207
213
  constructor(dir?: string, options?: ThreadStoreOptions);
208
214
  private init;
209
215
  private migrateToolExecutionPersistenceInTransaction;
@@ -212,21 +218,30 @@ export declare class ThreadStore {
212
218
  private initializeMissingRuntimeEventStatesInTransaction;
213
219
  private enforceAllRuntimeEventRetentionInTransaction;
214
220
  createThread(input?: CreateThreadInput): string;
221
+ private createThreadOwned;
215
222
  hasThread(id: string): boolean;
216
223
  getThread(id: string): ThreadRecord | undefined;
217
224
  listThreads(options?: ListThreadsOptions): ThreadRecord[];
218
225
  /** Backfill only legacy rows; an explicit source is immutable, including interactive. */
219
226
  backfillScheduledOrigin(threadId: string, origin: ScheduledThreadOrigin): boolean;
227
+ private backfillScheduledOriginOwned;
220
228
  /** A single SQLite read transaction pins every part of the committed snapshot. */
221
229
  readSnapshot(threadId: string): ThreadSnapshot;
222
230
  /** Copies durable evidence atomically; Runtime events and live authority are never copied. */
223
231
  forkSnapshot(snapshot: ThreadSnapshot, input?: ForkThreadSnapshotInput): string;
232
+ private forkSnapshotOwned;
224
233
  countMessages(threadId: string): number;
225
234
  countTranscriptMessages(threadId: string): number;
226
235
  updateTitle(threadId: string, title: string): void;
236
+ private updateTitleOwned;
227
237
  updateModel(threadId: string, model: string): void;
228
- deleteThread(threadId: string): void;
238
+ private updateModelOwned;
239
+ deleteThread(threadId: string, options?: {
240
+ readonly onlyIfEmpty?: boolean;
241
+ }): void;
242
+ private deleteThreadOwned;
229
243
  appendRuntimeEvent(input: AppendRuntimeEventInput): StoredRuntimeEvent;
244
+ private appendRuntimeEventOwned;
230
245
  getRuntimeEventCursor(threadId: string): RuntimeEventCursor | null;
231
246
  resumeRuntimeEvents(threadId: string, afterCursor: RuntimeEventCursor | null): ResumeRuntimeEventsResult;
232
247
  countRuntimeEvents(threadId: string): number;
@@ -234,17 +249,23 @@ export declare class ThreadStore {
234
249
  private enforceRuntimeEventRetentionInTransaction;
235
250
  /** Internal acceptance boundary; the same receipt cannot reinsert compacted active history. */
236
251
  acceptTurnInput(threadId: string, inputId: string, message: UserModelMessage): AcceptedTurnInput;
252
+ private acceptTurnInputOwned;
237
253
  getTurnInput(threadId: string, inputId: string): AcceptedTurnInput | undefined;
238
254
  listTurnInputs(threadId: string): readonly AcceptedTurnInput[];
239
255
  getTurnInputSkillCheckpoint(threadId: string, sequence: number): TurnInputSkillCheckpoint | undefined;
240
256
  recordTurnSkillCheckpoint(threadId: string, inputId: string, checkpoint: ExplicitSkillCheckpointV1): void;
257
+ private recordTurnSkillCheckpointOwned;
241
258
  appendMessages(threadId: string, messages: readonly ModelMessage[], options?: AppendMessagesOptions): void;
259
+ private appendMessagesOwned;
242
260
  recoverUncoveredToolExecutions(threadId: string, createMessage: (records: readonly SequencedToolExecutionRecord[]) => ModelMessage): ModelMessage | undefined;
261
+ private recoverUncoveredToolExecutionsOwned;
243
262
  private appendMessagesInTransaction;
244
263
  replaceMessages(threadId: string, messages: readonly ModelMessage[]): void;
264
+ private replaceMessagesOwned;
245
265
  getMessages(threadId: string): ModelMessage[];
246
266
  private replaceMessagesInTransaction;
247
267
  appendToolExecution(threadId: string, record: ToolExecutionRecord): number;
268
+ private appendToolExecutionOwned;
248
269
  private readAppOutputRow;
249
270
  private insertAppOutput;
250
271
  private enforceAppOutputRetention;
@@ -266,6 +287,7 @@ export declare class ThreadStore {
266
287
  getCheckpoint(threadId: string, checkpointId: string): CompactionCheckpoint | undefined;
267
288
  loadSessionState(threadId: string): ThreadSessionState;
268
289
  commitCompaction(threadId: string, input: CommitCompactionInput): CompactionCheckpointV3;
290
+ private commitCompactionOwned;
269
291
  private legacySummaryFragmentsToArchive;
270
292
  getContextRecovery(threadId: string): ContextRecoveryState | undefined;
271
293
  /** Historical ledger access; new task summaries do not add semantic facts. */
@@ -286,5 +308,33 @@ export declare class ThreadStore {
286
308
  private withCurrentTranscriptCompleteness;
287
309
  private validateTranscriptRange;
288
310
  private validateRecentPage;
311
+ /** A retained claim spans idle gaps. Its exact token must fence delayed callbacks. */
312
+ retainSession(threadId: string): SessionClaim | undefined;
313
+ retainThread(threadId: string): SessionClaim | undefined;
314
+ private withThreadWrite;
289
315
  close(): void;
290
316
  }
317
+ export declare const THREAD_LIBRARY_STATUSES: readonly ["new", "current", "needs_migration", "unsupported"];
318
+ export interface ThreadLibraryPreflight {
319
+ readonly databasePath: string;
320
+ readonly physicalIdentity?: string;
321
+ readonly writableAliasSafe?: boolean;
322
+ readonly schemaVersion: number | null;
323
+ readonly supportedSchemaVersion: number;
324
+ readonly status: (typeof THREAD_LIBRARY_STATUSES)[number];
325
+ }
326
+ export declare class ThreadLibraryMigrationRequiredError extends Error {
327
+ readonly preflight: ThreadLibraryPreflight;
328
+ constructor(preflight: ThreadLibraryPreflight);
329
+ }
330
+ /** Read-only preflight: does not create a directory, database, claim, or run migrations. */
331
+ export declare function inspectThreadLibrary(dir?: string): ThreadLibraryPreflight;
332
+ /** Call only after explicit user choice and stopping old clients (which do not honor claims). */
333
+ export declare function migrateThreadLibrary(input: {
334
+ readonly dir: string;
335
+ readonly ownership: SessionOwnershipHost;
336
+ readonly confirmStoppedLegacyClients: true;
337
+ }): {
338
+ readonly backupPath: string;
339
+ readonly preflight: ThreadLibraryPreflight;
340
+ };
@@ -1 +1 @@
1
- import{SemanticLedger as e,SEMANTIC_LEDGER_SQL as t,contextRecoveryNoteSchema as n}from"./semantic-ledger.js";import{chmodSync as r,existsSync as s,mkdirSync as o}from"node:fs";import{homedir as i}from"node:os";import{resolve as a}from"node:path";import{randomUUID as c}from"node:crypto";import{DatabaseSync as d}from"node:sqlite";import{modelMessageSchema as E}from"ai";import{APP_OUTPUT_LIMITS as _,appOutputResultSchema as u,appOutputDescriptorSchema as p,describeAppOutput as h,RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as T,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as l,runtimeDurableEventV16Schema as N,runtimeEventCursorSchema as m,runtimeEventIdSchema as R,timestampSchema as S}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as g,prepareToolExecutionRecordForPersistence as O,prepareAppOutputForPersistence as I,redactSecretText as L}from"../tool-bridge/tool-execution-record.js";import{TASK_SUMMARY_CHECKPOINT_VERSION as v,COMPACTION_TRANSCRIPT_COMPLETENESS as x,TRANSCRIPT_MESSAGE_PROVENANCES as A,UnsupportedCompactionCheckpointVersionError as C,archivedTranscriptMessageSchema as f,createSummaryCheckpoint as b,buildCompactionToolState as q,parseCompactionCheckpoint as M}from"../engine/compaction-checkpoint.js";import{listSemanticArchiveItems as w,semanticArchiveItemSchema as y}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as D,readCompactionSummaryPayload as U}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as F,repairActiveToolProtocol as k}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as j}from"../engine/explicit-skill-context.js";import{threadOriginSchema as X,threadDerivedFromSchema as W,scheduledThreadOriginSchema as H}from"./thread-origin.js";import{acceptedTurnInputSchema as B,turnInputIdSchema as P,turnInputSkillCheckpointSchema as G}from"./turn-input.js";const Y=11,K=100,J=500,$=50,V=500,z=100,Q=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:T,maxRecordsPerThread:l,maxAgeMs:2592e6};export class RuntimeEventCursorExpiredError extends Error{cursor;constructor(e){super("Runtime event cursor is older than the retained event prefix"),this.name="RuntimeEventCursorExpiredError",this.cursor=e}}export class RuntimeEventCursorGapError extends Error{cursor;constructor(e){super("Runtime event cursor does not belong to the current Thread event log"),this.name="RuntimeEventCursorGapError",this.cursor=e}}export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution","legacy_context"];export const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS={rawTranscript:"raw_transcript",recoveryEvidence:"recovery_evidence"};const Z="legacy_assumed";export function expandTilde(e){return e.startsWith("~/")?a(i(),e.slice(2)):e}export function defaultThreadsDir(){return a(i(),".roll-agent","threads")}function ee(e){return e.prepare("PRAGMA foreign_key_list(messages)").all().some(e=>"threads"===e.table&&"thread_id"===e.from&&"id"===e.to&&"CASCADE"===e.on_delete.toUpperCase())}function te(e){if(Number.isInteger(e.schema_version)&&e.schema_version>v)throw new C(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=M(JSON.parse(e.checkpoint_json));if(t.version!==e.schema_version)throw new Error(`Compaction checkpoint schema version mismatch: row v${String(e.schema_version)}, payload v${String(t.version)}`);return t}function ne(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:X.parse(e.origin_json?JSON.parse(e.origin_json):{kind:"interactive"}),...e.derived_from_json?{derivedFrom:W.parse(JSON.parse(e.derived_from_json))}:{}}}function re(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>re(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,re(n,t)])):e}function se(){return R.parse(c())}function oe(e,t,n){return m.parse(`rte1:${e}:${String(t)}:${n}`)}function ie(e){const t=m.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=R.safeParse(n[3]);return void 0===n[1]||!Number.isSafeInteger(r)||r<0||!s.success?void 0:{eventLogId:n[1],threadSequence:r,eventId:s.data}}function ae(e){return new Date(Date.parse(S.parse(e))).toISOString()}function ce(e,t,n){const r=R.parse(n.event_id),s=N.parse(JSON.parse(n.event_json));return{eventId:r,cursor:oe(e,n.thread_sequence,r),threadSequence:n.thread_sequence,threadId:t,...null===n.turn_id?{}:{turnId:n.turn_id},timestamp:n.event_timestamp,event:"tool.completed"===s.type&&null!=n.app_output_json?{...s,appOutput:p.parse(JSON.parse(n.app_output_json))}:s}}export class ThreadStore{db;now;readOnly;constructor(e=defaultThreadsDir(),t={}){this.now=t.now??(()=>new Date),this.readOnly=t.readOnly??!1;const n=expandTilde(e);if(t.readOnly){this.db=new d(a(n,"threads.db"),{readOnly:!0});try{this.db.exec(`PRAGMA busy_timeout = ${String(Q)}; PRAGMA query_only = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>Y)throw new Error(`Unsupported ThreadStore schema v${String(e.user_version)}`)}catch(e){throw this.db.close(),e}return}s(n)||o(n,{recursive:!0,mode:448}),"win32"!==process.platform&&r(n,448);const i=a(n,"threads.db");if(this.db=new d(i),"win32"!==process.platform)try{r(i,384)}catch(e){throw this.db.close(),e}this.init()}init(){this.db.exec(`PRAGMA busy_timeout = ${String(Q)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const n=this.db.prepare("PRAGMA user_version").get();if(n.user_version>Y)throw new Error(`ThreadStore schema v${String(n.user_version)} 高于当前支持的 v${String(Y)}`);this.db.exec("BEGIN IMMEDIATE");try{if(this.db.exec("CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );"),n.user_version<7){const e=this.db.prepare("PRAGMA table_info(threads)").all();for(const t of["origin_json","derived_from_json"])e.some(e=>e.name===t)||this.db.exec(`ALTER TABLE threads ADD COLUMN ${t} TEXT`)}if(ee(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS operation_app_outputs (\n thread_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n result_json TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n byte_length INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, operation_id),\n FOREIGN KEY (thread_id, operation_id) REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_turn_inputs (\n thread_id TEXT NOT NULL,\n input_id TEXT NOT NULL,\n transcript_sequence INTEGER NOT NULL,\n skill_checkpoint_json TEXT,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, input_id),\n UNIQUE (thread_id, transcript_sequence),\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS tool_execution_context_coverage (\n thread_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n representation TEXT NOT NULL\n CHECK (representation IN ('raw_transcript', 'recovery_evidence', 'legacy_assumed')),\n transcript_sequence INTEGER,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, execution_id),\n CHECK (\n (representation = 'legacy_assumed' AND transcript_sequence IS NULL)\n OR (representation <> 'legacy_assumed' AND transcript_sequence IS NOT NULL)\n ),\n FOREIGN KEY (thread_id, execution_id)\n REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE,\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);\n CREATE TABLE IF NOT EXISTS thread_runtime_event_state (\n thread_id TEXT PRIMARY KEY,\n event_log_id TEXT NOT NULL UNIQUE,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n retained_from_sequence INTEGER NOT NULL\n CHECK (retained_from_sequence >= 0 AND retained_from_sequence <= next_sequence),\n retained_predecessor_event_id TEXT,\n latest_event_id TEXT,\n retained_count INTEGER NOT NULL CHECK (retained_count >= 0),\n retained_bytes INTEGER NOT NULL CHECK (retained_bytes >= 0),\n CHECK (\n (next_sequence = 0\n AND retained_from_sequence = 0\n AND retained_predecessor_event_id IS NULL\n AND latest_event_id IS NULL)\n OR (next_sequence > 0 AND latest_event_id IS NOT NULL)\n ),\n CHECK (\n (retained_from_sequence = 0 AND retained_predecessor_event_id IS NULL)\n OR (retained_from_sequence > 0 AND retained_predecessor_event_id IS NOT NULL)\n ),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS runtime_events (\n thread_id TEXT NOT NULL,\n thread_sequence INTEGER NOT NULL CHECK (thread_sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n turn_id TEXT,\n event_timestamp TEXT NOT NULL,\n event_json TEXT NOT NULL,\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, thread_sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_runtime_events_thread_created\n ON runtime_events(thread_id, created_at, thread_sequence);"),this.db.prepare("PRAGMA table_info(runtime_events)").all().some(e=>"app_output_json"===e.name)||this.db.exec("ALTER TABLE runtime_events ADD COLUMN app_output_json TEXT"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),n.user_version<3&&this.db.exec("INSERT OR IGNORE INTO thread_transcript_state (thread_id, completeness)\n SELECT id, 'legacy_snapshot' FROM threads;\n INSERT OR IGNORE INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n SELECT thread_id, idx, role, content_json, 'legacy_snapshot', created_at\n FROM messages;"),n.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),n.user_version<6&&this.db.prepare("INSERT OR IGNORE INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, NULL, created_at\n FROM tool_executions").run(Z),this.db.exec(t),n.user_version<9){const t=new e(this.db),n=this.db.prepare("SELECT thread_id, schema_version, checkpoint_json FROM compaction_checkpoints ORDER BY generation").all();for(const e of n){if("string"!=typeof e.thread_id||"string"!=typeof e.checkpoint_json)continue;let n;try{n=te({schema_version:Number(e.schema_version),checkpoint_json:e.checkpoint_json})}catch(t){if(t instanceof C)throw t;this.db.prepare("UPDATE thread_transcript_state SET completeness = 'legacy_snapshot' WHERE thread_id = ?").run(e.thread_id);continue}if(2!==n.version)continue;const r=this.getLatestCheckpoint(e.thread_id);t.append(e.thread_id,n.generation,2===n.version?n.semanticState.scopeId??n.semanticState.goal?.id??"initial":"legacy",n.transcript.toolExecutions.throughSequence,w(n.semanticState),r?.id!==n.id)}}this.initializeMissingRuntimeEventStatesInTransaction();const r=this.now().toISOString();this.enforceAllToolExecutionRetentionInTransaction(r),this.enforceAllRuntimeEventRetentionInTransaction(r),this.db.exec(`PRAGMA user_version = ${String(Y)};`),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}migrateToolExecutionPersistenceInTransaction(){const e=this.db.prepare("SELECT rowid AS storage_rowid, record_json\n FROM tool_executions\n WHERE rowid > ?\n ORDER BY rowid ASC\n LIMIT 100"),t=this.db.prepare("UPDATE tool_executions\n SET tool_call_id = ?, agent_name = ?, tool_name = ?, record_json = ?, created_at = ?\n WHERE rowid = ?");let n=0;for(;;){const r=e.all(n);if(0===r.length)break;for(const e of r){const r=g(JSON.parse(e.record_json));t.run(r.toolCallId,r.agentName,r.toolName,JSON.stringify(r),r.createdAt,e.storage_rowid),n=e.storage_rowid}}}enforceAllToolExecutionRetentionInTransaction(e){const t=this.db.prepare("SELECT DISTINCT thread_id AS id FROM tool_executions").all();for(const{id:n}of t)this.enforceAppOutputRetention(n),this.enforceToolExecutionRetentionInTransaction(n,e)}migrateExplicitSkillCheckpointPersistenceInTransaction(){const e=[{table:"messages",column:"content_json"},{table:"transcript_messages",column:"message_json"}];for(const t of e){const e=this.db.prepare(`SELECT rowid AS storage_rowid, ${t.column} AS message_json\n FROM ${t.table}\n WHERE ${t.column} LIKE '%"modelUserContent"%'`).all(),n=this.db.prepare(`UPDATE ${t.table} SET ${t.column} = ? WHERE rowid = ?`);for(const t of e){const e=E.parse(JSON.parse(t.message_json)),r=j(e);n.run(JSON.stringify(r),t.storage_rowid)}}}initializeMissingRuntimeEventStatesInTransaction(){const e=this.db.prepare("SELECT threads.id\n FROM threads\n LEFT JOIN thread_runtime_event_state\n ON thread_runtime_event_state.thread_id = threads.id\n WHERE thread_runtime_event_state.thread_id IS NULL").all(),t=this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)");for(const n of e)t.run(n.id,c())}enforceAllRuntimeEventRetentionInTransaction(e){const t=this.db.prepare("SELECT thread_id AS id FROM thread_runtime_event_state").all();for(const n of t)this.enforceRuntimeEventRetentionInTransaction(n.id,e)}createThread(e={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const t=e.id??c(),n=this.now().toISOString(),r=X.parse(e.origin??{kind:"interactive"}),s=void 0===e.derivedFrom?void 0:W.parse(e.derivedFrom);this.db.exec("SAVEPOINT create_thread");try{return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at, origin_json, derived_from_json) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,n,n,JSON.stringify(r),void 0===s?null:JSON.stringify(s)),this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness) VALUES (?, ?)").run(t,x[0]),this.db.prepare("INSERT INTO thread_tool_execution_state (thread_id, next_sequence) VALUES (?, 0)").run(t),this.db.prepare("INSERT INTO thread_runtime_event_state\n (thread_id, event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id, retained_count, retained_bytes)\n VALUES (?, ?, 0, 0, NULL, NULL, 0, 0)").run(t,c()),this.db.exec("RELEASE create_thread"),t}catch(e){throw this.db.exec("ROLLBACK TO create_thread; RELEASE create_thread"),e}}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?ne(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(ne).filter(t=>void 0===e.origin||t.origin.kind===e.origin)}backfillScheduledOrigin(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=H.parse(t);return this.db.prepare("UPDATE threads SET origin_json = ? WHERE id = ? AND origin_json IS NULL").run(JSON.stringify(n),e).changes>0}readSnapshot(t){this.db.exec("BEGIN");try{const n=this.getThread(t);if(void 0===n)throw new Error(`Thread "${t}" 不存在`);const r=this.now().toISOString(),s=this.getMessages(t),o=e=>void 0!==this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e),i=o("transcript_messages")?this.db.prepare("SELECT sequence, provenance, created_at, message_json FROM transcript_messages WHERE thread_id = ? ORDER BY sequence").all(t).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):s.map((e,t)=>f.parse({sequence:t,message:e,provenance:"legacy_snapshot",createdAt:n.createdAt})),a=o("tool_executions")?this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? ORDER BY sequence").all(t).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence})):[],c=o("compaction_checkpoints")?this.db.prepare("SELECT schema_version, checkpoint_json FROM compaction_checkpoints WHERE thread_id = ? ORDER BY generation").all(t).map(te):[],d=o("tool_execution_context_coverage")?this.db.prepare("SELECT execution_id AS executionId, representation, transcript_sequence AS transcriptSequence, created_at AS createdAt FROM tool_execution_context_coverage WHERE thread_id = ?").all(t):a.map(e=>({executionId:e.id,representation:"legacy_assumed",transcriptSequence:null,createdAt:r})),E=o("thread_tool_execution_state")?this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(t):void 0,_=o("compaction_recovery")?new e(this.db).recovery(t):void 0,u={...o("compaction_semantic_items")?{semanticLedger:new e(this.db).versions(t)}:{},..._?{contextRecovery:_}:{},...o("compaction_recovery_notes")?{contextRecoveryNotes:new e(this.db).notes(t)}:{},thread:n,capturedAt:r,appOutputs:o("operation_app_outputs")?this.db.prepare("SELECT operation_id, result_json, expires_at FROM operation_app_outputs WHERE thread_id = ?").all(t).map(e=>({operationId:e.operation_id,result:this.readAppOutputRow(e.result_json,e.expires_at),expiresAt:e.expires_at})):[],messages:s,transcript:i,turnInputs:o("thread_turn_inputs")?this.listTurnInputs(t):[],toolExecutions:a,checkpoints:c,toolExecutionCoverage:d,nextToolExecutionSequence:E?.next_sequence??(a.at(-1)?.sequence??-1)+1,transcriptCompleteness:o("thread_transcript_state")?this.getTranscriptCompleteness(t):"legacy_snapshot"};return this.db.exec("COMMIT"),u}catch(e){throw this.db.exec("ROLLBACK"),e}}forkSnapshot(t,r={}){if(this.readOnly)throw new Error("ThreadStore is read-only");const s=new Map(t.checkpoints.map(e=>[e.id,c()])),o=t.checkpoints.map(e=>{const t=M(re(e,s));return t.version===v?t:M({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),i=t.messages.map(e=>E.parse(re(e,s))),a=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const d=this.createThread({...void 0===r.id?{}:{id:r.id},...void 0===r.title?void 0===t.thread.title?{}:{title:t.thread.title}:{title:r.title},...void 0===r.model?{}:{model:r.model},origin:{kind:"interactive"},derivedFrom:{threadId:t.thread.id,origin:t.thread.origin,capturedAt:t.capturedAt}});this.replaceMessagesInTransaction(d,k(i).messages,a);const E=this.db.prepare("INSERT INTO transcript_messages (thread_id, sequence, role, message_json, provenance, created_at) VALUES (?, ?, ?, ?, ?, ?)");for(const e of t.transcript){const t=f.parse(e);E.run(d,t.sequence,t.message.role,JSON.stringify(t.message),t.provenance,t.createdAt)}const _=this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, skill_checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?)");for(const e of t.turnInputs??[]){const t=B.parse(e);_.run(d,c(),t.transcriptSequence,void 0===t.skillCheckpoint?null:JSON.stringify(t.skillCheckpoint),t.createdAt)}this.db.prepare("UPDATE thread_transcript_state SET completeness = ? WHERE thread_id = ?").run(t.transcriptCompleteness,d);const u=this.db.prepare("INSERT INTO tool_executions (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");for(const{sequence:e,...n}of t.toolExecutions){const t=g(n);u.run(d,e,t.id,t.toolCallId,t.agentName,t.toolName,JSON.stringify(t),t.createdAt)}for(const e of t.appOutputs??[])this.insertAppOutput(d,e.operationId,e.result,e.expiresAt);this.enforceAppOutputRetention(d),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(t.nextToolExecutionSequence,d);const p=this.db.prepare("INSERT INTO tool_execution_context_coverage (thread_id, execution_id, representation, transcript_sequence, created_at) VALUES (?, ?, ?, ?, ?)");for(const e of t.toolExecutionCoverage)p.run(d,e.executionId,e.representation,e.transcriptSequence,e.createdAt);const h=this.db.prepare("INSERT INTO compaction_checkpoints (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence, tool_from_sequence, tool_through_sequence, checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const e of o)h.run(e.id,d,e.generation,e.version,e.transcript.messages.fromSequenceExclusive,e.transcript.messages.throughSequence,e.transcript.toolExecutions.fromSequenceExclusive,e.transcript.toolExecutions.throughSequence,JSON.stringify(e),e.createdAt);const T=new e(this.db);if(t.semanticLedger)T.restore(d,t.semanticLedger);else for(const e of o)2===e.version&&T.append(d,e.generation,2===e.version?e.semanticState.scopeId??e.semanticState.goal?.id??"initial":"legacy",e.transcript.toolExecutions.throughSequence,w(e.semanticState),e.id!==o.at(-1)?.id);if(t.contextRecovery){const e=s.get(t.contextRecovery.checkpointId);e&&this.db.prepare("INSERT INTO compaction_recovery\n (thread_id,checkpoint_id,required,unextracted,memo) VALUES (?,?,1,?,?)").run(d,e,Number(t.contextRecovery.unextracted),t.contextRecovery.memo)}for(const e of t.contextRecoveryNotes??[]){const t=n.parse(e),r=s.get(t.checkpointId);r&&this.db.prepare("INSERT INTO compaction_recovery_notes (thread_id,checkpoint_id,notes) VALUES (?,?,?)").run(d,r,t.notes)}return this.db.exec("COMMIT"),d}catch(e){throw this.db.exec("ROLLBACK"),e}}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}countTranscriptMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM transcript_messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}updateModel(e,t){this.db.prepare("UPDATE threads SET model = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){const t=N.parse(e.event),n="tool.completed"===t.type?(({appOutput:e,...t})=>t)(t):t,r=JSON.stringify(n),s=ae(e.timestamp),o=Buffer.byteLength(JSON.stringify(t),"utf8")+Buffer.byteLength(s,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(o>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread)throw new Error("Runtime event exceeds the per-Thread retained byte limit");this.db.exec("BEGIN IMMEDIATE");try{const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e.threadId);if(void 0===n)throw new Error(`Thread "${e.threadId}" 不存在或缺少 Runtime event state`);const i=se(),a=n.next_sequence;return this.db.prepare("INSERT INTO runtime_events\n (thread_id, thread_sequence, event_id, turn_id, event_timestamp,\n event_json, size_bytes, created_at, app_output_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(e.threadId,a,i,e.turnId??null,s,r,o,s,"tool.completed"===t.type?JSON.stringify(t.appOutput):null),this.db.prepare("UPDATE thread_runtime_event_state\n SET next_sequence = ?, latest_event_id = ?,\n retained_count = retained_count + 1,\n retained_bytes = retained_bytes + ?\n WHERE thread_id = ?").run(a+1,i,o,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,s),this.db.exec("COMMIT"),{eventId:i,cursor:oe(n.event_log_id,a,i),threadSequence:a,threadId:e.threadId,...void 0===e.turnId?{}:{turnId:e.turnId},timestamp:s,event:t}}catch(e){throw this.db.exec("ROLLBACK"),e}}getRuntimeEventCursor(e){const t=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===t)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);return 0===t.next_sequence||null===t.latest_event_id?null:oe(t.event_log_id,t.next_sequence-1,R.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:ie(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);this.db.exec("BEGIN IMMEDIATE");try{this.enforceRuntimeEventRetentionInTransaction(e,this.now().toISOString());const r=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===r)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);if(null===t&&r.retained_from_sequence>0)throw new RuntimeEventCursorExpiredError(t);if(null!==t&&void 0!==n&&this.validateRuntimeEventCursor(r,t,n,e),0===r.next_sequence||null===r.latest_event_id)return this.db.exec("COMMIT"),{events:[],throughCursor:null,replayedCount:0};const s=r.next_sequence-1,o=n?.threadSequence??-1,i=this.db.prepare("SELECT *\n FROM runtime_events\n WHERE thread_id = ?\n AND thread_sequence > ?\n AND thread_sequence <= ?\n ORDER BY thread_sequence ASC").all(e,o,s).map(t=>ce(r.event_log_id,e,t)),a=oe(r.event_log_id,s,R.parse(r.latest_event_id));return this.db.exec("COMMIT"),{events:i,throughCursor:a,replayedCount:i.length}}catch(e){throw e instanceof RuntimeEventCursorExpiredError||e instanceof RuntimeEventCursorGapError?this.db.exec("COMMIT"):this.db.exec("ROLLBACK"),e}}countRuntimeEvents(e){return this.db.prepare("SELECT COUNT(*) AS count FROM runtime_events WHERE thread_id = ?").get(e).count}validateRuntimeEventCursor(e,t,n,r){if(n.eventLogId!==e.event_log_id||n.threadSequence>=e.next_sequence)throw new RuntimeEventCursorGapError(t);const s=e.retained_from_sequence-1;if(n.threadSequence<s)throw new RuntimeEventCursorExpiredError(t);if(n.threadSequence===s){if(n.eventId!==e.retained_predecessor_event_id)throw new RuntimeEventCursorGapError(t);return}const o=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(r,n.threadSequence);if(void 0===o||o.event_id!==n.eventId)throw new RuntimeEventCursorGapError(t)}enforceRuntimeEventRetentionInTransaction(e,t){const n=this.db.prepare("SELECT event_log_id, next_sequence, retained_from_sequence,\n retained_predecessor_event_id, latest_event_id,\n retained_count, retained_bytes\n FROM thread_runtime_event_state\n WHERE thread_id = ?").get(e);if(void 0===n||0===n.retained_count)return;let r=n.retained_from_sequence;if(n.retained_count>RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread){const t=this.db.prepare("SELECT thread_sequence AS keep_from\n FROM runtime_events\n WHERE thread_id = ?\n ORDER BY thread_sequence DESC\n LIMIT 1 OFFSET ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxRecordsPerThread-1);r=Math.max(r,t?.keep_from??n.next_sequence)}if(n.retained_bytes>RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread){const t=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM (\n SELECT thread_sequence,\n SUM(size_bytes) OVER (\n ORDER BY thread_sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM runtime_events\n WHERE thread_id = ?\n )\n WHERE retained_bytes <= ?").get(e,RUNTIME_EVENT_RETENTION_POLICY.maxBytesPerThread);r=Math.max(r,t.keep_from??n.next_sequence)}const s=new Date(Date.parse(t)-RUNTIME_EVENT_RETENTION_POLICY.maxAgeMs).toISOString(),o=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from\n FROM runtime_events\n WHERE thread_id = ? AND created_at >= ?").get(e,s);if(r=Math.max(r,o.keep_from??n.next_sequence),r<=n.retained_from_sequence)return;const i=this.db.prepare("SELECT thread_sequence, event_id\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence = ?").get(e,r-1);if(void 0===i)throw new Error("Runtime event retention boundary is not a continuous retained prefix");const a=this.db.prepare("SELECT COUNT(*) AS removed_count, SUM(size_bytes) AS removed_bytes\n FROM runtime_events\n WHERE thread_id = ? AND thread_sequence < ?").get(e,r);this.db.prepare("DELETE FROM runtime_events WHERE thread_id = ? AND thread_sequence < ?").run(e,r),this.db.prepare("UPDATE thread_runtime_event_state\n SET retained_from_sequence = ?, retained_predecessor_event_id = ?,\n retained_count = retained_count - ?,\n retained_bytes = retained_bytes - ?\n WHERE thread_id = ?").run(r,i.event_id,a.removed_count,a.removed_bytes??0,e)}acceptTurnInput(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=P.parse(t),s=j(E.parse(n));if("user"!==s.role)throw new Error("Accepted turn input must be a user message");this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=this.getTurnInput(e,r);if(void 0!==t){const n=this.db.prepare("SELECT message_json FROM transcript_messages WHERE thread_id = ? AND sequence = ?").get(e,t.transcriptSequence);if(n?.message_json!==JSON.stringify(s))throw new Error("Turn input identity conflict");return this.db.exec("COMMIT"),t}const n=this.now().toISOString();this.appendMessagesInTransaction(e,[s],n,void 0);const o=this.db.prepare("SELECT MAX(sequence) AS sequence FROM transcript_messages WHERE thread_id = ?").get(e),i=B.parse({inputId:r,transcriptSequence:o?.sequence,createdAt:n});return this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, created_at) VALUES (?, ?, ?, ?)").run(e,r,i.transcriptSequence,n),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}getTurnInput(e,t){const n=this.db.prepare("SELECT input_id, transcript_sequence, skill_checkpoint_json, created_at FROM thread_turn_inputs WHERE thread_id = ? AND input_id = ?").get(e,P.parse(t));return void 0===n?void 0:B.parse({inputId:n.input_id,transcriptSequence:n.transcript_sequence,createdAt:n.created_at,..."string"==typeof n.skill_checkpoint_json?{skillCheckpoint:JSON.parse(n.skill_checkpoint_json)}:{}})}listTurnInputs(e){return this.db.prepare("SELECT input_id FROM thread_turn_inputs WHERE thread_id = ? ORDER BY transcript_sequence").all(e).map(t=>{const n=this.getTurnInput(e,String(t.input_id));if(void 0===n)throw new Error("Turn input receipt disappeared");return n})}getTurnInputSkillCheckpoint(e,t){const n=this.db.prepare("SELECT skill_checkpoint_json FROM thread_turn_inputs WHERE thread_id = ? AND transcript_sequence = ?").get(e,t);return"string"==typeof n?.skill_checkpoint_json?G.parse(JSON.parse(n.skill_checkpoint_json)):void 0}recordTurnSkillCheckpoint(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=G.parse(n),s=JSON.stringify(r);this.db.exec("BEGIN IMMEDIATE");try{const n=this.getTurnInput(e,t);if(void 0===n)throw new Error("Turn input receipt not found");if(void 0!==n.skillCheckpoint&&JSON.stringify(n.skillCheckpoint)!==s)throw new Error("Turn input Skill snapshot conflict");this.db.prepare("UPDATE thread_turn_inputs SET skill_checkpoint_json = ? WHERE thread_id = ? AND input_id = ?").run(s,e,t),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessages(e,t,n={}){const r=n.toolExecutionCoverage;if(0===t.length){if(void 0!==r&&r.executionIds.length>0)throw new Error("Tool execution coverage 必须与至少一条 transcript message 原子写入");return}if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);if(void 0!==r&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.rawTranscript&&r.representation!==TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence)throw new Error("Tool execution coverage representation 无效");const s=r?.executionIds??[],o=new Set;for(const e of s){if("string"!=typeof e||0===e.length)throw new Error("Tool execution coverage execution id 无效");if(o.has(e))throw new Error(`Tool execution coverage execution id 重复: ${e}`);o.add(e)}const i=t.map(e=>j(E.parse(e))),a=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.appendMessagesInTransaction(e,i,a,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}recoverUncoveredToolExecutions(e,t){this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=this.uncoveredToolExecutionRecords(e,{afterSequence:-1,throughSequence:Number.MAX_SAFE_INTEGER,toolCallId:void 0,limit:Number.MAX_SAFE_INTEGER});if(0===n.length)return void this.db.exec("COMMIT");const r=j(E.parse(t(n))),s={executionIds:n.map(e=>e.id),representation:TOOL_EXECUTION_CONTEXT_REPRESENTATIONS.recoveryEvidence};return this.appendMessagesInTransaction(e,[r],(new Date).toISOString(),s),this.db.exec("COMMIT"),r}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessagesInTransaction(e,t,n,r){const s=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)"),o=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)"),i=this.db.prepare("INSERT INTO tool_execution_context_coverage\n (thread_id, execution_id, representation, transcript_sequence, created_at)\n SELECT thread_id, id, ?, ?, ?\n FROM tool_executions\n WHERE thread_id = ?\n AND id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE thread_id = ? AND execution_id = ?\n )"),a=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e),c=this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e);let d=a.maxIdx+1,E=c.maxSequence+1;for(const r of t){const t=JSON.stringify(r);s.run(e,d,r.role,t,n),o.run(e,E,r.role,t,A[0],n),d+=1,E+=1}const _=E-1;if(void 0!==r){for(const t of r.executionIds){const s=i.run(r.representation,_,n,e,t,e,t);if(1!==Number(s.changes))throw new Error(`Tool execution "${t}" 不属于 Thread "${e}" 或已被 transcript 覆盖`)}this.enforceToolExecutionRetentionInTransaction(e,n)}this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>E.parse(e)),r=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.replaceMessagesInTransaction(e,n,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>E.parse(JSON.parse(e.content_json)))}replaceMessagesInTransaction(e,t,n){const r=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,s)=>{const o=j(t);r.run(e,s,o.role,JSON.stringify(o),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=O(t),r=JSON.stringify(n),s=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const o=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===o)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const i=o.next_sequence;return this.db.prepare("INSERT INTO tool_executions\n (thread_id, sequence, id, tool_call_id, agent_name, tool_name, record_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,i,n.id,n.toolCallId,n.agentName,n.toolName,r,n.createdAt),this.db.prepare("UPDATE thread_tool_execution_state SET next_sequence = ? WHERE thread_id = ?").run(i+1,e),void 0!==t.appOutput&&this.insertAppOutput(e,t.id,t.appOutput,new Date(Date.parse(s)+_.maxAgeMs).toISOString()),this.enforceAppOutputRetention(e),this.enforceToolExecutionRetentionInTransaction(e,s),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(s,e),this.db.exec("COMMIT"),i}catch(e){throw this.db.exec("ROLLBACK"),e}}readAppOutputRow(e,t){const n=u.parse(JSON.parse(e));return"available"===n.status&&Date.parse(t)<=this.now().getTime()?{status:"expired"}:n}insertAppOutput(e,t,n,r){const s="available"===n.status&&Date.parse(r)<=this.now().getTime()?{status:"expired"}:I(n),o=JSON.stringify(s);this.db.prepare("INSERT INTO operation_app_outputs\n (thread_id, operation_id, result_json, descriptor_json, byte_length, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e,t,o,JSON.stringify(h(s)),"available"===s.status?Buffer.byteLength(o,"utf8"):0,r)}enforceAppOutputRetention(e){const t=JSON.stringify({status:"expired"});this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND byte_length > 0 AND expires_at <= ?").run(t,t,e,this.now().toISOString()),this.db.prepare("UPDATE operation_app_outputs SET result_json = ?, descriptor_json = ?, byte_length = 0\n WHERE thread_id = ? AND operation_id IN (\n SELECT operation_id FROM (\n SELECT o.operation_id,\n ROW_NUMBER() OVER (ORDER BY e.sequence DESC) AS rank,\n SUM(o.byte_length) OVER (ORDER BY e.sequence DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS bytes\n FROM operation_app_outputs o JOIN tool_executions e ON e.thread_id = o.thread_id AND e.id = o.operation_id\n WHERE o.thread_id = ? AND o.byte_length > 0\n ) WHERE rank > ? OR bytes > ?\n )").run(t,t,e,e,_.threadRecords,_.threadBytes)}getAppOutputDescriptor(e,t){if(void 0===this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'operation_app_outputs'").get())return void 0===this.db.prepare("SELECT 1 FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t)?void 0:{status:"not_provided"};const n=this.db.prepare("SELECT o.descriptor_json, o.expires_at FROM tool_executions e\n LEFT JOIN operation_app_outputs o ON o.thread_id = e.thread_id AND o.operation_id = e.id\n WHERE e.thread_id = ? AND e.id = ?").get(e,t);if(void 0===n)return;if(null===n.descriptor_json)return{status:"not_provided"};const r=p.parse(JSON.parse(n.descriptor_json));return"available"===r.status&&null!==n.expires_at&&Date.parse(n.expires_at)<=this.now().getTime()?{status:"expired"}:r}getAppOutput(e,t){const n=this.getAppOutputDescriptor(e,t);if(void 0===n||"available"!==n.status)return n;const r=this.db.prepare("SELECT result_json FROM operation_app_outputs WHERE thread_id = ? AND operation_id = ?").get(e,t);return void 0===r?{status:"not_provided"}:u.parse(JSON.parse(r.result_json))}enforceToolExecutionRetentionInTransaction(e,t){const n=new Date(Date.parse(t)-TOOL_EXECUTION_RETENTION_POLICY.maxAgeMs).toISOString();let r=!1;const s=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND created_at < ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )").run(e,n);r=Number(s.changes)>0;const o=this.db.prepare("SELECT sequence\n FROM (\n SELECT sequence,\n ROW_NUMBER() OVER (ORDER BY sequence DESC) AS retained_rank,\n SUM(length(CAST(record_json AS BLOB))) OVER (\n ORDER BY sequence DESC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS retained_bytes\n FROM tool_executions\n WHERE thread_id = ?\n )\n WHERE retained_rank > ? OR retained_bytes > ?").all(e,TOOL_EXECUTION_RETENTION_POLICY.maxRecordsPerThread,TOOL_EXECUTION_RETENTION_POLICY.maxBytesPerThread);if(o.length>0){const t=this.db.prepare("DELETE FROM tool_executions\n WHERE thread_id = ?\n AND sequence = ?\n AND EXISTS (\n SELECT 1\n FROM tool_execution_context_coverage\n WHERE tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n )");for(const n of o){const s=t.run(e,n.sequence);r=Number(s.changes)>0||r}}r&&this.db.prepare("INSERT INTO thread_transcript_state (thread_id, completeness)\n VALUES (?, 'legacy_snapshot')\n ON CONFLICT(thread_id) DO UPDATE SET completeness = 'legacy_snapshot'").run(e)}listToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);if(r<=n)return[];return this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n AND (? IS NULL OR tool_call_id = ?)\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,t.toolCallId??null,t.toolCallId??null,s).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence}))}listUncoveredToolExecutions(e,t={}){const{afterSequence:n,throughSequence:r,limit:s}=this.resolveToolExecutionListRange(t);return r<=n?[]:this.uncoveredToolExecutionRecords(e,{afterSequence:n,throughSequence:r,toolCallId:t.toolCallId,limit:s})}resolveToolExecutionListRange(e){const t=e.afterSequence??-1,n=e.throughSequence??Number.MAX_SAFE_INTEGER,r=e.limit??K;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>J)throw new Error(`limit 必须是 1-${String(J)} 之间的整数`);if(!Number.isInteger(n)||n<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");return{afterSequence:t,throughSequence:n,limit:r}}uncoveredToolExecutionRecords(e,t){return this.db.prepare("SELECT tool_executions.sequence, tool_executions.record_json\n FROM tool_executions\n LEFT JOIN tool_execution_context_coverage\n ON tool_execution_context_coverage.thread_id = tool_executions.thread_id\n AND tool_execution_context_coverage.execution_id = tool_executions.id\n WHERE tool_executions.thread_id = ?\n AND tool_executions.sequence > ?\n AND tool_executions.sequence <= ?\n AND (? IS NULL OR tool_executions.tool_call_id = ?)\n AND tool_execution_context_coverage.execution_id IS NULL\n ORDER BY tool_executions.sequence ASC\n LIMIT ?").all(e,t.afterSequence,t.throughSequence,t.toolCallId??null,t.toolCallId??null,t.limit).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??K;this.validateRecentPage(n,r,J);const s=this.db.prepare("SELECT sequence, record_json\n FROM tool_executions\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>({...g(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getToolExecution(e,t){const n=this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? AND id = ?").get(e,t);if(n)return{...g(JSON.parse(n.record_json)),sequence:n.sequence}}getTranscriptCompleteness(e){const t=this.db.prepare("SELECT completeness FROM thread_transcript_state WHERE thread_id = ?").get(e);return t?.completeness===x[0]?x[0]:x[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??$;if(this.validateTranscriptRange(n,r,s,V),r<=n)return[];return this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence > ?\n AND sequence <= ?\n ORDER BY sequence ASC\n LIMIT ?").all(e,n,r,s).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)}))}listRecentTranscriptMessages(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??$;this.validateRecentPage(n,r,V);const s=this.db.prepare("SELECT sequence, provenance, created_at, message_json\n FROM transcript_messages\n WHERE thread_id = ?\n AND sequence < ?\n ORDER BY sequence DESC\n LIMIT ?").all(e,n,r+1),o=s.length>r,i=s.slice(0,r).map(e=>f.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:i,nextBeforeSequence:o?i[0]?.sequence:void 0}}getLatestCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{return this.withCurrentTranscriptCompleteness(e,te(n))}catch(e){if(e instanceof C)throw e}}getLatestSummaryCheckpoint(e){const t=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ?\n ORDER BY generation DESC").all(e);for(const n of t)try{const t=te(n);if(t.version===v||"valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof C)throw e}}getCheckpoint(e,t){const n=this.db.prepare("SELECT schema_version, checkpoint_json\n FROM compaction_checkpoints\n WHERE thread_id = ? AND id = ?").get(e,t);if(void 0!==n)try{return this.withCurrentTranscriptCompleteness(e,te(n))}catch(e){if(e instanceof C)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:k(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>E.parse(e)),r=t.expectedActiveMessages.map(e=>E.parse(e)),s=k(n);if(s.status!==F.valid){const e=[...new Set([...s.removedToolCallIds,...s.removedToolResultIds])].sort();throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const o=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=k(this.getMessages(e)).messages;if(JSON.stringify(s)!==JSON.stringify(r))throw new Error("Compaction active projection changed after the draft snapshot was captured");const i=this.getLatestCheckpoint(e);if(i?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const a=this.legacySummaryFragmentsToArchive(e,s,n),c=this.maxTranscriptMessageSequence(e),d=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration FROM compaction_checkpoints WHERE thread_id = ?").get(e),E={messages:{fromSequenceExclusive:i?.transcript.messages.throughSequence??-1,throughSequence:c+a.length},toolExecutions:{fromSequenceExclusive:i?.transcript.toolExecutions.throughSequence??-1,throughSequence:this.maxToolExecutionSequence(e)},completeness:this.getTranscriptCompleteness(e)},_=new Set(r.flatMap(e=>Array.isArray(e.content)?e.content.flatMap(e=>"tool-call"===e.type?[e.toolCallId]:[]):[])),u=this.listRecentToolExecutions(e,{limit:J}).entries.filter(e=>_.has(e.toolCallId)),p=b({summary:t.summary,generation:d.maxGeneration+1,transcript:E,...void 0===i?{}:{previousCheckpointId:i.id},toolState:q(r,u,32,E.completeness),createdAt:o});t.validateCheckpoint?.(p);for(const[t,n]of a.entries())this.db.prepare("INSERT INTO transcript_messages\n (thread_id,sequence,role,message_json,provenance,created_at) VALUES (?,?,?,?,?,?)").run(e,c+t+1,n.role,JSON.stringify(n),A[1],o);return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence, checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(p.id,e,p.generation,p.version,E.messages.fromSequenceExclusive,E.messages.throughSequence,E.toolExecutions.fromSequenceExclusive,E.toolExecutions.throughSequence,JSON.stringify(p),p.createdAt),this.replaceMessagesInTransaction(e,n,o),this.db.exec("COMMIT"),p}catch(e){throw this.db.exec("ROLLBACK"),e}}legacySummaryFragmentsToArchive(e,t,n){const r=e=>"string"==typeof e.content?U(e.content):void 0,s=new Set(n.map(r)),o=new Map,i=this.db.prepare("SELECT 1 FROM transcript_messages WHERE thread_id=? AND json_extract(message_json, '$.content') IN (?, ?) LIMIT 1");for(const n of t){const t=r(n);if(void 0===t||0===t.length||s.has(t))continue;const a=L(t),c=D(a);i.get(e,String(n.content),String(c.content))||o.set(a,c)}return[...o.values()]}getContextRecovery(t){if(this.getLatestCheckpoint(t)?.version===v)return;const n=new e(this.db),r=n.recovery(t);if(!r)return;const s=this.getCheckpoint(t,r.checkpointId);if(!s)throw new Error("Recovery checkpoint is missing");const o=2===s.version?s.semanticState.goal?.id:void 0;return{...r,requiredItems:n.rows(t,s.generation).flatMap(e=>"active"===e.status&&("constraint"===e.category||"goal"===e.category&&e.item_id===o)?[{itemId:e.item_id,version:e.generation,category:e.category}]:[])}}getSemanticLedgerRows(t){return new e(this.db).rows(t,this.getLatestCheckpoint(t)?.generation??0)}readCheckpointTranscript(e,t){if(!TRANSCRIPT_ENTRY_KINDS.includes(t.kind))throw new Error("kind 必须是 message、tool_execution 或 legacy_context");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??$,s="legacy_context"===t.kind?{fromSequenceExclusive:-1,throughSequence:Number.MAX_SAFE_INTEGER}:"message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,o=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(o,s.throughSequence,r,z);const i="legacy_context"===t.kind?this.listLegacyContextEntries(e,n,o,r+1):"message"===t.kind?this.listTranscriptMessages(e,{afterSequence:o,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:o,throughSequence:s.throughSequence,limit:Math.min(r+1,J)}).map(e=>({kind:"tool_execution",...e})),a=i.length>r,c=a?i.slice(0,r):i,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...a&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}listLegacyContextEntries(t,n,r,s){if(n.version===v)return[];const o=new e(this.db),i=[];if(r<0){const e=o.recovery(t),r=this.db.prepare("SELECT notes FROM compaction_recovery_notes WHERE thread_id=? AND checkpoint_id=?").get(t,n.id);i.push({kind:"legacy_context",sequence:0,value:{checkpoint:n,...e?.checkpointId===n.id?{memo:e.memo}:{},..."string"==typeof r?.notes?{notes:r.notes}:{}}})}for(const e of o.page(t,n.generation,r,s-i.length))i.push({kind:"legacy_context",sequence:e.sequence,value:{ledgerItem:y.parse({category:e.category,item:JSON.parse(e.item_json)}),generation:e.generation,status:e.status,scope:e.scope,resolution:e.resolution_json??null}});return i}maxTranscriptMessageSequence(e){return this.db.prepare("SELECT COALESCE(MAX(sequence), -1) AS maxSequence FROM transcript_messages WHERE thread_id = ?").get(e).maxSequence}maxToolExecutionSequence(e){const t=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);return(t?.next_sequence??0)-1}withCurrentTranscriptCompleteness(e,t){const n=x[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:x[0],s=r===n&&"valid"===t.toolState.integrityStatus?{...t.toolState,integrityStatus:"sanitized"}:t.toolState;return t.transcript.completeness===r&&t.toolState===s?t:{...t,toolState:s,transcript:{...t.transcript,completeness:r}}}validateTranscriptRange(e,t,n,r){if(!Number.isInteger(e)||e<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(t)||t<-1)throw new Error("throughSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(n)||n<1||n>r)throw new Error(`limit 必须是 1-${String(r)} 之间的整数`)}validateRecentPage(e,t,n){if(!Number.isSafeInteger(e)||e<0||e>Number.MAX_SAFE_INTEGER)throw new Error("beforeSequence 必须是非负安全整数");if(!Number.isInteger(t)||t<1||t>n)throw new Error(`limit 必须是 1-${String(n)} 之间的整数`)}close(){this.db.close()}}
1
+ import{SessionClaims as e,SessionOccupiedError as t,canonicalSessionDatabasePath as n}from"../ownership/session-claims.js";import{SemanticLedger as r,SEMANTIC_LEDGER_SQL as s,contextRecoveryNoteSchema as i}from"./semantic-ledger.js";import{chmodSync as o,existsSync as a,mkdirSync as c,realpathSync as d,statSync as E}from"node:fs";import{homedir as _}from"node:os";import{resolve as h}from"node:path";import{randomUUID as u}from"node:crypto";import{DatabaseSync as p}from"node:sqlite";import{modelMessageSchema as T}from"ai";import{APP_OUTPUT_LIMITS as l,appOutputResultSchema as m,appOutputDescriptorSchema as N,describeAppOutput as R,RUNTIME_V13_MAX_DURABLE_EVENT_RECORD_BYTES as S,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as g,runtimeDurableEventV16Schema as O,runtimeEventCursorSchema as I,runtimeEventIdSchema as v,timestampSchema as L}from"@roll-agent/protocol";import{parsePersistedToolExecutionRecord as x,prepareToolExecutionRecordForPersistence as f,prepareAppOutputForPersistence as A,redactSecretText as C}from"../tool-bridge/tool-execution-record.js";import{TASK_SUMMARY_CHECKPOINT_VERSION as b,COMPACTION_TRANSCRIPT_COMPLETENESS as w,TRANSCRIPT_MESSAGE_PROVENANCES as q,UnsupportedCompactionCheckpointVersionError as M,archivedTranscriptMessageSchema as y,createSummaryCheckpoint as D,buildCompactionToolState as U,parseCompactionCheckpoint as k}from"../engine/compaction-checkpoint.js";import{listSemanticArchiveItems as F,semanticArchiveItemSchema as j}from"../engine/compaction-semantic-state.js";import{createLegacyCompactionTranscriptMessage as W,readCompactionSummaryPayload as X}from"../engine/compactor.js";import{ACTIVE_TOOL_PROTOCOL_REPAIR_STATUS as H,repairActiveToolProtocol as P}from"../engine/tool-protocol-repair.js";import{sanitizePersistedExplicitSkillCheckpoint as B}from"../engine/explicit-skill-context.js";import{threadOriginSchema as G,threadDerivedFromSchema as Y,scheduledThreadOriginSchema as K}from"./thread-origin.js";import{acceptedTurnInputSchema as J,turnInputIdSchema as $,turnInputSkillCheckpointSchema as V}from"./turn-input.js";export const THREAD_STORE_SCHEMA_VERSION=11;const z=11,Q=100,Z=500,ee=50,te=500,ne=100,re=15e3;export const TOOL_EXECUTION_RETENTION_POLICY={maxBytesPerThread:16777216,maxRecordsPerThread:2e3,maxAgeMs:2592e6};export const RUNTIME_EVENT_RETENTION_POLICY={maxBytesPerThread:S,maxRecordsPerThread:g,maxAgeMs:2592e6};export class RuntimeEventCursorExpiredError extends Error{cursor;constructor(e){super("Runtime event cursor is older than the retained event prefix"),this.name="RuntimeEventCursorExpiredError",this.cursor=e}}export class RuntimeEventCursorGapError extends Error{cursor;constructor(e){super("Runtime event cursor does not belong to the current Thread event log"),this.name="RuntimeEventCursorGapError",this.cursor=e}}export const TRANSCRIPT_ENTRY_KINDS=["message","tool_execution","legacy_context"];export const TOOL_EXECUTION_CONTEXT_REPRESENTATIONS={rawTranscript:"raw_transcript",recoveryEvidence:"recovery_evidence"};const se="legacy_assumed";export function expandTilde(e){return e.startsWith("~/")?h(_(),e.slice(2)):e}export function defaultThreadsDir(){return h(_(),".roll-agent","threads")}function ie(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 oe(e){if(Number.isInteger(e.schema_version)&&e.schema_version>b)throw new M(e.schema_version);if(!Number.isInteger(e.schema_version))throw new Error("Invalid persisted compaction checkpoint schema version");const t=k(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 ae(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at,origin:G.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 ce(e,t){return"string"==typeof e?t.get(e)??e:Array.isArray(e)?e.map(e=>ce(e,t)):"object"==typeof e&&null!==e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,ce(n,t)])):e}function de(){return v.parse(u())}function Ee(e,t,n){return I.parse(`rte1:${e}:${String(t)}:${n}`)}function _e(e){const t=I.safeParse(e);if(!t.success)return;const n=t.data.split(":"),r=Number(n[2]),s=v.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 he(e){return new Date(Date.parse(L.parse(e))).toISOString()}function ue(e,t,n){const r=v.parse(n.event_id),s=O.parse(JSON.parse(n.event_json));return{eventId:r,cursor:Ee(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:N.parse(JSON.parse(n.app_output_json))}:s}}export class ThreadStore{db;now;readOnly;claims;sessionReservations=new Map;constructor(t=defaultThreadsDir(),n={}){this.now=n.now??(()=>new Date),this.readOnly=n.readOnly??!1;const r=expandTilde(t);if(this.claims=void 0,n.readOnly){this.db=new p(h(r,"threads.db"),{readOnly:!0});try{this.db.exec(`PRAGMA busy_timeout = ${String(re)}; PRAGMA query_only = ON;`);const e=this.db.prepare("PRAGMA user_version").get();if(e.user_version>z)throw new Error(`Unsupported ThreadStore schema v${String(e.user_version)}`)}catch(e){throw this.db.close(),e}return}a(r)||c(r,{recursive:!0,mode:448}),"win32"!==process.platform&&o(r,448),this.claims=void 0===n.ownership?void 0:new e(h(r,"threads.db"),n.ownership);const s=this.claims?.databasePath??h(r,"threads.db");let i,d;try{d=this.claims?.beginLibraryInitialization(()=>{const e=inspectThreadLibrary(r);if("current"!==e.status&&"new"!==e.status)throw new ThreadLibraryMigrationRequiredError(e);return"new"===e.status}),this.db=i=new p(s),"win32"!==process.platform&&o(s,384),this.init(void 0!==n.ownership)}catch(e){throw i?.close(),d?.(),this.claims?.close(),e}finally{d?.()}}init(e=!1){this.db.exec(`PRAGMA busy_timeout = ${String(re)};\n PRAGMA foreign_keys = ON;\n PRAGMA secure_delete = ON;`);const t=this.db.prepare("PRAGMA user_version").get();if(t.user_version>z)throw new Error(`ThreadStore schema v${String(t.user_version)} 高于当前支持的 v${String(z)}`);if(!e||t.user_version!==z){if(e&&0!==t.user_version)throw new Error("Existing history requires explicit backed-up migration");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 );"),t.user_version<7){const e=this.db.prepare("PRAGMA table_info(threads)").all();for(const t of["origin_json","derived_from_json"])e.some(e=>e.name===t)||this.db.exec(`ALTER TABLE threads ADD COLUMN ${t} TEXT`)}if(ie(this.db)||this.db.exec("CREATE TABLE messages_with_thread_fk (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n INSERT INTO messages_with_thread_fk\n (thread_id, idx, role, content_json, created_at)\n SELECT messages.thread_id, messages.idx, messages.role,\n messages.content_json, messages.created_at\n FROM messages\n INNER JOIN threads ON threads.id = messages.thread_id;\n DROP TABLE messages;\n ALTER TABLE messages_with_thread_fk RENAME TO messages;"),this.db.exec("CREATE TABLE IF NOT EXISTS tool_executions (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n record_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, id),\n UNIQUE (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS operation_app_outputs (\n thread_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n result_json TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n byte_length INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, operation_id),\n FOREIGN KEY (thread_id, operation_id) REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_tool_executions_thread_call\n ON tool_executions(thread_id, tool_call_id, sequence);\n CREATE TABLE IF NOT EXISTS thread_tool_execution_state (\n thread_id TEXT PRIMARY KEY,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS transcript_messages (\n thread_id TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n role TEXT NOT NULL,\n message_json TEXT NOT NULL,\n provenance TEXT NOT NULL CHECK (provenance IN ('native', 'legacy_snapshot')),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_turn_inputs (\n thread_id TEXT NOT NULL,\n input_id TEXT NOT NULL,\n transcript_sequence INTEGER NOT NULL,\n skill_checkpoint_json TEXT,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, input_id),\n UNIQUE (thread_id, transcript_sequence),\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS thread_transcript_state (\n thread_id TEXT PRIMARY KEY,\n completeness TEXT NOT NULL CHECK (completeness IN ('complete', 'legacy_snapshot')),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS tool_execution_context_coverage (\n thread_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n representation TEXT NOT NULL\n CHECK (representation IN ('raw_transcript', 'recovery_evidence', 'legacy_assumed')),\n transcript_sequence INTEGER,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, execution_id),\n CHECK (\n (representation = 'legacy_assumed' AND transcript_sequence IS NULL)\n OR (representation <> 'legacy_assumed' AND transcript_sequence IS NOT NULL)\n ),\n FOREIGN KEY (thread_id, execution_id)\n REFERENCES tool_executions(thread_id, id) ON DELETE CASCADE,\n FOREIGN KEY (thread_id, transcript_sequence)\n REFERENCES transcript_messages(thread_id, sequence) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS compaction_checkpoints (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n generation INTEGER NOT NULL,\n schema_version INTEGER NOT NULL,\n message_from_sequence INTEGER NOT NULL,\n message_through_sequence INTEGER NOT NULL,\n tool_from_sequence INTEGER NOT NULL,\n tool_through_sequence INTEGER NOT NULL,\n checkpoint_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, generation),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_thread_generation\n ON compaction_checkpoints(thread_id, generation DESC);\n CREATE TABLE IF NOT EXISTS thread_runtime_event_state (\n thread_id TEXT PRIMARY KEY,\n event_log_id TEXT NOT NULL UNIQUE,\n next_sequence INTEGER NOT NULL CHECK (next_sequence >= 0),\n retained_from_sequence INTEGER NOT NULL\n CHECK (retained_from_sequence >= 0 AND retained_from_sequence <= next_sequence),\n retained_predecessor_event_id TEXT,\n latest_event_id TEXT,\n retained_count INTEGER NOT NULL CHECK (retained_count >= 0),\n retained_bytes INTEGER NOT NULL CHECK (retained_bytes >= 0),\n CHECK (\n (next_sequence = 0\n AND retained_from_sequence = 0\n AND retained_predecessor_event_id IS NULL\n AND latest_event_id IS NULL)\n OR (next_sequence > 0 AND latest_event_id IS NOT NULL)\n ),\n CHECK (\n (retained_from_sequence = 0 AND retained_predecessor_event_id IS NULL)\n OR (retained_from_sequence > 0 AND retained_predecessor_event_id IS NOT NULL)\n ),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS runtime_events (\n thread_id TEXT NOT NULL,\n thread_sequence INTEGER NOT NULL CHECK (thread_sequence >= 0),\n event_id TEXT NOT NULL UNIQUE,\n turn_id TEXT,\n event_timestamp TEXT NOT NULL,\n event_json TEXT NOT NULL,\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, thread_sequence),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_runtime_events_thread_created\n ON runtime_events(thread_id, created_at, thread_sequence);"),this.db.prepare("PRAGMA table_info(runtime_events)").all().some(e=>"app_output_json"===e.name)||this.db.exec("ALTER TABLE runtime_events ADD COLUMN app_output_json TEXT"),this.db.exec("INSERT OR IGNORE INTO thread_tool_execution_state (thread_id, next_sequence)\n SELECT id, 0 FROM threads;\n UPDATE thread_tool_execution_state\n SET next_sequence = MAX(\n next_sequence,\n COALESCE((\n SELECT MAX(tool_executions.sequence) + 1\n FROM tool_executions\n WHERE tool_executions.thread_id = thread_tool_execution_state.thread_id\n ), 0)\n );"),t.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;"),t.user_version<4&&(this.migrateToolExecutionPersistenceInTransaction(),this.migrateExplicitSkillCheckpointPersistenceInTransaction()),t.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(se),this.db.exec(s),t.user_version<9){const e=new r(this.db),t=this.db.prepare("SELECT thread_id, schema_version, checkpoint_json FROM compaction_checkpoints ORDER BY generation").all();for(const n of t){if("string"!=typeof n.thread_id||"string"!=typeof n.checkpoint_json)continue;let t;try{t=oe({schema_version:Number(n.schema_version),checkpoint_json:n.checkpoint_json})}catch(e){if(e instanceof M)throw e;this.db.prepare("UPDATE thread_transcript_state SET completeness = 'legacy_snapshot' WHERE thread_id = ?").run(n.thread_id);continue}if(2!==t.version)continue;const r=this.getLatestCheckpoint(n.thread_id);e.append(n.thread_id,t.generation,2===t.version?t.semanticState.scopeId??t.semanticState.goal?.id??"initial":"legacy",t.transcript.toolExecutions.throughSequence,F(t.semanticState),r?.id!==t.id)}}this.initializeMissingRuntimeEventStatesInTransaction();const e=this.now().toISOString();this.enforceAllToolExecutionRetentionInTransaction(e),this.enforceAllRuntimeEventRetentionInTransaction(e),this.db.exec(`PRAGMA user_version = ${String(z)};`),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=x(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=T.parse(JSON.parse(t.message_json)),r=B(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,u())}enforceAllRuntimeEventRetentionInTransaction(e){const t=this.db.prepare("SELECT thread_id AS id FROM thread_runtime_event_state").all();for(const n of t)this.enforceRuntimeEventRetentionInTransaction(n.id,e)}createThread(e={}){const t=e.id??u();return this.withThreadWrite(t,()=>this.createThreadOwned({...e,id:t}))}createThreadOwned(e={}){const t=e.id??u();if(this.readOnly)throw new Error("ThreadStore is read-only");const n=this.now().toISOString(),r=G.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,w[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,u()),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?ae(t):void 0}listThreads(e={}){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(ae).filter(t=>void 0===e.origin||t.origin.kind===e.origin)}backfillScheduledOrigin(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ? AND origin_json IS NULL").get(e)&&this.withThreadWrite(e,()=>this.backfillScheduledOriginOwned(e,t))}backfillScheduledOriginOwned(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=K.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(),s=this.getMessages(e),i=e=>void 0!==this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(e),o=i("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=>y.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})):s.map((e,n)=>y.parse({sequence:n,message:e,provenance:"legacy_snapshot",createdAt:t.createdAt})),a=i("tool_executions")?this.db.prepare("SELECT sequence, record_json FROM tool_executions WHERE thread_id = ? ORDER BY sequence").all(e).map(e=>({...x(JSON.parse(e.record_json)),sequence:e.sequence})):[],c=i("compaction_checkpoints")?this.db.prepare("SELECT schema_version, checkpoint_json FROM compaction_checkpoints WHERE thread_id = ? ORDER BY generation").all(e).map(oe):[],d=i("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):a.map(e=>({executionId:e.id,representation:"legacy_assumed",transcriptSequence:null,createdAt:n})),E=i("thread_tool_execution_state")?this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e):void 0,_=i("compaction_recovery")?new r(this.db).recovery(e):void 0,h={...i("compaction_semantic_items")?{semanticLedger:new r(this.db).versions(e)}:{},..._?{contextRecovery:_}:{},...i("compaction_recovery_notes")?{contextRecoveryNotes:new r(this.db).notes(e)}:{},thread:t,capturedAt:n,appOutputs:i("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:s,transcript:o,turnInputs:i("thread_turn_inputs")?this.listTurnInputs(e):[],toolExecutions:a,checkpoints:c,toolExecutionCoverage:d,nextToolExecutionSequence:E?.next_sequence??(a.at(-1)?.sequence??-1)+1,transcriptCompleteness:i("thread_transcript_state")?this.getTranscriptCompleteness(e):"legacy_snapshot"};return this.db.exec("COMMIT"),h}catch(e){throw this.db.exec("ROLLBACK"),e}}forkSnapshot(e,t={}){const n=t.id??u();return this.withThreadWrite(n,()=>this.forkSnapshotOwned(e,{...t,id:n}))}forkSnapshotOwned(e,t={}){const n=t.id??u();if(this.readOnly)throw new Error("ThreadStore is read-only");const s=new Map(e.checkpoints.map(e=>[e.id,u()])),o=e.checkpoints.map(e=>{const t=k(ce(e,s));return t.version===b?t:k({...t,runningWork:t.runningWork.map(e=>({...e,recoverability:"unavailable"}))})}),a=e.messages.map(e=>T.parse(ce(e,s))),c=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.createThread({id:n,...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,P(a).messages,c);const d=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=y.parse(t);d.run(n,e.sequence,e.message.role,JSON.stringify(e.message),e.provenance,e.createdAt)}const E=this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, skill_checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?)");for(const t of e.turnInputs??[]){const e=J.parse(t);E.run(n,u(),e.transcriptSequence,void 0===e.skillCheckpoint?null:JSON.stringify(e.skillCheckpoint),e.createdAt)}this.db.prepare("UPDATE thread_transcript_state SET completeness = ? WHERE thread_id = ?").run(e.transcriptCompleteness,n);const _=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=x(r);_.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 h=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)h.run(n,t.executionId,t.representation,t.transcriptSequence,t.createdAt);const p=this.db.prepare("INSERT INTO compaction_checkpoints (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence, tool_from_sequence, tool_through_sequence, checkpoint_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");for(const e of o)p.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);const T=new r(this.db);if(e.semanticLedger)T.restore(n,e.semanticLedger);else for(const e of o)2===e.version&&T.append(n,e.generation,2===e.version?e.semanticState.scopeId??e.semanticState.goal?.id??"initial":"legacy",e.transcript.toolExecutions.throughSequence,F(e.semanticState),e.id!==o.at(-1)?.id);if(e.contextRecovery){const t=s.get(e.contextRecovery.checkpointId);t&&this.db.prepare("INSERT INTO compaction_recovery\n (thread_id,checkpoint_id,required,unextracted,memo) VALUES (?,?,1,?,?)").run(n,t,Number(e.contextRecovery.unextracted),e.contextRecovery.memo)}for(const t of e.contextRecoveryNotes??[]){const e=i.parse(t),r=s.get(e.checkpointId);r&&this.db.prepare("INSERT INTO compaction_recovery_notes (thread_id,checkpoint_id,notes) VALUES (?,?,?)").run(n,r,e.notes)}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){return this.withThreadWrite(e,()=>this.updateTitleOwned(e,t))}updateTitleOwned(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}updateModel(e,t){return this.withThreadWrite(e,()=>this.updateModelOwned(e,t))}updateModelOwned(e,t){this.db.prepare("UPDATE threads SET model = ? WHERE id = ?").run(t,e)}deleteThread(e,t={}){return this.withThreadWrite(e,()=>this.deleteThreadOwned(e,t))}deleteThreadOwned(e,t={}){t.onlyIfEmpty&&(this.countMessages(e)>0||this.countTranscriptMessages(e)>0)||this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendRuntimeEvent(e){return this.withThreadWrite(e.threadId,()=>this.appendRuntimeEventOwned(e))}appendRuntimeEventOwned(e){const t=O.parse(e.event),n="tool.completed"===t.type?(({appOutput:e,...t})=>t)(t):t,r=JSON.stringify(n),s=he(e.timestamp),i=Buffer.byteLength(JSON.stringify(t),"utf8")+Buffer.byteLength(s,"utf8")+Buffer.byteLength(e.turnId??"","utf8");if(i>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 o=de(),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,o,e.turnId??null,s,r,i,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,o,i,e.threadId),this.enforceRuntimeEventRetentionInTransaction(e.threadId,s),this.db.exec("COMMIT"),{eventId:o,cursor:Ee(n.event_log_id,a,o),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:Ee(t.event_log_id,t.next_sequence-1,v.parse(t.latest_event_id))}resumeRuntimeEvents(e,t){const n=null===t?void 0:_e(t);if(null!==t&&void 0===n)throw new RuntimeEventCursorGapError(t);const r=!this.readOnly&&void 0===this.claims;this.db.exec(r?"BEGIN IMMEDIATE":"BEGIN");try{r&&this.enforceRuntimeEventRetentionInTransaction(e,this.now().toISOString());const s=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===s)throw new Error(`Thread "${e}" 不存在或缺少 Runtime event state`);if(null===t&&s.retained_from_sequence>0)throw new RuntimeEventCursorExpiredError(t);if(null!==t&&void 0!==n&&this.validateRuntimeEventCursor(s,t,n,e),0===s.next_sequence||null===s.latest_event_id)return this.db.exec("COMMIT"),{events:[],throughCursor:null,replayedCount:0};const i=s.next_sequence-1,o=n?.threadSequence??-1,a=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,i);if(!r){const n=new Date(this.now().getTime()-RUNTIME_EVENT_RETENTION_POLICY.maxAgeMs).toISOString(),r=this.db.prepare("SELECT MIN(thread_sequence) AS keep_from FROM runtime_events WHERE thread_id = ? AND created_at >= ?").get(e,n);if(o<Math.max(s.retained_from_sequence,r.keep_from??s.next_sequence)-1)throw new RuntimeEventCursorExpiredError(t)}const c=a.map(t=>ue(s.event_log_id,e,t)),d=Ee(s.event_log_id,i,v.parse(s.latest_event_id));return this.db.exec("COMMIT"),{events:c,throughCursor:d,replayedCount:c.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 i=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===i||i.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(),i=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,i.keep_from??n.next_sequence),r<=n.retained_from_sequence)return;const o=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===o)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,o.event_id,a.removed_count,a.removed_bytes??0,e)}acceptTurnInput(e,t,n){return this.withThreadWrite(e,()=>this.acceptTurnInputOwned(e,t,n))}acceptTurnInputOwned(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=$.parse(t),s=B(T.parse(n));if("user"!==s.role)throw new Error("Accepted turn input must be a user message");this.db.exec("BEGIN IMMEDIATE");try{if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=this.getTurnInput(e,r);if(void 0!==t){const n=this.db.prepare("SELECT message_json FROM transcript_messages WHERE thread_id = ? AND sequence = ?").get(e,t.transcriptSequence);if(n?.message_json!==JSON.stringify(s))throw new Error("Turn input identity conflict");return this.db.exec("COMMIT"),t}const n=this.now().toISOString();this.appendMessagesInTransaction(e,[s],n,void 0);const i=this.db.prepare("SELECT MAX(sequence) AS sequence FROM transcript_messages WHERE thread_id = ?").get(e),o=J.parse({inputId:r,transcriptSequence:i?.sequence,createdAt:n});return this.db.prepare("INSERT INTO thread_turn_inputs (thread_id, input_id, transcript_sequence, created_at) VALUES (?, ?, ?, ?)").run(e,r,o.transcriptSequence,n),this.db.exec("COMMIT"),o}catch(e){throw this.db.exec("ROLLBACK"),e}}getTurnInput(e,t){const n=this.db.prepare("SELECT input_id, transcript_sequence, skill_checkpoint_json, created_at FROM thread_turn_inputs WHERE thread_id = ? AND input_id = ?").get(e,$.parse(t));return void 0===n?void 0:J.parse({inputId:n.input_id,transcriptSequence:n.transcript_sequence,createdAt:n.created_at,..."string"==typeof n.skill_checkpoint_json?{skillCheckpoint:JSON.parse(n.skill_checkpoint_json)}:{}})}listTurnInputs(e){return this.db.prepare("SELECT input_id FROM thread_turn_inputs WHERE thread_id = ? ORDER BY transcript_sequence").all(e).map(t=>{const n=this.getTurnInput(e,String(t.input_id));if(void 0===n)throw new Error("Turn input receipt disappeared");return n})}getTurnInputSkillCheckpoint(e,t){const n=this.db.prepare("SELECT skill_checkpoint_json FROM thread_turn_inputs WHERE thread_id = ? AND transcript_sequence = ?").get(e,t);return"string"==typeof n?.skill_checkpoint_json?V.parse(JSON.parse(n.skill_checkpoint_json)):void 0}recordTurnSkillCheckpoint(e,t,n){return this.withThreadWrite(e,()=>this.recordTurnSkillCheckpointOwned(e,t,n))}recordTurnSkillCheckpointOwned(e,t,n){if(this.readOnly)throw new Error("ThreadStore is read-only");const r=V.parse(n),s=JSON.stringify(r);this.db.exec("BEGIN IMMEDIATE");try{const n=this.getTurnInput(e,t);if(void 0===n)throw new Error("Turn input receipt not found");if(void 0!==n.skillCheckpoint&&JSON.stringify(n.skillCheckpoint)!==s)throw new Error("Turn input Skill snapshot conflict");this.db.prepare("UPDATE thread_turn_inputs SET skill_checkpoint_json = ? WHERE thread_id = ? AND input_id = ?").run(s,e,t),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}appendMessages(e,t,n={}){return this.withThreadWrite(e,()=>this.appendMessagesOwned(e,t,n))}appendMessagesOwned(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??[],i=new Set;for(const e of s){if("string"!=typeof e||0===e.length)throw new Error("Tool execution coverage execution id 无效");if(i.has(e))throw new Error(`Tool execution coverage execution id 重复: ${e}`);i.add(e)}const o=t.map(e=>B(T.parse(e))),a=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{this.appendMessagesInTransaction(e,o,a,r),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}recoverUncoveredToolExecutions(e,t){return this.withThreadWrite(e,()=>this.recoverUncoveredToolExecutionsOwned(e,t))}recoverUncoveredToolExecutionsOwned(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=B(T.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 (?, ?, ?, ?, ?)"),i=this.db.prepare("INSERT INTO transcript_messages\n (thread_id, sequence, role, message_json, provenance, created_at)\n VALUES (?, ?, ?, ?, ?, ?)"),o=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),i.run(e,E,r.role,t,q[0],n),d+=1,E+=1}const _=E-1;if(void 0!==r){for(const t of r.executionIds){const s=o.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){return this.withThreadWrite(e,()=>this.replaceMessagesOwned(e,t))}replaceMessagesOwned(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.map(e=>T.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=>T.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 i=B(t);r.run(e,s,i.role,JSON.stringify(i),n)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(n,e)}appendToolExecution(e,t){return this.withThreadWrite(e,()=>this.appendToolExecutionOwned(e,t))}appendToolExecutionOwned(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=f(t),r=JSON.stringify(n),s=this.now().toISOString();this.db.exec("BEGIN IMMEDIATE");try{const i=this.db.prepare("SELECT next_sequence FROM thread_tool_execution_state WHERE thread_id = ?").get(e);if(void 0===i)throw new Error(`Thread "${e}" 缺少 Tool execution sequence state`);const o=i.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),void 0!==t.appOutput&&this.insertAppOutput(e,t.id,t.appOutput,new Date(Date.parse(s)+l.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"),o}catch(e){throw this.db.exec("ROLLBACK"),e}}readAppOutputRow(e,t){const n=m.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"}:A(n),i=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,i,JSON.stringify(R(s)),"available"===s.status?Buffer.byteLength(i,"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,l.threadRecords,l.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=N.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"}:m.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 i=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(i.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 i){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=>({...x(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??Q;if(!Number.isInteger(t)||t<-1)throw new Error("afterSequence 必须是大于等于 -1 的整数");if(!Number.isInteger(r)||r<1||r>Z)throw new Error(`limit 必须是 1-${String(Z)} 之间的整数`);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=>({...x(JSON.parse(e.record_json)),sequence:e.sequence}))}listRecentToolExecutions(e,t={}){const n=t.beforeSequence??Number.MAX_SAFE_INTEGER,r=t.limit??Q;this.validateRecentPage(n,r,Z);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),i=s.length>r,o=s.slice(0,r).map(e=>({...x(JSON.parse(e.record_json)),sequence:e.sequence})).reverse();return{entries:o,nextBeforeSequence:i?o[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{...x(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===w[0]?w[0]:w[1]}listTranscriptMessages(e,t={}){const n=t.afterSequence??-1,r=t.throughSequence??Number.MAX_SAFE_INTEGER,s=t.limit??ee;if(this.validateTranscriptRange(n,r,s,te),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=>y.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??ee;this.validateRecentPage(n,r,te);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),i=s.length>r,o=s.slice(0,r).map(e=>y.parse({sequence:e.sequence,provenance:e.provenance,createdAt:e.created_at,message:JSON.parse(e.message_json)})).reverse();return{entries:o,nextBeforeSequence:i?o[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,oe(n))}catch(e){if(e instanceof M)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=oe(n);if(t.version===b||"valid"===t.summary.status)return this.withCurrentTranscriptCompleteness(e,t)}catch(e){if(e instanceof M)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,oe(n))}catch(e){if(e instanceof M)throw e;return}}loadSessionState(e){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);return{messages:P(this.getMessages(e)).messages,checkpoint:this.getLatestCheckpoint(e),transcriptCompleteness:this.getTranscriptCompleteness(e)}}commitCompaction(e,t){return this.withThreadWrite(e,()=>this.commitCompactionOwned(e,t))}commitCompactionOwned(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const n=t.messages.map(e=>T.parse(e)),r=t.expectedActiveMessages.map(e=>T.parse(e)),s=P(n);if(s.status!==H.valid){const e=[...new Set([...s.removedToolCallIds,...s.removedToolResultIds])].sort();throw new Error(`Compaction active projection contains malformed Tool protocol: ${e.join(", ")}`)}const i=(new Date).toISOString();this.db.exec("BEGIN IMMEDIATE");try{const s=P(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 o=this.getLatestCheckpoint(e);if(o?.id!==t.expectedLatestCheckpointId)throw new Error("Compaction checkpoint ancestry changed after the draft snapshot was captured");const a=this.legacySummaryFragmentsToArchive(e,s,n),c=this.maxTranscriptMessageSequence(e),d=this.db.prepare("SELECT COALESCE(MAX(generation), 0) AS maxGeneration FROM compaction_checkpoints WHERE thread_id = ?").get(e),E={messages:{fromSequenceExclusive:o?.transcript.messages.throughSequence??-1,throughSequence:c+a.length},toolExecutions:{fromSequenceExclusive:o?.transcript.toolExecutions.throughSequence??-1,throughSequence:this.maxToolExecutionSequence(e)},completeness:this.getTranscriptCompleteness(e)},_=new Set(r.flatMap(e=>Array.isArray(e.content)?e.content.flatMap(e=>"tool-call"===e.type?[e.toolCallId]:[]):[])),h=this.listRecentToolExecutions(e,{limit:Z}).entries.filter(e=>_.has(e.toolCallId)),u=D({summary:t.summary,generation:d.maxGeneration+1,transcript:E,...void 0===o?{}:{previousCheckpointId:o.id},toolState:U(r,h,32,E.completeness),createdAt:i});t.validateCheckpoint?.(u);for(const[t,n]of a.entries())this.db.prepare("INSERT INTO transcript_messages\n (thread_id,sequence,role,message_json,provenance,created_at) VALUES (?,?,?,?,?,?)").run(e,c+t+1,n.role,JSON.stringify(n),q[1],i);return this.db.prepare("INSERT INTO compaction_checkpoints\n (id, thread_id, generation, schema_version, message_from_sequence, message_through_sequence,\n tool_from_sequence, tool_through_sequence, checkpoint_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(u.id,e,u.generation,u.version,E.messages.fromSequenceExclusive,E.messages.throughSequence,E.toolExecutions.fromSequenceExclusive,E.toolExecutions.throughSequence,JSON.stringify(u),u.createdAt),this.replaceMessagesInTransaction(e,n,i),this.db.exec("COMMIT"),u}catch(e){throw this.db.exec("ROLLBACK"),e}}legacySummaryFragmentsToArchive(e,t,n){const r=e=>"string"==typeof e.content?X(e.content):void 0,s=new Set(n.map(r)),i=new Map,o=this.db.prepare("SELECT 1 FROM transcript_messages WHERE thread_id=? AND json_extract(message_json, '$.content') IN (?, ?) LIMIT 1");for(const n of t){const t=r(n);if(void 0===t||0===t.length||s.has(t))continue;const a=C(t),c=W(a);o.get(e,String(n.content),String(c.content))||i.set(a,c)}return[...i.values()]}getContextRecovery(e){if(this.getLatestCheckpoint(e)?.version===b)return;const t=new r(this.db),n=t.recovery(e);if(!n)return;const s=this.getCheckpoint(e,n.checkpointId);if(!s)throw new Error("Recovery checkpoint is missing");const i=2===s.version?s.semanticState.goal?.id:void 0;return{...n,requiredItems:t.rows(e,s.generation).flatMap(e=>"active"===e.status&&("constraint"===e.category||"goal"===e.category&&e.item_id===i)?[{itemId:e.item_id,version:e.generation,category:e.category}]:[])}}getSemanticLedgerRows(e){return new r(this.db).rows(e,this.getLatestCheckpoint(e)?.generation??0)}readCheckpointTranscript(e,t){if(!TRANSCRIPT_ENTRY_KINDS.includes(t.kind))throw new Error("kind 必须是 message、tool_execution 或 legacy_context");const n=this.getCheckpoint(e,t.checkpointId);if(void 0===n)throw new Error("checkpoint 不存在、已损坏或不属于当前 thread");const r=t.limit??ee,s="legacy_context"===t.kind?{fromSequenceExclusive:-1,throughSequence:Number.MAX_SAFE_INTEGER}:"message"===t.kind?n.transcript.messages:n.transcript.toolExecutions,i=Math.max(t.afterSequence??s.fromSequenceExclusive,s.fromSequenceExclusive);this.validateTranscriptRange(i,s.throughSequence,r,ne);const o="legacy_context"===t.kind?this.listLegacyContextEntries(e,n,i,r+1):"message"===t.kind?this.listTranscriptMessages(e,{afterSequence:i,throughSequence:s.throughSequence,limit:r+1}).map(e=>({kind:"message",...e})):this.listToolExecutions(e,{afterSequence:i,throughSequence:s.throughSequence,limit:Math.min(r+1,Z)}).map(e=>({kind:"tool_execution",...e})),a=o.length>r,c=a?o.slice(0,r):o,d=c.at(-1)?.sequence;return{checkpointId:n.id,kind:t.kind,entries:c,...a&&void 0!==d?{nextAfterSequence:d}:{},...void 0!==n.previousCheckpointId?{previousCheckpointId:n.previousCheckpointId}:{},completeness:n.transcript.completeness}}listLegacyContextEntries(e,t,n,s){if(t.version===b)return[];const i=new r(this.db),o=[];if(n<0){const n=i.recovery(e),r=this.db.prepare("SELECT notes FROM compaction_recovery_notes WHERE thread_id=? AND checkpoint_id=?").get(e,t.id);o.push({kind:"legacy_context",sequence:0,value:{checkpoint:t,...n?.checkpointId===t.id?{memo:n.memo}:{},..."string"==typeof r?.notes?{notes:r.notes}:{}}})}for(const r of i.page(e,t.generation,n,s-o.length))o.push({kind:"legacy_context",sequence:r.sequence,value:{ledgerItem:j.parse({category:r.category,item:JSON.parse(r.item_json)}),generation:r.generation,status:r.status,scope:r.scope,resolution:r.resolution_json??null}});return o}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=w[1],r=t.transcript.completeness===n||this.getTranscriptCompleteness(e)===n?n:w[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)} 之间的整数`)}retainSession(e){if(void 0===this.claims)return;if(this.sessionReservations.has(e))throw new t(e);const n=this.claims.acquire(e);try{if(this.hasThread(e)){this.db.exec("BEGIN IMMEDIATE");try{const t=this.now().toISOString();this.enforceAppOutputRetention(e),this.enforceToolExecutionRetentionInTransaction(e,t),this.enforceRuntimeEventRetentionInTransaction(e,t),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}}catch(e){throw n.release(),e}this.sessionReservations.set(e,n.token);let r,s=!1;return{...n,release:()=>{if(void 0!==r)throw r.cause;if(!s){try{n.release()}catch(e){throw r={cause:e},e}s=!0,this.sessionReservations.get(e)===n.token&&this.sessionReservations.delete(e)}}}}retainThread(e){if(this.readOnly)throw new Error("ThreadStore is read-only");return this.claims?.acquire(e)}withThreadWrite(e,t){if(this.readOnly)throw new Error("ThreadStore is read-only");const n=this.retainThread(e);try{return n?.assert(),t()}finally{n?.release()}}close(){this.claims?.close(),this.db.close()}}export const THREAD_LIBRARY_STATUSES=["new","current","needs_migration","unsupported"];export class ThreadLibraryMigrationRequiredError extends Error{preflight;constructor(e){super(`会话库需要显式备份升级或受支持的新版本:${e.status} (v${String(e.schemaVersion)})。请停止所有客户端后确认迁移;未修改历史。`),this.name="ThreadLibraryMigrationRequiredError",this.preflight=e}}export function inspectThreadLibrary(e=defaultThreadsDir()){const t=h(expandTilde(e),"threads.db");if(!a(t))return{databasePath:t,schemaVersion:null,supportedSchemaVersion:z,status:"new"};const n=d(t),r=E(n,{bigint:!0}),s=new p(n,{readOnly:!0});try{s.exec(`PRAGMA busy_timeout = ${String(re)}; PRAGMA query_only = ON;`);const e=s.prepare("PRAGMA user_version").get(),t=e?.user_version;if("number"!=typeof t)throw new Error("Invalid ThreadStore schema version");return{databasePath:n,physicalIdentity:`inode:${r.dev}:${r.ino}:${r.birthtimeNs}`,writableAliasSafe:1n===r.nlink,schemaVersion:t,supportedSchemaVersion:z,status:t===z?"current":t>z?"unsupported":"needs_migration"}}finally{s.close()}}export function migrateThreadLibrary(t){if(!0!==t.confirmStoppedLegacyClients)throw new Error("Migration requires explicit confirmation that legacy clients are stopped");const r=inspectThreadLibrary(t.dir);if("needs_migration"!==r.status)throw new ThreadLibraryMigrationRequiredError(r);const s=new e(r.databasePath,t.ownership);let i;try{i=s.maintenance(),i.assert();const e=inspectThreadLibrary(t.dir);if("needs_migration"!==e.status)throw new ThreadLibraryMigrationRequiredError(e);const r=n(e.databasePath),a=`${r}.backup-${u()}.sqlite`,c=new p(r,{readOnly:!0});try{c.prepare("VACUUM INTO ?").run(a)}finally{c.close()}"win32"!==process.platform&&o(a,384);const d=new p(a,{readOnly:!0});try{const t=d.prepare("PRAGMA quick_check").all();if(1!==t.length||"ok"!==t[0]?.quick_check||d.prepare("PRAGMA user_version").get()?.user_version!==e.schemaVersion)throw new Error("SQLite backup verification failed; original history was not migrated")}finally{d.close()}i.assert();return new ThreadStore(t.dir).close(),{backupPath:a,preflight:inspectThreadLibrary(t.dir)}}finally{i?.release(),s.close()}}
@@ -1 +1 @@
1
- import{execFile as t}from"node:child_process";import{rgPath as e}from"@vscode/ripgrep";const r=1e4,o=1048576;function n(t){return t instanceof Error&&"AbortError"===t.name||"object"==typeof t&&null!==t&&"code"in t&&"ABORT_ERR"===t.code}export function runRg(u,s,c={}){const d=c.maxOutputBytes??o;return new Promise(o=>{t(e,[...u],{cwd:s,timeout:c.timeoutMs??r,maxBuffer:2*d,encoding:"utf8",...c.abortSignal?{signal:c.abortSignal}:{}},(t,e,r)=>{if(n(t)||!0===c.abortSignal?.aborted)return void o({ok:!1,stdout:"",truncated:!1,cancelled:!0,errorMessage:"已取消"});const u="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"===t?.code;if(t&&!u&&"number"==typeof t.code&&t.code>1)return void o({ok:!1,stdout:"",truncated:!1,errorMessage:r.trim()||t.message});if(t&&!u&&"number"!=typeof t.code)return void o({ok:!1,stdout:"",truncated:!1,errorMessage:t.message});const s=e;if(Buffer.byteLength(s,"utf8")>d){const t=Buffer.from(s,"utf8").subarray(0,d).toString("utf8"),e=t.lastIndexOf("\n");return void o({ok:!0,stdout:e>0?t.slice(0,e):t,truncated:!0})}o({ok:!0,stdout:s,truncated:!1})})})}
1
+ import{execFile as t}from"node:child_process";import{toNamespacedPath as e}from"node:path";import{rgPath as o}from"@vscode/ripgrep";const r=1e4,n=1048576;function u(t){return t instanceof Error&&"AbortError"===t.name||"object"==typeof t&&null!==t&&"code"in t&&"ABORT_ERR"===t.code}export function runRg(s,d,c={}){const i=c.maxOutputBytes??n;return new Promise(n=>{t(e(o),[...s],{cwd:d,timeout:c.timeoutMs??r,maxBuffer:2*i,encoding:"utf8",...c.abortSignal?{signal:c.abortSignal}:{}},(t,e,o)=>{if(u(t)||!0===c.abortSignal?.aborted)return void n({ok:!1,stdout:"",truncated:!1,cancelled:!0,errorMessage:"已取消"});const r="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"===t?.code;if(t&&!r&&"number"==typeof t.code&&t.code>1)return void n({ok:!1,stdout:"",truncated:!1,errorMessage:o.trim()||t.message});if(t&&!r&&"number"!=typeof t.code)return void n({ok:!1,stdout:"",truncated:!1,errorMessage:t.message});const s=e;if(Buffer.byteLength(s,"utf8")>i){const t=Buffer.from(s,"utf8").subarray(0,i).toString("utf8"),e=t.lastIndexOf("\n");return void n({ok:!0,stdout:e>0?t.slice(0,e):t,truncated:!0})}n({ok:!0,stdout:s,truncated:!1})})})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",