@roll-agent/runtime 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bash/session/session-manager.d.ts +1 -0
- package/dist/bash/session/session-manager.js +1 -1
- package/dist/engine/agent-session.d.ts +6 -0
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/conversation-engine.d.ts +3 -0
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/host-contracts/index.d.ts +1 -0
- package/dist/host-contracts/index.js +1 -1
- package/dist/host-contracts/session-ownership.d.ts +12 -0
- package/dist/host-contracts/session-ownership.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/ownership/session-claims.d.ts +61 -0
- package/dist/ownership/session-claims.js +1 -0
- package/dist/server/runtime-server.js +1 -1
- package/dist/service/attachment-store.js +1 -1
- package/dist/service/runtime-service.d.ts +10 -1
- package/dist/service/runtime-service.js +1 -1
- package/dist/store/thread-store.d.ts +51 -1
- package/dist/store/thread-store.js +1 -1
- package/dist/tool-bridge/file-tools/rg-exec.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { SessionOwnershipHost } from "../host-contracts/session-ownership.ts";
|
|
3
|
+
export declare const SESSION_OCCUPANCY_CAPABILITY: {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly scope: "canonical-library-and-thread";
|
|
6
|
+
readonly claims: "sqlite-sidecar-cas";
|
|
7
|
+
readonly idleOwnership: true;
|
|
8
|
+
readonly forceTakeover: false;
|
|
9
|
+
readonly legacyWriters: "unverified-sequential-only";
|
|
10
|
+
};
|
|
11
|
+
declare const claimSchema: z.ZodObject<{
|
|
12
|
+
thread_id: z.ZodString;
|
|
13
|
+
claim_id: z.ZodString;
|
|
14
|
+
identity: z.ZodString;
|
|
15
|
+
owner_json: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
type Claim = z.infer<typeof claimSchema>;
|
|
18
|
+
export declare class SessionOccupiedError extends Error {
|
|
19
|
+
readonly details: {
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
readonly threadId: string;
|
|
22
|
+
readonly ownerKind?: string;
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
readonly workspace?: string;
|
|
25
|
+
readonly action: string;
|
|
26
|
+
};
|
|
27
|
+
constructor(threadId: string, claim?: Claim, reason?: string);
|
|
28
|
+
}
|
|
29
|
+
/** Symlinks share the canonical SQLite journal. Hardlinks do not; reject before opening writable. */
|
|
30
|
+
export declare function canonicalSessionDatabasePath(path: string): string;
|
|
31
|
+
export interface SessionClaim {
|
|
32
|
+
readonly threadId: string;
|
|
33
|
+
readonly token: string;
|
|
34
|
+
assert(): void;
|
|
35
|
+
release(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Cooperative local-process fencing. The sidecar is never unlinked/replaced. A dead process
|
|
39
|
+
* cannot write; a live/unknown process is never reaped. This is not a distributed FS lock and
|
|
40
|
+
* does not constrain old binaries or external effects surviving their parent process.
|
|
41
|
+
*/
|
|
42
|
+
export declare class SessionClaims {
|
|
43
|
+
private readonly db;
|
|
44
|
+
private readonly held;
|
|
45
|
+
private closed;
|
|
46
|
+
readonly databasePath: string;
|
|
47
|
+
constructor(path: string, privateHost: SessionOwnershipHost);
|
|
48
|
+
private readonly host;
|
|
49
|
+
private read;
|
|
50
|
+
private checkOpen;
|
|
51
|
+
private reap;
|
|
52
|
+
acquire(threadId: string): SessionClaim;
|
|
53
|
+
private acquireKey;
|
|
54
|
+
/** No writer can enter while maintenance owns this gate; any held writer blocks it. */
|
|
55
|
+
maintenance(): SessionClaim;
|
|
56
|
+
private assert;
|
|
57
|
+
private handle;
|
|
58
|
+
/** Refuse to convert resource disposal into proof that unsettled sessions are safe. */
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{chmodSync as i,existsSync as r,lstatSync as t,mkdirSync as s,realpathSync as a,statSync as o}from"node:fs";import{dirname as n,resolve as d}from"node:path";import{DatabaseSync as c}from"node:sqlite";import{z as h}from"zod";export const SESSION_OCCUPANCY_CAPABILITY={version:1,scope:"canonical-library-and-thread",claims:"sqlite-sidecar-cas",idleOwnership:!0,forceTakeover:!1,legacyWriters:"unverified-sequential-only"};const l=h.object({thread_id:h.string(),claim_id:h.string(),identity:h.string(),owner_json:h.string()}),p=h.object({kind:h.string(),label:h.string(),workspace:h.string().optional()});function m(e){if(void 0!==e)try{return p.parse(JSON.parse(e.owner_json))}catch{return}}const u="";export class SessionOccupiedError extends Error{details;constructor(e,i,r="session_occupied"){super("此会话正在另一个客户端中使用。请先退出该客户端的当前会话,再重新检测;两端请选择其一。"),this.name="SessionOccupiedError";const t=m(i);this.details={reason:r,threadId:e,action:"close_other_client_and_retry",...void 0!==t?{ownerKind:t.kind,label:t.label,...void 0===t.workspace?{}:{workspace:t.workspace}}:{}}}}export function canonicalSessionDatabasePath(e){if(!r(e)){if(t(e,{throwIfNoEntry:!1})?.isSymbolicLink())throw new SessionOccupiedError("",void 0,"ambiguous_library_alias");return d(a(n(e)),"threads.db")}const i=a(e);if(o(i).nlink>1)throw new SessionOccupiedError("",void 0,"ambiguous_library_alias");return i}export class SessionClaims{db;held=new Map;closed=!1;databasePath;constructor(e,r){if(this.host=r,!r.identity)throw new Error("Session ownership requires a verifiable host identity");s(n(e),{recursive:!0,mode:448}),this.databasePath=canonicalSessionDatabasePath(e);const t=`${this.databasePath}.occupancy.sqlite`;this.db=new c(t);try{"win32"!==process.platform&&i(t,384),this.db.exec("PRAGMA busy_timeout = 15000;\n CREATE TABLE IF NOT EXISTS claims (\n thread_id TEXT PRIMARY KEY, claim_id TEXT NOT NULL,\n identity TEXT NOT NULL, owner_json TEXT NOT NULL\n );")}catch(e){throw this.db.close(),e}}host;read(e){const i=this.db.prepare("SELECT * FROM claims WHERE thread_id = ?").get(e);return void 0===i?void 0:l.parse(i)}checkOpen(){if(this.closed)throw new Error("Session claims are closed");if(r(this.databasePath)&&canonicalSessionDatabasePath(this.databasePath)!==this.databasePath)throw new SessionOccupiedError("",void 0,"ambiguous_library_alias")}reap(e){let i="unknown";try{i=this.host.probe(e.identity)}catch{}return"dead"===i&&(this.db.prepare("DELETE FROM claims WHERE thread_id = ? AND claim_id = ?").run(e.thread_id,e.claim_id),!0)}acquire(e){if(!e)throw new Error("Thread ID must be nonempty");return this.acquireKey(e)}acquireKey(i){this.checkOpen();const r=this.held.get(i);if(void 0!==r)return this.assert(r.claim),r.references++,this.handle(r.claim);const t={thread_id:i,claim_id:e(),identity:this.host.identity,owner_json:JSON.stringify(this.host.owner)};for(let e=0;e<32;e++){let e;this.db.exec("BEGIN IMMEDIATE");try{e=this.read(u)??(i===u?this.db.prepare("SELECT * FROM claims LIMIT 1").all().map(e=>l.parse(e))[0]:this.read(i)),void 0===e&&this.db.prepare("INSERT INTO claims VALUES (?, ?, ?, ?)").run(i,t.claim_id,t.identity,t.owner_json),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}if(void 0===e)return this.held.set(i,{claim:t,references:1}),this.handle(t);if(!this.reap(e))throw new SessionOccupiedError(i,e)}throw new SessionOccupiedError(i)}maintenance(){return this.acquireKey(u)}assert(e){if(this.checkOpen(),this.read(e.thread_id)?.claim_id!==e.claim_id)throw new SessionOccupiedError(e.thread_id,void 0,"stale_session_claim")}handle(e){let i=!1;return{threadId:e.thread_id,token:e.claim_id,assert:()=>{if(i)throw new SessionOccupiedError(e.thread_id,void 0,"stale_session_claim");this.assert(e)},release:()=>{if(i)return;i=!0;const r=this.held.get(e.thread_id);r?.claim.claim_id===e.claim_id&&(r.references--,0===r.references&&(this.db.prepare("DELETE FROM claims WHERE thread_id = ? AND claim_id = ?").run(e.thread_id,e.claim_id),this.held.delete(e.thread_id)))}}}close(){if(!this.closed){if(0!==this.held.size)throw new Error("Cannot close ownership store with unsettled session claims");this.db.close(),this.closed=!0}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_ERROR_CODES as
|
|
1
|
+
import{SessionOccupiedError as e}from"../ownership/session-claims.js";import{RUNTIME_ERROR_CODES as s,jsonValueSchema as t}from"@roll-agent/protocol";import{createSafeCapabilitySnapshot as o}from"../engine/capability-manifest.js";import{isPersistedToolExecutionRecord as r}from"../tool-bridge/tool-execution-record.js";import{RuntimeServiceError as i}from"../service/runtime-service.js";import{RuntimeClientRequestCoordinator as n}from"./runtime-client-request-coordinator.js";import{RuntimeProtocolAdapter as a}from"./runtime-protocol-adapter.js";import{EVENT_NOTIFICATION as c,RpcMethod as d,abortParamsSchema as l,approveParamsSchema as u,capabilitiesParamsSchema as p,closeParamsSchema as h,compactParamsSchema as m,createParamsSchema as f,isRequest as v,messagesParamsSchema as w,rejectParamsSchema as g,resumeParamsSchema as y,sendParamsSchema as R,toolExecutionsParamsSchema as I}from"./protocol.js";function S(e){return e instanceof Error?e.message:String(e)}function C(e){return e.map(e=>{const{providerOptions:s,...t}=e;if("string"==typeof t.content)return t;const o=t.content.filter(e=>"reasoning"!==e.type).map(e=>{if(!("providerOptions"in e))return e;const{providerOptions:s,...t}=e;return t});return{...t,content:o}}).filter(e=>!("assistant"===e.role&&Array.isArray(e.content)&&0===e.content.length))}class MethodNotFoundError extends Error{}class RuntimeTransportWriteError extends Error{}function b(e){return e instanceof Error&&"ZodError"===e.name&&"issues"in e&&Array.isArray(e.issues)}function A(s,o){if(s instanceof i||s instanceof e){const o=t.safeParse(s.details);return{code:-32e3,message:s.message,data:{rollCode:s instanceof e?"THREAD_BUSY":s.rollCode,retryable:s instanceof e||s.retryable,...o.success?{details:o.data}:{}}}}return b(s)?{code:-32602,message:"Invalid params",data:{rollCode:"INVALID_PARAMS",retryable:!1}}:s instanceof MethodNotFoundError?{code:-32601,message:s.message}:{code:-32603,message:o?"Internal error":S(s),data:{rollCode:"INTERNAL_ERROR",retryable:!1}}}const E={unselected:"unselected",legacy:"legacy",runtime:"runtime"},q=new Set(Object.values(d));export class RuntimeServer{engine;connection;authorizeRawToolEvidence;sessions=new Map;runtimeService;runtimeClientRequests;protocolAdapter;connectionMode=E.unselected;abortPromise;constructor(e,s,t={}){this.engine=e,this.connection=s,this.authorizeRawToolEvidence=t.authorizeRawToolEvidence,this.runtimeService=t.runtimeService,this.runtimeClientRequests=void 0===t.runtimeService?void 0:t.runtimeClientRequests??new n,this.protocolAdapter=void 0===t.runtimeService||void 0===this.runtimeClientRequests?void 0:new a(t.runtimeService,s,this.runtimeClientRequests);try{this.connection.onMessage(e=>this.handleMessage(e))}catch(e){throw this.protocolAdapter?.disposeConstructionFailure(),e}}async abortAll(){this.abortPromise??=this.performAbortAll(),await this.abortPromise}async performAbortAll(){const e=[...this.sessions.values()];this.sessions.clear();const s=this.protocolAdapter?.close();this.runtimeClientRequests?.cancelAll("Runtime Server 已关闭");try{await Promise.all([...e.map(e=>e.close()),...void 0===s?[]:[s],...void 0===this.runtimeService?[]:[this.runtimeService.close()]])}finally{this.protocolAdapter?.releaseServiceControl()}}handleMessage(e){if(!0===this.protocolAdapter?.handleResponse(e))return;if(!v(e))return;const s=!0===this.protocolAdapter?.handles(e.method);try{this.selectConnectionMode(e.method,s)}catch(t){return void this.sendOrClose({jsonrpc:"2.0",id:e.id,error:A(t,s)})}this.dispatch(e).then(s=>{const t=this.sendOrClose({jsonrpc:"2.0",id:e.id,result:s.result});s.afterResponse?.(t)},t=>{t instanceof RuntimeTransportWriteError||this.sendOrClose({jsonrpc:"2.0",id:e.id,error:A(t,s)})})}sendOrClose(e){try{return this.connection.send(e),!0}catch{try{this.connection.close()}catch{}return!1}}sendNotificationOrThrow(e){if(!this.sendOrClose(e))throw new RuntimeTransportWriteError("Runtime transport write failed")}requireSession(e){const s=this.sessions.get(e);if(!s)throw new Error(`Session "${e}" 不存在`);return s}selectConnectionMode(e,t){if(t){if(this.connectionMode===E.legacy)throw new i(s.capabilityUnavailable,"当前连接已选择 legacy session.* 协议,不能切换到 Runtime Protocol");this.connectionMode=E.runtime}else if(q.has(e)){if(this.connectionMode===E.runtime)throw new i(s.capabilityUnavailable,"当前连接已选择 Runtime Protocol,不能调用 legacy session.* 方法");this.connectionMode=E.legacy}}async dispatch(e){return!0===this.protocolAdapter?.handles(e.method)?this.protocolAdapter.dispatch(e):{result:await this.dispatchLegacy(e)}}async dispatchLegacy(e){switch(e.method){case d.Create:{const s=f.parse(e.params),t=await this.engine.createSession(s.title?{title:s.title}:{});return this.sessions.set(t.id,t),{sessionId:t.id}}case d.Resume:{const s=y.parse(e.params),t=await this.engine.resumeSession(s.threadId);return this.sessions.set(t.id,t),{sessionId:t.id}}case d.Send:{const s=R.parse(e.params),t=this.requireSession(s.sessionId);for await(const e of t.send(s.input))"reasoning-delta"!==e.type&&this.sendNotificationOrThrow({jsonrpc:"2.0",method:c,params:{sessionId:s.sessionId,event:"confirmation-required"===e.type?{...e,fullDiff:void 0}:e}});return{status:"completed"}}case d.Approve:{const s=u.parse(e.params);return{resolved:this.requireSession(s.sessionId).approve(s.approvalId)}}case d.Reject:{const s=g.parse(e.params);return{resolved:this.requireSession(s.sessionId).reject(s.approvalId,s.reason)}}case d.Abort:{const s=l.parse(e.params);return{ok:!0,cancelled:this.requireSession(s.sessionId).cancel()}}case d.Close:{const s=h.parse(e.params),t=this.requireSession(s.sessionId);return await t.close(),this.sessions.delete(s.sessionId),{closed:!0}}case d.Messages:{const s=w.parse(e.params);return{messages:C(this.requireSession(s.sessionId).getMessages())}}case d.ToolExecutions:{const s=I.parse(e.params),t=this.requireSession(s.sessionId);if(s.includeRaw&&!await(this.authorizeRawToolEvidence?.({sessionId:s.sessionId,...void 0!==s.executionId?{executionId:s.executionId}:{}})))throw new Error("Raw Tool evidence access denied by RuntimeServer policy");if(void 0!==s.executionId){const e=t.getToolExecution(s.executionId,s.includeRaw);if(s.includeRaw&&void 0!==e&&!r(e))throw new Error("Raw Tool evidence is unavailable without a durable ledger projection");return{record:e}}const o=t.getToolExecutions({...void 0!==s.afterSequence?{afterSequence:s.afterSequence}:{},...void 0!==s.limit?{limit:s.limit}:{},...void 0!==s.toolCallId?{toolCallId:s.toolCallId}:{}},s.includeRaw);if(s.includeRaw&&o.some(e=>!r(e)))throw new Error("Raw Tool evidence is unavailable without a durable ledger projection");return{records:o}}case d.Capabilities:{const s=p.parse(e.params),t=this.requireSession(s.sessionId);return o(t.getCapabilityManifest(),t.getCapabilityTurnContext())}case d.Compact:{const s=m.parse(e.params),t=this.requireSession(s.sessionId);for await(const e of t.compact("manual"))this.sendNotificationOrThrow({jsonrpc:"2.0",method:c,params:{sessionId:s.sessionId,event:e}});return{status:"completed"}}default:throw new MethodNotFoundError(`Method not found: ${e.method}`)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,
|
|
1
|
+
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,readFileSync as i,rmSync as d,writeFileSync as n}from"node:fs";import{basename as c,extname as o,isAbsolute as m,join as h}from"node:path";import{attachmentIdSchema as u,attachmentMediaTypeSchema as l,RUNTIME_ERROR_CODES as g,RUNTIME_V14_MAX_ATTACHMENT_BYTES as p,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as f,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as y,SUPPORTED_ATTACHMENT_MEDIA_TYPES as k}from"@roll-agent/protocol";const x=6e5,I=18e5,S={"image/png":["png"],"image/jpeg":["jpg","jpeg"],"image/gif":["gif"],"image/webp":["webp"],"application/pdf":["pdf"],"text/plain":["txt","log","text"],"text/markdown":["md","markdown"]};function v(e){const t=c(e).replace(/[\\/:]+/gu,"").replace(/\p{Cc}+/gu,"").trim();return t.length>0?t:"attachment"}function T(t){return e("sha256").update(t).digest("hex")}function B(e,t){const a=S[t];if(void 0===a)return!1;const s=o(e).slice(1).toLowerCase();return a.includes(s)}export class AttachmentStore{dir;maxAttachmentBytes;maxStagedAttachments;stagedTtlMs;committedTtlMs;now;records=new Map;closed=!1;constructor(e){this.maxAttachmentBytes=e.maxAttachmentBytes??p,this.maxStagedAttachments=e.maxStagedAttachments??y,this.stagedTtlMs=e.stagedTtlMs??x,this.committedTtlMs=e.committedTtlMs??I,this.now=e.now??Date.now,this.dir=h(e.dir,t()),r(this.dir,{recursive:!0})}get limits(){return{maxAttachmentBytes:this.maxAttachmentBytes,maxAttachmentChunkBytes:f,maxStagedAttachments:this.maxStagedAttachments}}stage(e){if(this.sweepExpired(),e.bytes>this.maxAttachmentBytes)return{ok:!1,code:g.attachmentTooLarge,message:`附件超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(!(e.mediaType in S))return{ok:!1,code:g.attachmentTypeUnsupported,message:`不支持的附件类型: ${e.mediaType}(支持 ${k.join(", ")})`};if(!B(e.fileName,e.mediaType))return{ok:!1,code:g.attachmentTypeUnsupported,message:`文件扩展名与 mediaType "${e.mediaType}" 不一致`};if(this.records.size>=this.maxStagedAttachments)return{ok:!1,code:g.attachmentQuotaExceeded,message:`附件数已达上限 ${String(this.maxStagedAttachments)}:附件被 turn.start 引用后不会自动释放,请调用 attachment.release 释放不再需要的附件,或等待 TTL 回收`,retryable:!0};const a=u.parse(t()),s={attachmentId:a,threadId:e.threadId,fileName:e.fileName,displayName:v(e.fileName),mediaType:e.mediaType,declaredBytes:e.bytes,declaredSha256:e.sha256,source:e.source,createdAtMs:this.now(),state:"staged",receivedBytes:0,nextSequence:0,committedAtMs:void 0};if("chunks"===e.source)return n(this.filePath(a),Buffer.alloc(0)),this.records.set(a,s),{ok:!0,attachmentId:a,state:"staged"};const r=this.loadLocalFile(e.sourcePath??"",e.mediaType,e.bytes,e.sha256);return r.ok?(n(this.filePath(a),r.data),s.state="committed",s.receivedBytes=r.data.length,s.committedAtMs=this.now(),this.records.set(a,s),{ok:!0,attachmentId:a,state:"committed",descriptor:this.describe(s)}):r}appendChunk(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("staged"!==t.state||"chunks"!==t.source)return{ok:!1,code:g.invalidParams,message:"附件不处于 chunk 上传状态"};if(e.sequence!==t.nextSequence)return{ok:!1,code:g.invalidParams,message:`chunk sequence 不连续:期望 ${String(t.nextSequence)},收到 ${String(e.sequence)}`};const s=Buffer.from(e.dataBase64,"base64");return 0===s.length||s.length>f?{ok:!1,code:g.invalidParams,message:`chunk 大小必须在 1..${String(f)} 字节内`}:t.receivedBytes+s.length>t.declaredBytes?(this.remove(t.attachmentId),{ok:!1,code:g.attachmentTooLarge,message:"累计上传字节数超过申报大小,附件已回收"}):(a(this.filePath(t.attachmentId),s),t.receivedBytes+=s.length,t.nextSequence+=1,{ok:!0,receivedBytes:t.receivedBytes,nextSequence:t.nextSequence})}commit(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"===t.state)return{ok:!0,descriptor:this.describe(t)};if(t.receivedBytes!==t.declaredBytes)return{ok:!1,code:g.attachmentUploadIncomplete,message:`上传未完成:已收 ${String(t.receivedBytes)} 字节,申报 ${String(t.declaredBytes)} 字节`};return T(i(this.filePath(t.attachmentId)))!==t.declaredSha256?(this.remove(t.attachmentId),{ok:!1,code:g.attachmentHashMismatch,message:"附件内容 sha256 与申报值不匹配,附件已回收"}):(t.state="committed",t.committedAtMs=this.now(),{ok:!0,descriptor:this.describe(t)})}release(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);return void 0===t?{ok:!0,released:!1}:(this.remove(t.attachmentId),{ok:!0,released:!0})}readCommitted(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"!==t.state)return{ok:!1,code:g.attachmentNotCommitted,message:`附件 "${e.attachmentId}" 尚未 commit`};let a;try{a=i(this.filePath(t.attachmentId))}catch{return this.remove(t.attachmentId),{ok:!1,code:g.internalError,message:"附件数据文件缺失,附件已回收"}}return{ok:!0,descriptor:this.describe(t),dataBase64:a.toString("base64")}}releaseThread(e){for(const t of[...this.records.values()])t.threadId===e&&this.remove(t.attachmentId)}close(){this.closed||(this.closed=!0,this.records.clear(),d(this.dir,{recursive:!0,force:!0}))}describe(e){return{attachmentId:e.attachmentId,fileName:e.fileName,displayName:e.displayName,mediaType:l.parse(e.mediaType),bytes:e.declaredBytes,sha256:e.declaredSha256,source:e.source,createdAt:new Date(e.createdAtMs).toISOString()}}loadLocalFile(e,t,a,r){if(!m(e))return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 必须是绝对路径"};if(!B(e,t))return{ok:!1,code:g.attachmentTypeUnsupported,message:`sourcePath 扩展名与 mediaType "${t}" 不一致`};let d;try{d=s(e)}catch{return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不存在或不可读"}}if(d.isSymbolicLink())return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不允许是符号链接"};if(!d.isFile())return{ok:!1,code:g.attachmentPathRejected,message:"sourcePath 不是普通文件"};if(d.size>this.maxAttachmentBytes)return{ok:!1,code:g.attachmentTooLarge,message:`文件实际大小 ${String(d.size)} 超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(d.size!==a)return{ok:!1,code:g.invalidParams,message:`文件实际大小 ${String(d.size)} 与申报 ${String(a)} 不一致`};const n=i(e);return T(n)!==r?{ok:!1,code:g.attachmentHashMismatch,message:"文件内容 sha256 与申报值不匹配"}:{ok:!0,data:n}}findOwned(e,t){const a=this.records.get(t);if(void 0!==a&&a.threadId===e)return a}notFound(e){return{ok:!1,code:g.attachmentNotFound,message:`附件 "${e}" 不存在、已过期或不属于该 Thread`}}remove(e){this.records.delete(e),d(this.filePath(e),{force:!0})}sweepExpired(){const e=this.now();for(const t of[...this.records.values()]){const a="staged"===t.state?this.stagedTtlMs:this.committedTtlMs;e-(t.committedAtMs??t.createdAtMs)>a&&this.remove(t.attachmentId)}}filePath(e){return h(this.dir,`${e}.bin`)}}
|
|
@@ -5,7 +5,7 @@ import type { SessionUserInputRequestId } from "../interaction/user-input-intera
|
|
|
5
5
|
import type { ReasoningPresentation } from "../types/events.ts";
|
|
6
6
|
import { ThreadStore, type StoredRuntimeEvent } from "../store/thread-store.ts";
|
|
7
7
|
type RollErrorCode = RuntimeProtocolErrorDataV14["rollCode"];
|
|
8
|
-
export type RuntimeServiceSession = Pick<AgentSession, "id" | "send" | "approve" | "reject" | "cancel" | "close" | "getCapabilityManifest" | "getCapabilityTurnContext"> & Partial<Pick<AgentSession, "setUserInputAvailable" | "resolveUserInput" | "cancelUserInput">>;
|
|
8
|
+
export type RuntimeServiceSession = Pick<AgentSession, "id" | "send" | "approve" | "reject" | "cancel" | "close" | "getCapabilityManifest" | "getCapabilityTurnContext"> & Partial<Pick<AgentSession, "setUserInputAvailable" | "resolveUserInput" | "cancelUserInput" | "assertWritable" | "addCloseBarrier">>;
|
|
9
9
|
export interface RuntimeServiceEngine {
|
|
10
10
|
createSession(input?: {
|
|
11
11
|
readonly title?: string;
|
|
@@ -74,11 +74,17 @@ export declare class MutationRequestCache {
|
|
|
74
74
|
private readonly settled;
|
|
75
75
|
constructor(maxEntries: number);
|
|
76
76
|
run<TMethod extends MutationRuntimeMethod>(requestId: string, method: TMethod, params: LatestRuntimeMethodParams<TMethod>, action: () => LatestRuntimeMethodResult<TMethod> | Promise<LatestRuntimeMethodResult<TMethod>>): Promise<LatestRuntimeMethodResult<TMethod>>;
|
|
77
|
+
settlePending(): Promise<void>;
|
|
77
78
|
clear(): void;
|
|
78
79
|
private assertMatchingRequest;
|
|
79
80
|
private settle;
|
|
80
81
|
}
|
|
81
82
|
export declare class RuntimeService {
|
|
83
|
+
private closePromise;
|
|
84
|
+
private readonly openingSessions;
|
|
85
|
+
private readonly closingThreads;
|
|
86
|
+
private readonly ownership;
|
|
87
|
+
private readonly turnJobs;
|
|
82
88
|
readonly runtimeInstanceId: RuntimeInstanceId;
|
|
83
89
|
private readonly engine;
|
|
84
90
|
private readonly store;
|
|
@@ -145,6 +151,9 @@ export declare class RuntimeService {
|
|
|
145
151
|
private assertOpen;
|
|
146
152
|
private requireThread;
|
|
147
153
|
private requireSession;
|
|
154
|
+
private assertThreadOpen;
|
|
155
|
+
private retainOwnership;
|
|
156
|
+
private finishThread;
|
|
148
157
|
private rememberSession;
|
|
149
158
|
private emit;
|
|
150
159
|
private notifyFatalError;
|
|
@@ -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
|
-
|
|
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
|
+
};
|