@roll-agent/runtime 0.23.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/agent-session.d.ts +8 -3
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/cancelled-turn-recovery.d.ts +2 -2
- package/dist/engine/capability-manifest.d.ts +3 -0
- package/dist/engine/capability-manifest.js +1 -1
- package/dist/engine/compaction-checkpoint.d.ts +268 -268
- package/dist/engine/compaction-semantic-state.d.ts +124 -124
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/engine/explicit-skill-context.d.ts +2 -2
- package/dist/engine/observation-projection.d.ts +27 -0
- package/dist/engine/observation-projection.js +1 -0
- package/dist/engine/observation-readback.d.ts +6 -0
- package/dist/engine/observation-readback.js +1 -0
- package/dist/server/runtime-protocol-adapter.js +1 -1
- package/dist/service/runtime-service.d.ts +5 -4
- package/dist/service/runtime-service.js +1 -1
- package/dist/store/thread-origin.d.ts +8 -8
- package/dist/store/thread-store.d.ts +14 -3
- package/dist/store/thread-store.js +1 -1
- package/dist/tool-bridge/app-output-content.d.ts +2 -0
- package/dist/tool-bridge/app-output-content.js +1 -0
- package/dist/tool-bridge/build-tools.d.ts +4 -1
- package/dist/tool-bridge/build-tools.js +1 -1
- package/dist/tool-bridge/naming.d.ts +2 -0
- package/dist/tool-bridge/naming.js +1 -1
- package/dist/tool-bridge/normalize-app-output.d.ts +5 -0
- package/dist/tool-bridge/normalize-app-output.js +1 -0
- package/dist/tool-bridge/normalize-result.d.ts +3 -1
- package/dist/tool-bridge/normalize-result.js +1 -1
- package/dist/tool-bridge/observation-recall-tool.d.ts +22 -0
- package/dist/tool-bridge/observation-recall-tool.js +1 -0
- package/dist/tool-bridge/schedule-create-input.d.ts +12 -12
- package/dist/tool-bridge/tool-approval-continuation.js +1 -1
- package/dist/tool-bridge/tool-execution-record.d.ts +5 -0
- package/dist/tool-bridge/tool-execution-record.js +1 -1
- package/dist/tool-bridge/transcript-tool.d.ts +2 -2
- package/dist/types/cancellation.d.ts +2 -2
- package/dist/types/events.d.ts +2 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as s,providerAcceptsToolSchemaIssues as i}from"@roll-agent/core/llm/providers";import{AgentStore as o}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as r}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as n,startAgent as a,waitForAgentReady as l}from"@roll-agent/core/registry/process-manager";import{acquireAgentUsageLease as c,getExitedAgentUsageNames as d}from"@roll-agent/core/registry/agent-usage-lease";import{formatToolSchemaIssue as h,normalizeListedTools as u}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as p}from"@roll-agent/core/config/helpers";import{catalogPackageSpec as g,getAgentCatalog as m}from"@roll-agent/core/registry/catalog";import{resolveAgentCatalog as v}from"@roll-agent/core/registry/catalog-discovery";import{installAgent as f}from"@roll-agent/core/registry/install";import{createSkillLibrary as w}from"@roll-agent/core/skills/library";import{ROLL_RESOURCE_HINTS_META_KEY as y}from"../tool-bridge/build-tools.js";import{TOOL_RESOURCE_ACCESS_MODES as S,TOOL_RESOURCE_HINT_KINDS as C}from"../tool-bridge/tool-execution-coordinator.js";import{AgentSession as b}from"./agent-session.js";import{resolveModelContextWindow as x}from"./context-window.js";import{createDefaultModelCatalog as k}from"./model-catalog-default.js";import{createWorkspaceInstructionsSource as A,parseWorkspaceInstructionsSetting as O}from"./workspace-instructions.js";import{CAPABILITY_HOST_MODES as E,shouldOfferAgentInstall as P,shouldOfferScheduleCreate as M}from"./capability-manifest.js";import{}from"../tool-bridge/bash-tool.js";import{unknownCommandClassifier as T}from"../types/command-classification.js";import{resolveShellProfile as N}from"../bash/profile.js";import{inspectGitVcsContext as I}from"./vcs-context.js";import{AGENT_BOOTSTRAP_MAX_CONCURRENCY as L,mapWithBoundedConcurrency as R}from"./agent-bootstrap.js";import{createToolLedgerGapRecoveryMessage as U}from"./cancelled-turn-recovery.js";const B=80,j="ConversationEngine is closing",$=3e4,F=6e4,D={connected:"connected",failed:"failed"};export function buildSessionExecSettings(e){const t=e.config.runtime.shell;return{workdir:process.cwd(),profile:e.profile,maxSessions:t.session.maxSessions,defaultYieldMs:t.session.defaultYieldMs,maxOutputTokens:t.session.maxOutputTokens,bufferCapacity:t.maxCaptureBytes,env:e.env,...e.onCommandSpawn?{onCommandSpawn:e.onCommandSpawn}:{}}}export function buildSessionBashSettings(e){const t=e.config.runtime.shell;return{workdir:process.cwd(),defaultTimeoutMs:t.defaultTimeoutMs,maxTimeoutMs:t.maxTimeoutMs,turnTimeoutMs:e.config.runtime.turnTimeoutMs,maxCaptureBytes:t.maxCaptureBytes,maxModelOutputChars:t.maxModelOutputChars,profile:e.profile,env:e.env,...e.onCommandSpawn?{onCommandSpawn:e.onCommandSpawn}:{}}}class AgentBootstrapTimeoutError extends Error{constructor(e){super(`Agent bootstrap timed out after ${String(e)}ms`),this.name="AgentBootstrapTimeoutError"}}class AgentBootstrapCleanupError extends AggregateError{cleanupErrors;constructor(e,t,s){const i=s.map(e=>e instanceof Error?e.message:String(e)).join("; ");super([t,...s],`Agent "${e}" bootstrap failed and resource cleanup also failed: ${i}`),this.name="AgentBootstrapCleanupError",this.cleanupErrors=s}}function W(){return new Error(j)}function H(e){return e.reason??new Error("Operation aborted")}function q(e){if(!0===e?.aborted)throw H(e)}function V(e,t){return void 0===t?e:Math.max(1,Math.min(e,t-Date.now()))}function _(e){if(void 0!==e&&Date.now()>=e)throw new Error("Agent bootstrap deadline expired")}function K(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const s={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(s.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(s.destructiveHint=t.destructiveHint),void 0===s.readOnlyHint&&void 0===s.destructiveHint?void 0:s}function Y(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(e){return Object.values(C).some(t=>t===e)}function G(e){return Object.values(S).some(t=>t===e)}function J(e){return{hints:void 0,issue:e}}function Q(e){if(!Y(e)||!Y(e._meta))return{hints:void 0,issue:void 0};if(!Object.hasOwn(e._meta,y))return{hints:void 0,issue:void 0};const t=e._meta[y];if(!Array.isArray(t))return J("必须是数组");const s=[];for(const[e,i]of t.entries()){if(!Y(i)||"string"!=typeof i.field||0===i.field.trim().length||"string"!=typeof i.kind||!z(i.kind)||void 0!==i.mode&&("string"!=typeof i.mode||!G(i.mode))||void 0!==i.namespace&&"string"!=typeof i.namespace||i.kind===C.custom&&("string"!=typeof i.namespace||0===i.namespace.trim().length))return J(`第 ${String(e+1)} 项字段无效`);const t=i.kind===C.custom&&"string"==typeof i.namespace?i.namespace.trim():void 0;s.push({field:i.field.trim(),kind:i.kind,...void 0!==i.mode?{mode:i.mode}:{},...void 0!==t?{namespace:t}:{}})}return{hints:s.length>0?s:void 0,issue:void 0}}function X(e){return"retry"===e.type?`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`:"warn"===e.type?`警告:${e.message}`:e.message}async function Z(e,t,s,i){q(i),"core-managed"===e.runtime.ownership&&(void 0===n(t,e.skill.name)&&a(e,t,s),await l(e,{...i?{signal:i}:{}}))}function ee(e){if(null===e.workspaceInstructions)return;if(void 0!==e.workspaceInstructions)return e.workspaceInstructions;const t=process.cwd();return A({cwd:t,setting:O(e.config.chat.instructions,t),...e.onWorkspaceInstructionsIssue?{onIssue:e.onWorkspaceInstructionsIssue}:{}})}export class ConversationEngine{config;clientManager;store;policy;fileToolsEnabled;maxSteps;providerOptions;modelOverride;modelNameOverride;providerNameOverride;switchingProviderName;modelCatalog;structuredOutputProviderOptions;structuredOutputReasoning;acquireAgentUsage;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;workspaceInstructions;onAgentBootstrapIssue;hostMode;scheduleTools;resolveDynamicCapabilityContext;sessionExecEnabled;explicitShellProfile;resolveShellProfileFn;installAgentFn;resolveCatalogFn;inspectVcsContext;shellEnv;onShellCommandSpawn;ready;refreshChain=Promise.resolve();resolvedCatalog;shellProfileResolution;shellUnsupportedWarned=!1;liveSessions=new Map;agentUsageLeases=new Map;shutdownController=new AbortController;disposePromise;closing=!1;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.modelCatalog=e.modelCatalog??k(),this.policy=e.policy,this.fileToolsEnabled=e.fileToolsEnabled??!0,this.maxSteps=e.maxSteps??B,this.providerOptions=e.providerOptions,this.structuredOutputProviderOptions=e.structuredOutputProviderOptions,this.structuredOutputReasoning=e.structuredOutputReasoning,this.acquireAgentUsage=e.acquireAgentUsage??(e.ensureAgentReady?async(t,s,i)=>{await(e.ensureAgentReady?.(t,s,i))}:(e,t,s)=>this.acquireDefaultAgentUsage(e,t,s)),this.debugEvents=e.debugEvents??!1,this.hostMode=e.hostMode??E.embedded,this.scheduleTools=e.scheduleTools??void 0,this.resolveDynamicCapabilityContext=e.resolveDynamicCapabilityContext,this.sessionExecEnabled=e.sessionExecEnabled??!0,this.explicitShellProfile=e.shellProfile,this.resolveShellProfileFn=e.resolveShellProfileFn??N,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.workspaceInstructions=ee(e),this.onAgentBootstrapIssue=e.onAgentBootstrapIssue,this.installAgentFn=e.installAgentFn??f,this.resolveCatalogFn=e.resolveCatalogFn??v,this.inspectVcsContext=e.inspectVcsContext??I,this.shellEnv={...e.shellEnv??process.env},this.onShellCommandSpawn=e.onShellCommandSpawn}async acquireDefaultAgentUsage(e,t,s){const i=await c(e,this.config.agents.dataDir,t,{holderKind:"chat",startIfStopped:!0,...s?{signal:s}:{}});if(void 0!==i)return i;await Z(e,this.config.agents.dataDir,t,s)}async createSession(t={}){this.assertAcceptingSessions();const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.store?this.store.createThread({...void 0===t.id?{}:{id:t.id},...void 0===t.origin?{}:{origin:t.origin},...t.title?{title:t.title}:{},model:this.resolveModelName()}):t.id??e();return this.buildSession(s,i,[])}async resumeSession(e){if(this.assertAcceptingSessions(),!this.store)throw new Error("resumeSession requires a ThreadStore");if("scheduled"===this.store.getThread(e)?.origin.kind)throw new Error("定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.liveSessions.get(e);if(void 0!==t)return t;if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.liveSessions.get(e);if(void 0!==i)return i;this.recoverUncoveredToolExecutionContext(e);const o=this.store.loadSessionState(e);return this.buildSession(s,e,o.messages,o.checkpoint)}async forkSession(e,t={}){if(this.assertAcceptingSessions(),!this.store)throw new Error("forkSession requires a ThreadStore");const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.store.forkSnapshot(e,{...t,model:this.resolveModelName()});try{this.recoverUncoveredToolExecutionContext(i);const e=this.store.loadSessionState(i);return this.buildSession(s,i,e.messages,e.checkpoint)}catch(e){throw this.store.deleteThread(i),e}}assertAcceptingSessions(){if(this.closing)throw W()}resolveRuntimeShellProfile(){if(!this.config.runtime.shell.enabled)return;if(void 0!==this.explicitShellProfile)return this.explicitShellProfile??void 0;void 0===this.shellProfileResolution&&(this.shellProfileResolution=this.resolveShellProfileFn({platform:process.platform,env:this.shellEnv}));const e=this.shellProfileResolution;if(e.supported)return e.profile;if(!this.shellUnsupportedWarned){this.shellUnsupportedWarned=!0;const t="pwsh-version-unsupported"===e.reason?"检测到的 pwsh 版本低于 7":"未检测到 PowerShell 7 (pwsh)";process.stderr.write(`roll chat: ${t},Windows 原生 shell 工具已跳过注册;可运行 winget install Microsoft.PowerShell\n`)}}resolveShellSettings(e){return buildSessionBashSettings({config:this.config,profile:e,env:this.shellEnv,...this.onShellCommandSpawn?{onCommandSpawn:this.onShellCommandSpawn}:{}})}resolveSessionExecSettings(e){if(!this.sessionExecEnabled)return;const t=this.config.runtime.shell;return t.enabled&&t.session.enabled&&e.supportsSessionExec?buildSessionExecSettings({config:this.config,profile:e,env:this.shellEnv,...this.onShellCommandSpawn?{onCommandSpawn:this.onShellCommandSpawn}:{}}):void 0}buildSession(e,t,s,i){const o=this.store,r=o?.getThread(t)?.derivedFrom,n=this.resolveContextWindowFor(this.resolveProviderName(),this.resolveModelName())?.window,a=(e.skillLibrary?.list()??[]).length>0?e.skillLibrary:void 0,l=this.resolveRuntimeShellProfile(),c=l?this.resolveShellSettings(l):void 0,d=this.fileToolsEnabled?{workdir:c?.workdir??process.cwd()}:void 0,h=l?this.resolveSessionExecSettings(l):void 0,u=l?l.supportsSafeCommandClassification&&this.config.runtime.shell.autoApproveSafe?l:T:void 0,p=P(this.hostMode)?this.resolveAgentInstallBinding():void 0,g=this.scheduleTools,m=this.composeCapabilityContext(e.sources.length,l),v=new b({id:t,model:this.activeModel(e),sources:e.sources,toolSchemaPolicy:this.toolSchemaPolicyFor(this.resolveProviderName()),onToolExcluded:e=>this.onAgentBootstrapIssue?.({agentName:e.agentName,message:`工具 "${e.toolName}" 的参数 schema 含当前 provider "${this.resolveToolSchemaProviderName()}" 无法接受的引用(${e.issues.map(e=>e.ref).join("、")}),已从本会话工具集移除`}),capabilityContext:m,resolveDynamicCapabilityContext:async e=>{const[t,s]=await Promise.all([this.inspectVcsContext(m.cwd),this.resolveDynamicCapabilityContext?.(e)??{}]),i=s.vcs??t;return{...s.ruleIds?{ruleIds:s.ruleIds}:{},...i?{vcs:i}:{},...s.origin?{origin:s.origin}:{}}},...a?{skillLibrary:a}:{},...this.workspaceInstructions?{workspaceInstructions:this.workspaceInstructions}:{},...d?{fileTools:d}:{},...c?{bash:c}:{},...u?{bashClassifier:u}:{},...h?{bashSession:h}:{},...p?{agentInstall:p}:{},...g?{schedules:{port:g,sessionCwd:m.cwd,includeCreate:M(this.hostMode)}}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...this.structuredOutputProviderOptions?{structuredOutputProviderOptions:this.structuredOutputProviderOptions}:{},...this.structuredOutputReasoning?{structuredOutputReasoning:this.structuredOutputReasoning}:{},onProviderOptionsChange:e=>this.syncProviderOptions(e),...void 0!==n?{contextWindow:n}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...void 0!==r?{derivedFrom:r}:{},...i?{initialCheckpoint:i}:{},...o?{onPersist:(e,s)=>o.appendMessages(t,e,s),onReplace:e=>o.replaceMessages(t,e),onToolExecution:e=>o.appendToolExecution(t,e),listToolExecutions:e=>o.listToolExecutions(t,e),getToolExecution:e=>o.getToolExecution(t,e),recoverUncoveredToolExecutions:()=>this.recoverUncoveredToolExecutionContext(t),listTranscriptMessages:e=>o.listTranscriptMessages(t,e),commitCompaction:e=>o.commitCompaction(t,e),readCheckpointTranscript:e=>o.readCheckpointTranscript(t,e)}:{},onClose:()=>{this.liveSessions.get(t)===v&&this.liveSessions.delete(t)}});return this.liveSessions.set(t,v),v}recoverUncoveredToolExecutionContext(e){const t=this.store?.recoverUncoveredToolExecutions(e,U);return void 0===t?[]:[t]}syncProviderOptions(e){this.providerOptions=e,this.clientManager.setSamplingProviderOptions(e)}switchModel(e){this.assertAcceptingSessions();const t=this.resolveContextWindowFor(e.provider,e.modelName),s=t?.window,i={model:e.model,...void 0!==s?{contextWindow:s}:{},...e.providerOptions?{providerOptions:e.providerOptions}:{},...e.structuredOutputProviderOptions?{structuredOutputProviderOptions:e.structuredOutputProviderOptions}:{},...e.structuredOutputReasoning?{structuredOutputReasoning:e.structuredOutputReasoning}:{},toolSchemaPolicy:this.toolSchemaPolicyFor(e.provider)};for(const e of this.liveSessions.values())if(!e.canSwitchModel())throw new Error("存在正在生成回复的会话,稍后再切换模型");this.switchingProviderName=e.provider;try{for(const e of this.liveSessions.values())e.switchModel(i)}finally{this.switchingProviderName=void 0}this.modelOverride=e.model,this.modelNameOverride=e.modelName,this.providerNameOverride=e.provider,this.providerOptions=e.providerOptions,this.structuredOutputProviderOptions=e.structuredOutputProviderOptions,this.structuredOutputReasoning=e.structuredOutputReasoning,this.clientManager.setSamplingModel(e.model),this.clientManager.setSamplingProviderOptions(e.providerOptions);for(const t of this.liveSessions.keys())this.store?.updateModel(t,e.modelName);return t}ensureReady(){return this.assertAcceptingSessions(),this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=Date.now(),t=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:t,sources:this.explicitSources,...this.explicitSkillLibrary?{skillLibrary:this.explicitSkillLibrary}:{}};const s=this.explicitAgents??new o(this.config.agents.dataDir).list(),i=this.config.runtime.agentBootstrap.timeoutMs,r=e+i,n=new AbortController,a=new AgentBootstrapTimeoutError(i);let l=!1;const c=()=>{n.signal.aborted||n.abort(H(this.shutdownController.signal))},d=()=>{!this.closing&&!n.signal.aborted&&Date.now()>=r&&(l=!0,n.abort(a))};this.shutdownController.signal.aborted?c():this.shutdownController.signal.addEventListener("abort",c,{once:!0});const h=setTimeout(()=>{this.closing||n.signal.aborted||(l=!0,n.abort(a))},Math.max(0,r-Date.now())),u=(e,t)=>({kind:D.failed,issues:[{agentName:e.skill.name,message:a.message},...void 0===t?[]:[{agentName:e.skill.name,message:t.message}]]});try{if(this.agentInstallEnabled())try{this.resolvedCatalog=await this.resolveCatalogFn(this.config,{allowNetwork:!1,signal:n.signal,...this.config.install.registry?{registry:this.config.install.registry}:{}})}catch(e){if(this.closing||this.shutdownController.signal.aborted)throw W();if(d(),!l)throw e}if(this.closing||this.shutdownController.signal.aborted)throw W();if(d(),0===s.length){const e=this.resolveSkillLibrary(s);return{model:t,sources:[],...e?{skillLibrary:e}:{}}}const e=await R(s,L,async e=>{const s=[],i=e=>{s.push(e)};try{d(),q(n.signal);const o=await this.connectAgentSource(e,t,i,{signal:n.signal,deadlineAt:r});return{kind:D.connected,source:o,issues:s}}catch(t){if(this.closing||this.shutdownController.signal.aborted)throw W();return d(),l?u(e,t instanceof AgentBootstrapCleanupError?t:void 0):(s.push({agentName:e.skill.name,message:t instanceof Error?t.message:String(t)}),{kind:D.failed,issues:s})}},{signal:n.signal,onSkipped:e=>u(e)});if(this.closing||this.shutdownController.signal.aborted)throw W();const i=[];for(const t of e){for(const e of t.issues)this.onAgentBootstrapIssue?.(e);t.kind===D.connected&&i.push(t.source)}const o=this.resolveSkillLibrary(s);return{model:t,sources:i,...o?{skillLibrary:o}:{}}}finally{clearTimeout(h),this.shutdownController.signal.removeEventListener("abort",c)}}async connectAgentSource(e,t,s=e=>this.onAgentBootstrapIssue?.(e),i={}){this.assertAcceptingSessions(),q(i.signal),_(i.deadlineAt);const o=r(e),n=p(this.config,e.skill.name),a=this.agentUsageLeases.get(e.skill.name);let l,c;try{l=void 0===a?await this.acquireAgentUsage(e,n,i.signal):void 0,this.assertAcceptingSessions(),q(i.signal),_(i.deadlineAt);const r={timeoutMs:V($,i.deadlineAt),samplingModel:t,...this.providerOptions?{samplingProviderOptions:this.providerOptions}:{},...n?{env:n}:{},...i.signal?{signal:i.signal}:{}},d=this.clientManager.connectWithOwnership;c="function"==typeof d?await d.call(this.clientManager,e.skill.name,o,e.installPath,r):{client:await this.clientManager.connect(e.skill.name,o,e.installPath,r),commit:()=>{},rollback:async()=>{}},this.assertAcceptingSessions(),q(i.signal),_(i.deadlineAt);const p=(await c.client.listTools(void 0,{timeout:V(F,i.deadlineAt),...i.signal?{signal:i.signal}:{}})).tools;this.assertAcceptingSessions(),q(i.signal),_(i.deadlineAt);const g=u(p,{onSchemaIssue:t=>s({agentName:e.skill.name,message:h(e.skill.name,t)})}).map((t,i)=>{const o=Q(p[i]);return void 0!==o.issue&&s({agentName:e.skill.name,message:`Tool "${t.name}" 的 ${y} 无效(${o.issue}),已回退 Agent 级资源锁`}),{tool:t,annotations:K(p[i]),...o.hints?{resourceHints:o.hints}:{}}});return c.commit(),void 0!==l&&this.agentUsageLeases.set(e.skill.name,l),{agentName:e.skill.name,client:c.client,tools:g,...e.source?{agentSource:e.source.type}:{},transport:o.type,runtimeOwnership:e.runtime.ownership,..."stdio"===o.type?{resourceBaseDir:e.installPath}:{}}}catch(t){const s=[];if(void 0!==c&&await c.rollback().catch(e=>{s.push(e)}),void 0!==l){const t=l;await t.release().catch(i=>{this.agentUsageLeases.set(e.skill.name,t),s.push(i)})}if(s.length>0)throw new AgentBootstrapCleanupError(e.skill.name,t,s);throw t}}async prepareAgentRefresh(e){this.assertAcceptingSessions();const t=this.refreshChain.then(()=>this.runAgentRefresh(e));return this.refreshChain=t.then(()=>{},()=>{}),t}async runAgentRefresh(e){const t=await this.ensureReady(),s=await this.connectAgentSource(e,this.activeModel(t),void 0,{signal:this.shutdownController.signal}),i=[...t.sources.filter(e=>e.agentName!==s.agentName),s],r=this.explicitAgents??new o(this.config.agents.dataDir).list(),n=this.resolveSkillLibrary(r);this.ready=Promise.resolve({model:this.activeModel(t),sources:i,...n?{skillLibrary:n}:{}});const a=n?.list()??[],l=n&&a.length>0?n:void 0,c=this.resolveRuntimeShellProfile();return{source:s,...l?{skillLibrary:l}:{},capabilityContext:this.composeCapabilityContext(i.length,c)}}composeCapabilityContext(e,t){const s=this.resolveAgentOnboardingInfo();return{profile:t?.toolName??"no-shell",hostMode:this.hostMode,cwd:process.cwd(),platform:process.platform,...t?{shellHints:t.systemPromptHints()}:{},agentCount:e,...s?{agentOnboardingCatalog:s}:{}}}agentInstallEnabled(){return void 0===this.explicitSources&&void 0===this.explicitAgents}currentCatalog(){return this.resolvedCatalog??m(this.config)}resolveAgentOnboardingInfo(){if(!this.agentInstallEnabled())return;const e=this.currentCatalog();return 0!==e.length?e.map(e=>({shortName:e.shortName,description:e.description})):void 0}resolveAgentInstallBinding(){if(!this.agentInstallEnabled())return;const e=this.currentCatalog();return 0!==e.length?{catalog:e.map(e=>({shortName:e.shortName,description:e.description})),install:(e,t)=>this.installCatalogAgent(e,t)}:void 0}async installCatalogAgent(e,t){const s=this.currentCatalog().find(t=>t.shortName===e);if(!s)return{outcome:{ok:!1,message:`未知的官方 Agent 短名: ${e}`}};const i=await this.installAgentFn({packageSpec:g(s),skipBrowserSetup:!0,autoStart:!1,expectedSkillName:s.skillName},{agentsConfig:this.config.agents,installConfig:this.config.install,getStartEnv:e=>p(this.config,e),report:e=>t(X(e))});if(!i.ok)return{outcome:{ok:!1,message:i.message,...i.retryCommand?{retryCommand:i.retryCommand}:{}}};const o=i.agent,r="installed-package"===o.source?.type?o.source.installedVersion:void 0,n="core-managed"===o.runtime.ownership&&void 0!==o.runtime.setup?.playwright,a={ok:!0,agentName:o.skill.name,...r?{version:r}:{},missingEnv:(i.envReport?.missingRequired??[]).map(e=>e.name),...n?{retryCommand:`roll agent install ${s.shortName}`}:{},refreshApplied:!1};if((await this.ensureReady()).sources.some(e=>e.agentName===o.skill.name))return t(`Agent "${o.skill.name}" 本会话已接入旧版本连接,更新需重启 roll chat 生效。`),{outcome:a};try{return{outcome:a,refresh:await this.prepareAgentRefresh(o)}}catch(e){return t(`接入新 Agent 失败:${e instanceof Error?e.message:String(e)}`),{outcome:a}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:w({agents:e,extraDirs:this.config.skills.dirs,...this.onSkillLibraryIssue?{onIssue:this.onSkillLibraryIssue}:{}})}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),i=this.config.llm.providers[e];if(!i)throw new Error(`LLM provider "${e}" 未配置`);return s(e,t,i.apiKey,i.baseUrl)}resolveProviderName(){return this.providerNameOverride??this.config.runtime.provider??this.config.llm.defaultProvider}resolveContextWindowFor(e,t){return x({provider:e,model:t,...void 0!==this.config.runtime.contextWindow?{override:this.config.runtime.contextWindow}:{},catalog:this.modelCatalog})}resolveModelName(){return this.modelNameOverride??this.config.runtime.model??this.config.llm.defaultModel}toolSchemaPolicyFor(e){return t=>i(e,t)}resolveToolSchemaProviderName(){return this.switchingProviderName??this.resolveProviderName()}activeModel(e){return this.modelOverride??e.model}async getContextSummary(){const e=await this.ensureReady();this.assertAcceptingSessions();const t=this.workspaceInstructions?.current()?.path;return{agentCount:e.sources.length,toolCount:e.sources.reduce((e,t)=>e+t.tools.length,0),skillCount:e.skillLibrary?.list().length??0,...void 0!==t?{instructionsPath:t}:{}}}async releaseAgentUsageLeases(){const e=[...this.agentUsageLeases.entries()],t=await Promise.allSettled(e.map(async([e,t])=>{try{await t.release()}finally{this.agentUsageLeases.get(e)===t&&this.agentUsageLeases.delete(e)}}));for(const e of t)"rejected"===e.status&&process.stderr.write(`roll chat: Agent 使用租约释放失败: ${e.reason instanceof Error?e.reason.message:String(e.reason)}\n`)}async dispose(){if(void 0!==this.disposePromise)return this.disposePromise;this.closing=!0,this.shutdownController.abort(W());const e=Promise.allSettled([...this.ready?[this.ready]:[],this.refreshChain]).then(()=>{});return this.disposePromise=(async()=>{let t={failed:!1};try{await Promise.all([...this.liveSessions.values()].map(e=>e.close()))}catch(e){t={failed:!0,error:e}}this.liveSessions.clear(),await e;let s={failed:!1};try{await this.clientManager.disconnectAll(d(this.agentUsageLeases.values()))}catch(e){s={failed:!0,error:e}}if(await this.releaseAgentUsageLeases(),t.failed&&s.failed)throw new AggregateError([t.error,s.error],"ConversationEngine 关闭 session 与 MCP 连接均失败。");if(t.failed)throw t.error;if(s.failed)throw s.error})(),this.disposePromise}}
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{OBSERVATION_RETENTION_META_KEY as t,observationRetentionDeclarationSchema as s}from"@roll-agent/protocol/observation-retention";import{McpClientManager as i}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as o,providerAcceptsToolSchemaIssues as r}from"@roll-agent/core/llm/providers";import{AgentStore as n}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as a}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as l,startAgent as c,waitForAgentReady as d}from"@roll-agent/core/registry/process-manager";import{acquireAgentUsageLease as h,getExitedAgentUsageNames as u}from"@roll-agent/core/registry/agent-usage-lease";import{formatToolSchemaIssue as p,formatAppOutputDiscoveryIssue as g,normalizeListedTools as m}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as v}from"@roll-agent/core/config/helpers";import{catalogPackageSpec as f,getAgentCatalog as w}from"@roll-agent/core/registry/catalog";import{resolveAgentCatalog as y}from"@roll-agent/core/registry/catalog-discovery";import{installAgent as S}from"@roll-agent/core/registry/install";import{createSkillLibrary as b}from"@roll-agent/core/skills/library";import{ROLL_RESOURCE_HINTS_META_KEY as C}from"../tool-bridge/build-tools.js";import{TOOL_RESOURCE_ACCESS_MODES as k,TOOL_RESOURCE_HINT_KINDS as x}from"../tool-bridge/tool-execution-coordinator.js";import{AgentSession as A}from"./agent-session.js";import{resolveModelContextWindow as O}from"./context-window.js";import{createDefaultModelCatalog as E}from"./model-catalog-default.js";import{createWorkspaceInstructionsSource as P,parseWorkspaceInstructionsSetting as M}from"./workspace-instructions.js";import{CAPABILITY_HOST_MODES as T,shouldOfferAgentInstall as N,shouldOfferScheduleCreate as I}from"./capability-manifest.js";import{}from"../tool-bridge/bash-tool.js";import{unknownCommandClassifier as L}from"../types/command-classification.js";import{resolveShellProfile as R}from"../bash/profile.js";import{inspectGitVcsContext as U}from"./vcs-context.js";import{AGENT_BOOTSTRAP_MAX_CONCURRENCY as $,mapWithBoundedConcurrency as j}from"./agent-bootstrap.js";import{createToolLedgerGapRecoveryMessage as B}from"./cancelled-turn-recovery.js";const F=80,D="ConversationEngine is closing",W=3e4,H=6e4,q={connected:"connected",failed:"failed"};export function buildSessionExecSettings(e){const t=e.config.runtime.shell;return{workdir:process.cwd(),profile:e.profile,maxSessions:t.session.maxSessions,defaultYieldMs:t.session.defaultYieldMs,maxOutputTokens:t.session.maxOutputTokens,bufferCapacity:t.maxCaptureBytes,env:e.env,...e.onCommandSpawn?{onCommandSpawn:e.onCommandSpawn}:{}}}export function buildSessionBashSettings(e){const t=e.config.runtime.shell;return{workdir:process.cwd(),defaultTimeoutMs:t.defaultTimeoutMs,maxTimeoutMs:t.maxTimeoutMs,turnTimeoutMs:e.config.runtime.turnTimeoutMs,maxCaptureBytes:t.maxCaptureBytes,maxModelOutputChars:t.maxModelOutputChars,profile:e.profile,env:e.env,...e.onCommandSpawn?{onCommandSpawn:e.onCommandSpawn}:{}}}class AgentBootstrapTimeoutError extends Error{constructor(e){super(`Agent bootstrap timed out after ${String(e)}ms`),this.name="AgentBootstrapTimeoutError"}}class AgentBootstrapCleanupError extends AggregateError{cleanupErrors;constructor(e,t,s){const i=s.map(e=>e instanceof Error?e.message:String(e)).join("; ");super([t,...s],`Agent "${e}" bootstrap failed and resource cleanup also failed: ${i}`),this.name="AgentBootstrapCleanupError",this.cleanupErrors=s}}function _(){return new Error(D)}function V(e){return e.reason??new Error("Operation aborted")}function K(e){if(!0===e?.aborted)throw V(e)}function Y(e,t){return void 0===t?e:Math.max(1,Math.min(e,t-Date.now()))}function z(e){if(void 0!==e&&Date.now()>=e)throw new Error("Agent bootstrap deadline expired")}function G(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const s={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(s.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(s.destructiveHint=t.destructiveHint),void 0===s.readOnlyHint&&void 0===s.destructiveHint?void 0:s}function J(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Q(e){if(!J(e)||!J(e._meta))return{};if(!Object.hasOwn(e._meta,t))return{};const i=s.safeParse(e._meta[t]);return i.success?{declaration:i.data}:{issue:"声明字段无效"}}function X(e){return Object.values(x).some(t=>t===e)}function Z(e){return Object.values(k).some(t=>t===e)}function ee(e){return{hints:void 0,issue:e}}function te(e){if(!J(e)||!J(e._meta))return{hints:void 0,issue:void 0};if(!Object.hasOwn(e._meta,C))return{hints:void 0,issue:void 0};const t=e._meta[C];if(!Array.isArray(t))return ee("必须是数组");const s=[];for(const[e,i]of t.entries()){if(!J(i)||"string"!=typeof i.field||0===i.field.trim().length||"string"!=typeof i.kind||!X(i.kind)||void 0!==i.mode&&("string"!=typeof i.mode||!Z(i.mode))||void 0!==i.namespace&&"string"!=typeof i.namespace||i.kind===x.custom&&("string"!=typeof i.namespace||0===i.namespace.trim().length))return ee(`第 ${String(e+1)} 项字段无效`);const t=i.kind===x.custom&&"string"==typeof i.namespace?i.namespace.trim():void 0;s.push({field:i.field.trim(),kind:i.kind,...void 0!==i.mode?{mode:i.mode}:{},...void 0!==t?{namespace:t}:{}})}return{hints:s.length>0?s:void 0,issue:void 0}}function se(e){return"retry"===e.type?`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`:"warn"===e.type?`警告:${e.message}`:e.message}async function ie(e,t,s,i){K(i),"core-managed"===e.runtime.ownership&&(void 0===l(t,e.skill.name)&&c(e,t,s),await d(e,{...i?{signal:i}:{}}))}function oe(e){if(null===e.workspaceInstructions)return;if(void 0!==e.workspaceInstructions)return e.workspaceInstructions;const t=process.cwd();return P({cwd:t,setting:M(e.config.chat.instructions,t),...e.onWorkspaceInstructionsIssue?{onIssue:e.onWorkspaceInstructionsIssue}:{}})}export class ConversationEngine{config;clientManager;store;policy;fileToolsEnabled;maxSteps;providerOptions;modelOverride;modelNameOverride;providerNameOverride;switchingProviderName;modelCatalog;structuredOutputProviderOptions;structuredOutputReasoning;acquireAgentUsage;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;workspaceInstructions;onAgentBootstrapIssue;hostMode;scheduleTools;resolveDynamicCapabilityContext;sessionExecEnabled;explicitShellProfile;resolveShellProfileFn;installAgentFn;resolveCatalogFn;inspectVcsContext;shellEnv;onShellCommandSpawn;ready;refreshChain=Promise.resolve();resolvedCatalog;shellProfileResolution;shellUnsupportedWarned=!1;liveSessions=new Map;agentUsageLeases=new Map;shutdownController=new AbortController;disposePromise;closing=!1;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new i,this.store=e.store,this.modelCatalog=e.modelCatalog??E(),this.policy=e.policy,this.fileToolsEnabled=e.fileToolsEnabled??!0,this.maxSteps=e.maxSteps??F,this.providerOptions=e.providerOptions,this.structuredOutputProviderOptions=e.structuredOutputProviderOptions,this.structuredOutputReasoning=e.structuredOutputReasoning,this.acquireAgentUsage=e.acquireAgentUsage??(e.ensureAgentReady?async(t,s,i)=>{await(e.ensureAgentReady?.(t,s,i))}:(e,t,s)=>this.acquireDefaultAgentUsage(e,t,s)),this.debugEvents=e.debugEvents??!1,this.hostMode=e.hostMode??T.embedded,this.scheduleTools=e.scheduleTools??void 0,this.resolveDynamicCapabilityContext=e.resolveDynamicCapabilityContext,this.sessionExecEnabled=e.sessionExecEnabled??!0,this.explicitShellProfile=e.shellProfile,this.resolveShellProfileFn=e.resolveShellProfileFn??R,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.workspaceInstructions=oe(e),this.onAgentBootstrapIssue=e.onAgentBootstrapIssue,this.installAgentFn=e.installAgentFn??S,this.resolveCatalogFn=e.resolveCatalogFn??y,this.inspectVcsContext=e.inspectVcsContext??U,this.shellEnv={...e.shellEnv??process.env},this.onShellCommandSpawn=e.onShellCommandSpawn}async acquireDefaultAgentUsage(e,t,s){const i=await h(e,this.config.agents.dataDir,t,{holderKind:"chat",startIfStopped:!0,...s?{signal:s}:{}});if(void 0!==i)return i;await ie(e,this.config.agents.dataDir,t,s)}async createSession(t={}){this.assertAcceptingSessions();const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.store?this.store.createThread({...void 0===t.id?{}:{id:t.id},...void 0===t.origin?{}:{origin:t.origin},...t.title?{title:t.title}:{},model:this.resolveModelName()}):t.id??e();return this.buildSession(s,i,[])}async resumeSession(e){if(this.assertAcceptingSessions(),!this.store)throw new Error("resumeSession requires a ThreadStore");if("scheduled"===this.store.getThread(e)?.origin.kind)throw new Error("定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.liveSessions.get(e);if(void 0!==t)return t;if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.liveSessions.get(e);if(void 0!==i)return i;this.recoverUncoveredToolExecutionContext(e);const o=this.store.loadSessionState(e);return this.buildSession(s,e,o.messages,o.checkpoint)}async forkSession(e,t={}){if(this.assertAcceptingSessions(),!this.store)throw new Error("forkSession requires a ThreadStore");const s=await this.ensureReady();this.assertAcceptingSessions();const i=this.store.forkSnapshot(e,{...t,model:this.resolveModelName()});try{this.recoverUncoveredToolExecutionContext(i);const e=this.store.loadSessionState(i);return this.buildSession(s,i,e.messages,e.checkpoint)}catch(e){throw this.store.deleteThread(i),e}}assertAcceptingSessions(){if(this.closing)throw _()}resolveRuntimeShellProfile(){if(!this.config.runtime.shell.enabled)return;if(void 0!==this.explicitShellProfile)return this.explicitShellProfile??void 0;void 0===this.shellProfileResolution&&(this.shellProfileResolution=this.resolveShellProfileFn({platform:process.platform,env:this.shellEnv}));const e=this.shellProfileResolution;if(e.supported)return e.profile;if(!this.shellUnsupportedWarned){this.shellUnsupportedWarned=!0;const t="pwsh-version-unsupported"===e.reason?"检测到的 pwsh 版本低于 7":"未检测到 PowerShell 7 (pwsh)";process.stderr.write(`roll chat: ${t},Windows 原生 shell 工具已跳过注册;可运行 winget install Microsoft.PowerShell\n`)}}resolveShellSettings(e){return buildSessionBashSettings({config:this.config,profile:e,env:this.shellEnv,...this.onShellCommandSpawn?{onCommandSpawn:this.onShellCommandSpawn}:{}})}resolveSessionExecSettings(e){if(!this.sessionExecEnabled)return;const t=this.config.runtime.shell;return t.enabled&&t.session.enabled&&e.supportsSessionExec?buildSessionExecSettings({config:this.config,profile:e,env:this.shellEnv,...this.onShellCommandSpawn?{onCommandSpawn:this.onShellCommandSpawn}:{}}):void 0}buildSession(e,t,s,i){const o=this.store,r=o?.getThread(t)?.derivedFrom,n=this.resolveContextWindowFor(this.resolveProviderName(),this.resolveModelName())?.window,a=(e.skillLibrary?.list()??[]).length>0?e.skillLibrary:void 0,l=this.resolveRuntimeShellProfile(),c=l?this.resolveShellSettings(l):void 0,d=this.fileToolsEnabled?{workdir:c?.workdir??process.cwd()}:void 0,h=l?this.resolveSessionExecSettings(l):void 0,u=l?l.supportsSafeCommandClassification&&this.config.runtime.shell.autoApproveSafe?l:L:void 0,p=N(this.hostMode)?this.resolveAgentInstallBinding():void 0,g=this.scheduleTools,m=this.composeCapabilityContext(e.sources.length,l),v=new A({id:t,model:this.activeModel(e),sources:e.sources,toolSchemaPolicy:this.toolSchemaPolicyFor(this.resolveProviderName()),onToolExcluded:e=>this.onAgentBootstrapIssue?.({agentName:e.agentName,message:`工具 "${e.toolName}" 的参数 schema 含当前 provider "${this.resolveToolSchemaProviderName()}" 无法接受的引用(${e.issues.map(e=>e.ref).join("、")}),已从本会话工具集移除`}),capabilityContext:m,resolveDynamicCapabilityContext:async e=>{const[t,s]=await Promise.all([this.inspectVcsContext(m.cwd),this.resolveDynamicCapabilityContext?.(e)??{}]),i=s.vcs??t;return{...s.ruleIds?{ruleIds:s.ruleIds}:{},...i?{vcs:i}:{},...s.origin?{origin:s.origin}:{}}},...a?{skillLibrary:a}:{},...this.workspaceInstructions?{workspaceInstructions:this.workspaceInstructions}:{},...d?{fileTools:d}:{},...c?{bash:c}:{},...u?{bashClassifier:u}:{},...h?{bashSession:h}:{},...p?{agentInstall:p}:{},...g?{schedules:{port:g,sessionCwd:m.cwd,includeCreate:I(this.hostMode)}}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...this.structuredOutputProviderOptions?{structuredOutputProviderOptions:this.structuredOutputProviderOptions}:{},...this.structuredOutputReasoning?{structuredOutputReasoning:this.structuredOutputReasoning}:{},onProviderOptionsChange:e=>this.syncProviderOptions(e),...void 0!==n?{contextWindow:n}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...void 0!==r?{derivedFrom:r}:{},...i?{initialCheckpoint:i}:{},...o?{onPersist:(e,s)=>o.appendMessages(t,e,s),onReplace:e=>o.replaceMessages(t,e),onToolExecution:e=>o.appendToolExecution(t,e),listToolExecutions:e=>o.listToolExecutions(t,e),getToolExecution:e=>o.getToolExecution(t,e),recoverUncoveredToolExecutions:()=>this.recoverUncoveredToolExecutionContext(t),listTranscriptMessages:e=>o.listTranscriptMessages(t,e),commitCompaction:e=>o.commitCompaction(t,e),readCheckpointTranscript:e=>o.readCheckpointTranscript(t,e)}:{},onClose:()=>{this.liveSessions.get(t)===v&&this.liveSessions.delete(t)}});return this.liveSessions.set(t,v),v}recoverUncoveredToolExecutionContext(e){const t=this.store?.recoverUncoveredToolExecutions(e,B);return void 0===t?[]:[t]}syncProviderOptions(e){this.providerOptions=e,this.clientManager.setSamplingProviderOptions(e)}switchModel(e){this.assertAcceptingSessions();const t=this.resolveContextWindowFor(e.provider,e.modelName),s=t?.window,i={model:e.model,...void 0!==s?{contextWindow:s}:{},...e.providerOptions?{providerOptions:e.providerOptions}:{},...e.structuredOutputProviderOptions?{structuredOutputProviderOptions:e.structuredOutputProviderOptions}:{},...e.structuredOutputReasoning?{structuredOutputReasoning:e.structuredOutputReasoning}:{},toolSchemaPolicy:this.toolSchemaPolicyFor(e.provider)};for(const e of this.liveSessions.values())if(!e.canSwitchModel())throw new Error("存在正在生成回复的会话,稍后再切换模型");this.switchingProviderName=e.provider;try{for(const e of this.liveSessions.values())e.switchModel(i)}finally{this.switchingProviderName=void 0}this.modelOverride=e.model,this.modelNameOverride=e.modelName,this.providerNameOverride=e.provider,this.providerOptions=e.providerOptions,this.structuredOutputProviderOptions=e.structuredOutputProviderOptions,this.structuredOutputReasoning=e.structuredOutputReasoning,this.clientManager.setSamplingModel(e.model),this.clientManager.setSamplingProviderOptions(e.providerOptions);for(const t of this.liveSessions.keys())this.store?.updateModel(t,e.modelName);return t}ensureReady(){return this.assertAcceptingSessions(),this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=Date.now(),t=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:t,sources:this.explicitSources,...this.explicitSkillLibrary?{skillLibrary:this.explicitSkillLibrary}:{}};const s=this.explicitAgents??new n(this.config.agents.dataDir).list(),i=this.config.runtime.agentBootstrap.timeoutMs,o=e+i,r=new AbortController,a=new AgentBootstrapTimeoutError(i);let l=!1;const c=()=>{r.signal.aborted||r.abort(V(this.shutdownController.signal))},d=()=>{!this.closing&&!r.signal.aborted&&Date.now()>=o&&(l=!0,r.abort(a))};this.shutdownController.signal.aborted?c():this.shutdownController.signal.addEventListener("abort",c,{once:!0});const h=setTimeout(()=>{this.closing||r.signal.aborted||(l=!0,r.abort(a))},Math.max(0,o-Date.now())),u=(e,t)=>({kind:q.failed,issues:[{agentName:e.skill.name,message:a.message},...void 0===t?[]:[{agentName:e.skill.name,message:t.message}]]});try{if(this.agentInstallEnabled())try{this.resolvedCatalog=await this.resolveCatalogFn(this.config,{allowNetwork:!1,signal:r.signal,...this.config.install.registry?{registry:this.config.install.registry}:{}})}catch(e){if(this.closing||this.shutdownController.signal.aborted)throw _();if(d(),!l)throw e}if(this.closing||this.shutdownController.signal.aborted)throw _();if(d(),0===s.length){const e=this.resolveSkillLibrary(s);return{model:t,sources:[],...e?{skillLibrary:e}:{}}}const e=await j(s,$,async e=>{const s=[],i=e=>{s.push(e)};try{d(),K(r.signal);const n=await this.connectAgentSource(e,t,i,{signal:r.signal,deadlineAt:o});return{kind:q.connected,source:n,issues:s}}catch(t){if(this.closing||this.shutdownController.signal.aborted)throw _();return d(),l?u(e,t instanceof AgentBootstrapCleanupError?t:void 0):(s.push({agentName:e.skill.name,message:t instanceof Error?t.message:String(t)}),{kind:q.failed,issues:s})}},{signal:r.signal,onSkipped:e=>u(e)});if(this.closing||this.shutdownController.signal.aborted)throw _();const i=[];for(const t of e){for(const e of t.issues)this.onAgentBootstrapIssue?.(e);t.kind===q.connected&&i.push(t.source)}const n=this.resolveSkillLibrary(s);return{model:t,sources:i,...n?{skillLibrary:n}:{}}}finally{clearTimeout(h),this.shutdownController.signal.removeEventListener("abort",c)}}async connectAgentSource(e,s,i=e=>this.onAgentBootstrapIssue?.(e),o={}){this.assertAcceptingSessions(),K(o.signal),z(o.deadlineAt);const r=a(e),n=v(this.config,e.skill.name),l=this.agentUsageLeases.get(e.skill.name);let c,d;try{c=void 0===l?await this.acquireAgentUsage(e,n,o.signal):void 0,this.assertAcceptingSessions(),K(o.signal),z(o.deadlineAt);const a={timeoutMs:Y(W,o.deadlineAt),samplingModel:s,...this.providerOptions?{samplingProviderOptions:this.providerOptions}:{},...n?{env:n}:{},...o.signal?{signal:o.signal}:{}},h=this.clientManager.connectWithOwnership;d="function"==typeof h?await h.call(this.clientManager,e.skill.name,r,e.installPath,a):{client:await this.clientManager.connect(e.skill.name,r,e.installPath,a),commit:()=>{},rollback:async()=>{}},this.assertAcceptingSessions(),K(o.signal),z(o.deadlineAt);const u=(await d.client.listTools(void 0,{timeout:Y(H,o.deadlineAt),...o.signal?{signal:o.signal}:{}})).tools;this.assertAcceptingSessions(),K(o.signal),z(o.deadlineAt);const v=m(u,{onAppOutputIssue:t=>i({agentName:e.skill.name,message:g(e.skill.name,t)}),onSchemaIssue:t=>i({agentName:e.skill.name,message:p(e.skill.name,t)})}).map((s,o)=>{const r=te(u[o]),n=Q(u[o]);return n.issue&&i({agentName:e.skill.name,message:`Tool "${s.name}" 的 ${t} 无效(${n.issue}),观察历史保持原样`}),void 0!==r.issue&&i({agentName:e.skill.name,message:`Tool "${s.name}" 的 ${C} 无效(${r.issue}),已回退 Agent 级资源锁`}),{tool:s,annotations:G(u[o]),...r.hints?{resourceHints:r.hints}:{},...n.declaration?{observationRetention:n.declaration}:{}}});return d.commit(),void 0!==c&&this.agentUsageLeases.set(e.skill.name,c),{agentName:e.skill.name,client:d.client,tools:v,...e.source?{agentSource:e.source.type}:{},transport:r.type,runtimeOwnership:e.runtime.ownership,..."stdio"===r.type?{resourceBaseDir:e.installPath}:{}}}catch(t){const s=[];if(void 0!==d&&await d.rollback().catch(e=>{s.push(e)}),void 0!==c){const t=c;await t.release().catch(i=>{this.agentUsageLeases.set(e.skill.name,t),s.push(i)})}if(s.length>0)throw new AgentBootstrapCleanupError(e.skill.name,t,s);throw t}}async prepareAgentRefresh(e){this.assertAcceptingSessions();const t=this.refreshChain.then(()=>this.runAgentRefresh(e));return this.refreshChain=t.then(()=>{},()=>{}),t}async runAgentRefresh(e){const t=await this.ensureReady(),s=await this.connectAgentSource(e,this.activeModel(t),void 0,{signal:this.shutdownController.signal}),i=[...t.sources.filter(e=>e.agentName!==s.agentName),s],o=this.explicitAgents??new n(this.config.agents.dataDir).list(),r=this.resolveSkillLibrary(o);this.ready=Promise.resolve({model:this.activeModel(t),sources:i,...r?{skillLibrary:r}:{}});const a=r?.list()??[],l=r&&a.length>0?r:void 0,c=this.resolveRuntimeShellProfile();return{source:s,...l?{skillLibrary:l}:{},capabilityContext:this.composeCapabilityContext(i.length,c)}}composeCapabilityContext(e,t){const s=this.resolveAgentOnboardingInfo();return{profile:t?.toolName??"no-shell",hostMode:this.hostMode,cwd:process.cwd(),platform:process.platform,...t?{shellHints:t.systemPromptHints()}:{},agentCount:e,...s?{agentOnboardingCatalog:s}:{}}}agentInstallEnabled(){return void 0===this.explicitSources&&void 0===this.explicitAgents}currentCatalog(){return this.resolvedCatalog??w(this.config)}resolveAgentOnboardingInfo(){if(!this.agentInstallEnabled())return;const e=this.currentCatalog();return 0!==e.length?e.map(e=>({shortName:e.shortName,description:e.description})):void 0}resolveAgentInstallBinding(){if(!this.agentInstallEnabled())return;const e=this.currentCatalog();return 0!==e.length?{catalog:e.map(e=>({shortName:e.shortName,description:e.description})),install:(e,t)=>this.installCatalogAgent(e,t)}:void 0}async installCatalogAgent(e,t){const s=this.currentCatalog().find(t=>t.shortName===e);if(!s)return{outcome:{ok:!1,message:`未知的官方 Agent 短名: ${e}`}};const i=await this.installAgentFn({packageSpec:f(s),skipBrowserSetup:!0,autoStart:!1,expectedSkillName:s.skillName},{agentsConfig:this.config.agents,installConfig:this.config.install,getStartEnv:e=>v(this.config,e),report:e=>t(se(e))});if(!i.ok)return{outcome:{ok:!1,message:i.message,...i.retryCommand?{retryCommand:i.retryCommand}:{}}};const o=i.agent,r="installed-package"===o.source?.type?o.source.installedVersion:void 0,n="core-managed"===o.runtime.ownership&&void 0!==o.runtime.setup?.playwright,a={ok:!0,agentName:o.skill.name,...r?{version:r}:{},missingEnv:(i.envReport?.missingRequired??[]).map(e=>e.name),...n?{retryCommand:`roll agent install ${s.shortName}`}:{},refreshApplied:!1};if((await this.ensureReady()).sources.some(e=>e.agentName===o.skill.name))return t(`Agent "${o.skill.name}" 本会话已接入旧版本连接,更新需重启 roll chat 生效。`),{outcome:a};try{return{outcome:a,refresh:await this.prepareAgentRefresh(o)}}catch(e){return t(`接入新 Agent 失败:${e instanceof Error?e.message:String(e)}`),{outcome:a}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:b({agents:e,extraDirs:this.config.skills.dirs,...this.onSkillLibraryIssue?{onIssue:this.onSkillLibraryIssue}:{}})}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),s=this.config.llm.providers[e];if(!s)throw new Error(`LLM provider "${e}" 未配置`);return o(e,t,s.apiKey,s.baseUrl)}resolveProviderName(){return this.providerNameOverride??this.config.runtime.provider??this.config.llm.defaultProvider}resolveContextWindowFor(e,t){return O({provider:e,model:t,...void 0!==this.config.runtime.contextWindow?{override:this.config.runtime.contextWindow}:{},catalog:this.modelCatalog})}resolveModelName(){return this.modelNameOverride??this.config.runtime.model??this.config.llm.defaultModel}toolSchemaPolicyFor(e){return t=>r(e,t)}resolveToolSchemaProviderName(){return this.switchingProviderName??this.resolveProviderName()}activeModel(e){return this.modelOverride??e.model}async getContextSummary(){const e=await this.ensureReady();this.assertAcceptingSessions();const t=this.workspaceInstructions?.current()?.path;return{agentCount:e.sources.length,toolCount:e.sources.reduce((e,t)=>e+t.tools.length,0),skillCount:e.skillLibrary?.list().length??0,...void 0!==t?{instructionsPath:t}:{}}}async releaseAgentUsageLeases(){const e=[...this.agentUsageLeases.entries()],t=await Promise.allSettled(e.map(async([e,t])=>{try{await t.release()}finally{this.agentUsageLeases.get(e)===t&&this.agentUsageLeases.delete(e)}}));for(const e of t)"rejected"===e.status&&process.stderr.write(`roll chat: Agent 使用租约释放失败: ${e.reason instanceof Error?e.reason.message:String(e.reason)}\n`)}async dispose(){if(void 0!==this.disposePromise)return this.disposePromise;this.closing=!0,this.shutdownController.abort(_());const e=Promise.allSettled([...this.ready?[this.ready]:[],this.refreshChain]).then(()=>{});return this.disposePromise=(async()=>{let t={failed:!1};try{await Promise.all([...this.liveSessions.values()].map(e=>e.close()))}catch(e){t={failed:!0,error:e}}this.liveSessions.clear(),await e;let s={failed:!1};try{await this.clientManager.disconnectAll(u(this.agentUsageLeases.values()))}catch(e){s={failed:!0,error:e}}if(await this.releaseAgentUsageLeases(),t.failed&&s.failed)throw new AggregateError([t.error,s.error],"ConversationEngine 关闭 session 与 MCP 连接均失败。");if(t.failed)throw t.error;if(s.failed)throw s.error})(),this.disposePromise}}
|
|
@@ -58,8 +58,8 @@ export declare const explicitSkillCheckpointV1Schema: z.ZodReadonly<z.ZodObject<
|
|
|
58
58
|
}>[] | undefined;
|
|
59
59
|
}>>;
|
|
60
60
|
}, "strip", z.ZodTypeAny, {
|
|
61
|
-
version: 1;
|
|
62
61
|
kind: "explicit-skill";
|
|
62
|
+
version: 1;
|
|
63
63
|
snapshot: Readonly<{
|
|
64
64
|
userPrompt: string;
|
|
65
65
|
skillNames: string[];
|
|
@@ -70,8 +70,8 @@ export declare const explicitSkillCheckpointV1Schema: z.ZodReadonly<z.ZodObject<
|
|
|
70
70
|
}>[] | undefined;
|
|
71
71
|
}>;
|
|
72
72
|
}, {
|
|
73
|
-
version: 1;
|
|
74
73
|
kind: "explicit-skill";
|
|
74
|
+
version: 1;
|
|
75
75
|
snapshot: Readonly<{
|
|
76
76
|
userPrompt: string;
|
|
77
77
|
skillNames: string[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
2
|
+
import type { ObservationRetentionDeclaration } from "@roll-agent/protocol/observation-retention";
|
|
3
|
+
import type { ToolModelOutput } from "../tool-bridge/normalize-result.ts";
|
|
4
|
+
type JsonRecord = Record<string, unknown>;
|
|
5
|
+
export interface Observation {
|
|
6
|
+
readonly page: JsonRecord;
|
|
7
|
+
readonly snapshot: JsonRecord;
|
|
8
|
+
readonly identity: string;
|
|
9
|
+
readonly resource: string;
|
|
10
|
+
readonly complete: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Read only successful, fully structured observations. Errors and clipped JSON are untouched. */
|
|
13
|
+
export declare function observationFromMcpRaw(raw: unknown, declaration: ObservationRetentionDeclaration | undefined): Observation | undefined;
|
|
14
|
+
export declare function observationFromModelOutput(output: unknown): Observation | undefined;
|
|
15
|
+
/** Model-only output; SDK/MCP raw output and canonical step results are not changed. */
|
|
16
|
+
export declare function currentObservationModelOutput(raw: unknown, declaration: ObservationRetentionDeclaration | undefined): ToolModelOutput | undefined;
|
|
17
|
+
/** Complete original MCP content for canonical step persistence, including large snapshots. */
|
|
18
|
+
export declare function canonicalObservationOutput(raw: unknown, declaration: ObservationRetentionDeclaration | undefined): ToolModelOutput | undefined;
|
|
19
|
+
export interface ObservationProjectionOptions {
|
|
20
|
+
readonly declarations: ReadonlyMap<string, ObservationRetentionDeclaration>;
|
|
21
|
+
readonly resultId?: (toolCallId: string) => string | undefined;
|
|
22
|
+
readonly recallToolId?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Pure model-visible projection. It neither mutates input messages nor drops tool-result pairs. */
|
|
25
|
+
export declare function projectObservationMessages(messages: readonly ModelMessage[], options: ObservationProjectionOptions): ModelMessage[];
|
|
26
|
+
export declare function restoreCanonicalObservationOutputs(messages: readonly ModelMessage[], outputs: ReadonlyMap<string, ToolModelOutput>): ModelMessage[];
|
|
27
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=5e4,e=4e4;function r(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function n(t){return"string"==typeof t&&t.trim().length>0}function o(t){if(!r(t)||!r(t.page)||!r(t.snapshot))return;const e=t.snapshot;if(!n(e.browserInstance)||!n(e.pageId)||!n(e.documentId)||!Array.isArray(e.nodes)||!Array.isArray(e.refs)||!0!==e.truncated&&!1!==e.truncated)return;const o=Array.isArray(e.coverageWarnings)?e.coverageWarnings:[];return{page:t.page,snapshot:e,identity:JSON.stringify([e.browserInstance,e.pageId,e.documentId]),resource:JSON.stringify([e.browserInstance,e.pageId]),complete:!1===e.truncated&&!n(e.scope)&&0===o.length}}export function observationFromMcpRaw(t,e){if("browser-ax-snapshot"===e?.kind&&r(t)&&!0!==t.isError){if(r(t.structuredContent)){const e=o(t.structuredContent);if(e)return e}if(Array.isArray(t.content))for(const e of t.content)if(r(e)&&"text"===e.type&&"string"==typeof e.text)try{const t=o(JSON.parse(e.text));if(t)return t}catch{}}}export function observationFromModelOutput(t){if(r(t)){if("json"===t.type)return o(t.value);if("content"===t.type&&Array.isArray(t.value))for(const e of t.value)if(r(e)&&"text"===e.type&&"string"==typeof e.text)try{const t=o(JSON.parse(e.text));if(t)return t}catch{}}}function s(t,e,n,o=0){if("string"==typeof t){const r=Math.min(1024,Math.max(0,e-16));return t.length<=r?t:(n.truncated=!0,`${t.slice(0,r)}[省略]`)}if(null===t||"boolean"==typeof t)return t;if("number"==typeof t)return Number.isFinite(t)?t:null;if(o>=5)return n.truncated=!0,"[嵌套内容省略]";if(Array.isArray(t)){const r=[];for(const i of t.slice(0,24)){const t=s(i,e-JSON.stringify(r).length,n,o+1);if(JSON.stringify([...r,t]).length>e){n.truncated=!0;break}r.push(t)}return r.length<t.length&&(n.truncated=!0),r}if(r(t)){const r={},i=Object.entries(t).filter(([,t])=>void 0!==t);for(const[t,a]of i.slice(0,24)){const i=t.slice(0,128);i!==t&&(n.truncated=!0);const c=s(a,e-JSON.stringify(r).length,n,o+1);if(JSON.stringify({...r,[i]:c}).length>e){n.truncated=!0;break}r[i]=c}return Object.keys(r).length<i.length&&(n.truncated=!0),r}return n.truncated=!0,null}function i(t,e,n,o){if(!r(t))return o.omittedNodes+=1,void(o.truncated=!0);const{children:a,ref:c,...u}=t,d="string"==typeof c?e.get(c):void 0,l=d?Object.fromEntries(Object.entries(d).filter(([t,e])=>"ref"!==t&&void 0!==e&&(void 0===u[t]||JSON.stringify(u[t])!==JSON.stringify(e)))):void 0,f=s({...n||void 0===c?{}:{ref:c},...void 0!==u.role?{role:u.role}:{},...void 0!==u.name?{name:u.name}:{},...void 0!==u.frameId?{frameId:u.frameId}:{},...void 0!==u.depth?{depth:u.depth}:{},...u,...!n&&l&&Object.keys(l).length>0?{refDetails:l}:{}},2500,o);if(!r(f))return;const p=JSON.stringify(f).length+16;if(p>o.remaining)return o.omittedNodes+=1,void(o.truncated=!0);if(o.remaining-=p,Array.isArray(a)){const t=[];for(const r of a){const s=i(r,e,n,o);void 0!==s&&t.push(s)}if(t.length>0)return{...f,children:t}}return f}function a(o,a=!1,c){const u=o.snapshot,d={remaining:e,truncated:!1,omittedNodes:0},l=new Map;for(const t of u.refs)r(t)&&n(t.ref)&&l.set(t.ref,t);const f=u.nodes.flatMap(t=>{const e=i(t,l,a,d);return void 0===e?[]:[e]}),p=new Set,g=t=>{for(const e of t)r(e)&&(n(e.ref)&&p.add(e.ref),Array.isArray(e.children)&&g(e.children))};g(u.nodes);const y=a?[]:[...l].filter(([t])=>!p.has(t)).map(([,t])=>t).slice(0,16);!a&&y.length<[...l].filter(([t])=>!p.has(t)).length&&(d.truncated=!0);const{refs:h,nodes:m,...v}=u,I=s(o.page,2e3,d),O=s({snapshotId:u.snapshotId,browserInstance:u.browserInstance,pageId:u.pageId,documentId:u.documentId,scope:u.scope,truncated:u.truncated,coverageWarnings:u.coverageWarnings,...v},5e3,d),b=s(y,1e3,d),A={...c?{resultId:c}:{},page:I,snapshot:{...r(O)?O:{},nodes:f,...Array.isArray(b)&&b.length>0?{unmatchedRefs:b}:{},...a?{historicalEvidenceOnly:!0,refsAreStale:!0}:{},...d.truncated?{modelProjectionTruncated:!0,modelProjectionOmittedNodes:d.omittedNodes,modelProjectionWarning:"模型侧观察已省略部分内容;可按结果 ID 分页回查原始观察"}:{}}};for(;JSON.stringify(A).length>t&&f.length>0;)f.pop(),d.omittedNodes+=1,d.truncated=!0,Object.assign(A.snapshot,{modelProjectionTruncated:!0,modelProjectionOmittedNodes:d.omittedNodes,modelProjectionWarning:"模型侧观察已省略部分内容;可按结果 ID 分页回查原始观察"});return{type:"json",value:A}}export function currentObservationModelOutput(t,e){const r=observationFromMcpRaw(t,e);return r?a(r):void 0}export function canonicalObservationOutput(t,e){if(!observationFromMcpRaw(t,e)||!r(t))return;const n=r(t.structuredContent)?o(t.structuredContent):void 0,s=t.content;if(Array.isArray(s)){const e=s.flatMap(t=>r(t)&&"text"===t.type&&"string"==typeof t.text?[{type:"text",text:t.text}]:[]);if(e.length>0){if(!n)return{type:"content",value:e};try{if(JSON.stringify(JSON.parse(e[0]?.text??""))===JSON.stringify(t.structuredContent))return{type:"content",value:e}}catch{}}}return n?{type:"json",value:t.structuredContent}:void 0}export function projectObservationMessages(t,e){const r=new Set,n=new Map,o=new Set,s=[];let i,c=0,u=!1;for(const[e,r]of t.entries())"tool"===r.role?(u||(c+=1),s[e]=c,u=!0):u=!1;let d=!1;return[...t].reverse().map((c,u)=>{if("user"===c.role)return d=!0,c;if("tool"!==c.role)return c;const l=[...c.content].reverse().map(c=>{if("tool-result"!==c.type)return c;if(c.toolName===e.recallToolId){const e=s[t.length-1-u];return d||void 0===e||void 0!==i&&i!==e?{...c,output:{type:"text",value:"[历史观察回查;如需证据请按结果 ID 再次读取]"}}:(i=e,c)}if(!e.declarations.get(c.toolName))return c;const l=c.output,f=observationFromModelOutput(l);if(!f)return{...c,output:l};const p=n.get(f.resource);void 0===p&&n.set(f.resource,f.identity);const g=void 0!==p&&p!==f.identity,y=!r.has(f.identity);return y&&r.add(f.identity),y&&!g?(f.complete&&o.add(f.identity),{...c,output:a(f,!1,e.resultId?.(c.toolCallId))}):g||!f.complete||o.has(f.identity)?{...c,output:{type:"json",value:{historicalObservation:!0,refsAreStale:!0,...g?{documentStale:!0}:{},...e.resultId?.(c.toolCallId)?{resultId:e.resultId(c.toolCallId)}:{},snapshotId:"string"==typeof f.snapshot.snapshotId?f.snapshot.snapshotId:null,browserInstance:String(f.snapshot.browserInstance),pageId:String(f.snapshot.pageId),documentId:String(f.snapshot.documentId),scope:"string"==typeof f.snapshot.scope?f.snapshot.scope:null,truncated:!0===f.snapshot.truncated,coverageWarnings:Array.isArray(f.snapshot.coverageWarnings)?f.snapshot.coverageWarnings.filter(t=>"string"==typeof t):[]}}}:(o.add(f.identity),{...c,output:a(f,!0,e.resultId?.(c.toolCallId))})});return{...c,content:l.reverse()}}).reverse()}export function restoreCanonicalObservationOutputs(t,e){return t.map(t=>"tool"===t.role?{...t,content:t.content.map(t=>"tool-result"===t.type&&e.has(t.toolCallId)?{...t,output:e.get(t.toolCallId)}:t)}:t)}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
2
|
+
import type { ObservationRetentionDeclaration } from "@roll-agent/protocol/observation-retention";
|
|
3
|
+
import type { SequencedToolExecutionRecord } from "../store/thread-store.ts";
|
|
4
|
+
import type { ObservationRecallInput } from "../tool-bridge/observation-recall-tool.ts";
|
|
5
|
+
/** Result ID was already resolved against this thread's ledger by the caller. */
|
|
6
|
+
export declare function readObservationPage(input: ObservationRecallInput, record: SequencedToolExecutionRecord, messages: readonly ModelMessage[], declaration: ObservationRetentionDeclaration, allowActive?: boolean): unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{redactSecretText as e}from"../tool-bridge/tool-execution-record.js";import{observationFromMcpRaw as t,observationFromModelOutput as r}from"./observation-projection.js";function o(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function n(t){return"string"==typeof t?e(t).slice(0,1e3):null}function a(e){const t=[],r=(e,n)=>{for(const a of e){if(!o(a))continue;const{children:e,ref:s,locator:l,...i}=a,d=t.length;t.push({parent:n,value:i}),Array.isArray(e)&&r(e,d)}};return r(e,null),t}function s(e,t){let o;for(const n of[...e].reverse())if("tool"===n.role)for(const e of[...n.content].reverse())if("tool-result"===e.type&&e.toolCallId===t.toolCallId){if(o)return;o=r(e.output)}return o}export function readObservationPage(r,o,l,i,d=!0){const c=d?s(l,o):void 0,u=c||"json"!==o.raw.encoding||!0===o.persistence?.fields.raw.truncated?void 0:t(o.raw.value,i),p=c??u;if(!p)return{resultId:r.resultId,complete:!1,reason:o.persistence?.fields.raw.truncated?"durable_payload_truncated":"json"!==o.raw.encoding?"durable_payload_diagnostic":"observation_payload_unavailable",refsAreStale:!0,nodes:[]};const f=a(p.snapshot.nodes),g=(r.afterNode??-1)+1,v=r.limit??8,y=f.slice(g,g+v).map((t,r)=>{const o=e(JSON.stringify(t.value));return{index:g+r,parent:t.parent,text:o.slice(0,1500),...o.length>1500?{nodeTruncated:!0}:{}}}),I=g+y.length<f.length?g+y.length-1:void 0,h=p.snapshot;return{resultId:r.resultId,complete:!0,source:c?"active_history":"durable_ledger",evidenceRedacted:void 0===c,refsAreStale:!0,snapshotId:n(h.snapshotId),browserInstance:n(h.browserInstance),pageId:n(h.pageId),documentId:n(h.documentId),scope:n(h.scope),truncated:h.truncated,coverageWarnings:Array.isArray(h.coverageWarnings)?h.coverageWarnings.slice(0,16).map(n):[],nodes:y,...void 0!==I?{nextAfterNode:I}:{},totalNodes:f.length}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as n,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as a,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,runtimeMethodSchemas as y,runtimeDurableEventV13Schema as I,turnIdSchema as q}from"@roll-agent/protocol";import{RuntimeService as g,RuntimeServiceError as w}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as S,RuntimeClientRequestCoordinator as C,RuntimeClientRequestExpiredError as V,createRuntimeClientResponderId as P,getRuntimeClientRequestCoordinatorInternal as A}from"./runtime-client-request-coordinator.js";const U="客户端未完成审批请求,Runtime 已终止当前 Turn",E="客户端未完成用户输入请求",T=new WeakSet,z=o,k=a;function G(e){return`user-input:${e}`}function B(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(T.has(e))throw new w(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");T.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=P();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new w(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion)throw new w(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=y[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new w(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.4",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:q.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:I.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return A(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new w(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new w(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new w(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new w(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=y[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(A(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new w(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new w(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new w(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new w(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.threadList:return this.service.listThreads(y[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(y[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(y[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(y[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(y[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(y[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(y[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(y[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.operationGet:return this.service.getOperation(y[s.operationGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(y[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(y[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(y[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(y[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(T.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,B(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=z||e.bufferedBytes+i>k?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new w(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new w(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=A(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new w(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,E):this.clientRequests.cancel(G(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,E);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(n.userInputRequest,s,{key:G(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,E)}const a=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof V?"用户输入请求已超时":e instanceof S?e.reason:E;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(a)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let a;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new S("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new S("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};a=this.clientRequests.request(n.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,U)).then(()=>{});return void this.trackSettlement(e)}const o=a.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof w&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,U)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof V)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof S?e.reason:U;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new w(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new w(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,a=A(this.clientRequests),o=a.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??a.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new w(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(n.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as n,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as a,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,projectOperationViewForVersion as y,runtimeMethodSchemas as I,runtimeDurableEventV15Schema as q,turnIdSchema as g}from"@roll-agent/protocol";import{RuntimeService as w,RuntimeServiceError as S}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as C,RuntimeClientRequestCoordinator as V,RuntimeClientRequestExpiredError as P,createRuntimeClientResponderId as A,getRuntimeClientRequestCoordinatorInternal as U}from"./runtime-client-request-coordinator.js";const E="客户端未完成审批请求,Runtime 已终止当前 Turn",T="客户端未完成用户输入请求",z=new WeakSet,G=o,k=a;function B(e){return`user-input:${e}`}function F(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(z.has(e))throw new S(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");z.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=A();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion&&"1.5"!==this.protocolVersion)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=I[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new S(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.5",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:g.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:q.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return U(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new S(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new S(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=I[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(U(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new S(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.threadList:return this.service.listThreads(I[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(I[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(I[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion||"1.5"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(I[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(I[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(I[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(I[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(I[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.operationGet:{const t=this.service.getOperation(I[s.operationGet].params.parse(e.params));return{operation:null===t.operation?null:y(this.protocolVersion,t.operation)}}case s.operationResultGet:return this.service.getOperationResult(I[s.operationResultGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(I[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(I[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(I[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(I[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(z.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,F(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=G||e.bufferedBytes+i>k?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new S(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=U(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,T):this.clientRequests.cancel(B(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,T);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(n.userInputRequest,s,{key:B(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,T)}const a=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof P?"用户输入请求已超时":e instanceof C?e.reason:T;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(a)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let a;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new C("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new C("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};a=this.clientRequests.request(n.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,E)).then(()=>{});return void this.trackSettlement(e)}const o=a.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof S&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,E)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof P)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof C?e.reason:E;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new S(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,a=U(this.clientRequests),o=a.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??a.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(n.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type
|
|
1
|
+
import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type RuntimeEventEnvelopeV15, type RuntimeEventsResumeParams, type RuntimeEventsResumeResult, type RuntimeInstanceId, type LatestRuntimeMethodParams, type LatestRuntimeMethodResult, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorDataV14, type ThreadSnapshotV15Full, type ThreadId, type TurnId, type UserInputForm, type UserInputResult } from "@roll-agent/protocol";
|
|
2
2
|
import type { AgentSession } from "../engine/agent-session.ts";
|
|
3
3
|
import { AttachmentStore } from "./attachment-store.ts";
|
|
4
4
|
import type { SessionUserInputRequestId } from "../interaction/user-input-interaction-manager.ts";
|
|
@@ -37,7 +37,7 @@ export type RuntimeApprovalDecision = {
|
|
|
37
37
|
readonly decision: "reject";
|
|
38
38
|
readonly reason?: string;
|
|
39
39
|
};
|
|
40
|
-
export type RuntimeThreadSnapshot = Omit<
|
|
40
|
+
export type RuntimeThreadSnapshot = Omit<ThreadSnapshotV15Full, "pendingInteractions">;
|
|
41
41
|
export interface RuntimeEventReplayBatch extends RuntimeEventsResumeResult {
|
|
42
42
|
readonly events: readonly StoredRuntimeEvent[];
|
|
43
43
|
}
|
|
@@ -101,7 +101,7 @@ export declare class RuntimeService {
|
|
|
101
101
|
private userInputAvailable;
|
|
102
102
|
constructor(engine: RuntimeServiceEngine, store: ThreadStore, options?: RuntimeServiceOptions);
|
|
103
103
|
initialize(params: InitializeParams): InitializeResult;
|
|
104
|
-
onEvent(listener: (event:
|
|
104
|
+
onEvent(listener: (event: RuntimeEventEnvelopeV15) => void): () => void;
|
|
105
105
|
onFatalError(listener: (error: unknown) => void): () => void;
|
|
106
106
|
onUserInputInteraction(listener: (event: RuntimeUserInputInteractionEvent) => void): () => void;
|
|
107
107
|
setUserInputAvailable(available: boolean): void;
|
|
@@ -132,7 +132,8 @@ export declare class RuntimeService {
|
|
|
132
132
|
}>): Promise<boolean>;
|
|
133
133
|
resolvePendingUserInput(requestId: SessionUserInputRequestId, result: UserInputResult): boolean;
|
|
134
134
|
cancelPendingUserInput(requestId: SessionUserInputRequestId, reason: string): boolean;
|
|
135
|
-
getOperation(params: RuntimeMethodParams<"operation.get">):
|
|
135
|
+
getOperation(params: RuntimeMethodParams<"operation.get">): LatestRuntimeMethodResult<"operation.get">;
|
|
136
|
+
getOperationResult(params: LatestRuntimeMethodParams<"operation.result.get">): LatestRuntimeMethodResult<"operation.result.get">;
|
|
136
137
|
close(): Promise<void>;
|
|
137
138
|
private assertOpen;
|
|
138
139
|
private requireThread;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,APPROVAL_EXPLANATION_PREVIEW_KEY as s,RUNTIME_ERROR_CODES as n,RUNTIME_FEATURES_V13 as i,RUNTIME_METHODS as a,RUNTIME_PROTOCOL_VERSION as o,RUNTIME_V14_MAX_ATTACHMENT_BYTES as d,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as u,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as h,RUNTIME_V14_MAX_TURN_ATTACHMENTS as c,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as l,approvalExplanationSchema as p,approvalIdSchema as m,normalizeUserInputResultForForm as I,operationIdSchema as v,parseLatestRuntimeMethodResult as g,runtimeDurableEventV13Schema as f,runtimeEphemeralEventV13Schema as y,runtimeInstanceIdSchema as T,runtimeProtocolVersionSchema as w,streamIdSchema as A,threadIdSchema as q,userInputFormSchema as S}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as x}from"../engine/capability-manifest.js";import{isSensitiveFieldName as E,redactSecretText as b,toRedactedToolExecutionRecordSummary as O}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as P,RuntimeEventCursorGapError as C,ThreadStore as U}from"../store/thread-store.js";const N=4194304,F=500,k=1e4,B=16e3,j=64,M=128,V=16,$=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(n.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=>g(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=>g(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(n.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 D(e,t,r=0){if(void 0!==t&&($.has(t.toLowerCase())||E(t)))return"[redacted]";if(r>=V)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=b(e);return t.length<=B?t:`${t.slice(0,B)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,j).map(e=>D(e,void 0,r+1));return e.length>j&&t.push(`[${String(e.length-j)} items omitted]`),t}if(z(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,M))void 0!==n&&(t[s]=D(n,s,r+1));return Object.keys(e).length>M&&(t._omitted=`${String(Object.keys(e).length-M)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function _(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 G(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function J(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:G(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:G(e.image)}]:[]:[]):[]}function H(e){return q.parse(e)}function K(e,t){return{id:H(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 Q(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=b(_(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...J(e.message)]}}function X(e){const t=O(e);return{id:v.parse(t.id),sequence:e.sequence,toolCallId:t.toolCallId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,outcome:{kind:t.outcome.kind,..."reason"in t.outcome&&void 0!==t.outcome.reason?{reason:t.outcome.reason}:{}},display:D(t.display,void 0)}}function Y(e,t){const n=D(t.input,void 0),i=void 0===t.explanation?void 0:p.safeParse(b(t.explanation)),a=void 0===t.diff?void 0:D(t.diff,void 0),o=z(n)?{...n,...!0===i?.success?{[s]:i.data}:{},...void 0!==a?{[r]:a}:{}}:n;return{id:m.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:b(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;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??N,this.idempotencyCacheEntries=s.idempotencyCacheEntries??k,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=T.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(n.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:l}});const r=i.filter(e=>"reasoning-summary"!==e||void 0!==this.reasoningSummaryProjector),s={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:F,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t){const e=this.attachmentStore?.limits;return{...s,protocolVersion:t,features:void 0!==this.attachmentStore?[...r,"attachments"]:[...r],limits:{...s.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??d,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??u,maxTurnAttachments:c,maxStagedAttachments:e?.maxStagedAttachments??h}}}return"1.3"===t?{...s,protocolVersion:t,limits:{...s.limits,eventReplay:!0}}:{...s,protocolVersion:t,limits:{...s.limits,eventReplay:!1}}}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(n.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>K(this.store,e)),i=t+s.length;return{items:s,nextCursor:i<r.length?String(i):null}}async createThread(e){return this.mutationRequests.run(e.requestId,a.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:K(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:K(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Q(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(X),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)}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof P)throw new RuntimeServiceError(n.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof C)throw new RuntimeServiceError(n.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,a.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:K(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,a.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(n.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,a.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:D(x(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,a.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(n.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(n.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(n.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(n.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(n.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const i=this.resolveTurnAttachments(e),a={threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0};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,i).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,a.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(n.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,a.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(n.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,a.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,a.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,a.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,a.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(n.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,i=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(n.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),i.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return i}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}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:X(t)}}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.attachmentStore?.close(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear(),this.fatalErrorListeners.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(n.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(n.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:o,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:s,threadId:e,...void 0!==t?{turnId:t}:{}},i=f.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:y.parse(r)};this.sequence+=1;for(const e of this.listeners)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{}}async driveTurn(e,t,r){const s={streamId:void 0,text:"",terminalEvent:void 0},n=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let i;try{for await(const t of e.session.send(n))this.projectSessionEvent(e,s,t);i=s.terminalEvent??{type:"turn.completed"}}catch(e){i=s.terminalEvent??{type:"turn.failed",stage:"execute",message:b(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}this.emitTurnTerminal(e,i)}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":{const s=A.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??A.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":{const n=r.streamId??A.parse(t());return void this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:b(s.text||r.text)})}case"reasoning-delta":{const t=this.reasoningSummaryProjector?.(s.delta);return void(void 0!==t&&t.length>0&&this.emit(e.threadId,e.turnId,{type:"reasoning.summary.delta",reasoningId:s.reasoningId,delta:b(t)}))}case"tool-call":return void this.emit(e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:D(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:b(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:v.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:D(s.display,void 0)});case"confirmation-required":{const t=Y(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt}),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=S.parse(s.form),r=S.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.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:b(s.message)}:{type:"turn.failed",stage:"execute",message:b(e.interactionFailure)};break;case"error":r.terminalEvent??={type:"turn.failed",stage:s.stage,message:b(s.message)}}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:b(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(n.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:b(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 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 I,normalizeUserInputResultForForm as v,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV15Schema as y,runtimeEphemeralEventV13Schema as T,runtimeInstanceIdSchema as A,runtimeProtocolVersionSchema as w,streamIdSchema as q,threadIdSchema as S,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as E}from"../engine/capability-manifest.js";import{isSensitiveFieldName as O,redactSecretText as b,toRedactedToolExecutionRecordSummary as P}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as C,RuntimeEventCursorGapError as U,ThreadStore as N}from"../store/thread-store.js";const F=4194304,k=500,B=1e4,j=16e3,M=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 D(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=D(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 _(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(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=b(e);return t.length<=j?t:`${t.slice(0,j)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,M).map(e=>z(e,void 0,r+1));return e.length>M&&t.push(`[${String(e.length-M)} 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 J(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function H(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:J(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:J(e.image)}]:[]:[]):[]}function K(e){return S.parse(e)}function Q(e,t){return{id:K(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 X(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=b(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...H(e.message)]}}function Y(e,t={status:"not_provided"}){const r=P(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 Z(e,t){const s=z(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(b(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}:{}}:s;return{id:I.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:b(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;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??F,this.idempotencyCacheEntries=s.idempotencyCacheEntries??B,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,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||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:k,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===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}}}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=>Q(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:Q(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:Q(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=X(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Y(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)}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof C)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof U)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:Q(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:z(E(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};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).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=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}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Y(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.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;for(const e of this.listeners)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{}}async driveTurn(e,t,r){const s={streamId:void 0,text:"",terminalEvent:void 0},n=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let i;try{for await(const t of e.session.send(n))this.projectSessionEvent(e,s,t);i=s.terminalEvent??{type:"turn.completed"}}catch(e){i=s.terminalEvent??{type:"turn.failed",stage:"execute",message:b(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}this.emitTurnTerminal(e,i)}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":{const s=q.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??q.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":{const n=r.streamId??q.parse(t());return void this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:b(s.text||r.text)})}case"reasoning-delta":{const t=this.reasoningSummaryProjector?.(s.delta);return void(void 0!==t&&t.length>0&&this.emit(e.threadId,e.turnId,{type:"reasoning.summary.delta",reasoningId:s.reasoningId,delta:b(t)}))}case"tool-call":return void this.emit(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.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:b(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:z(s.display,void 0)});case"confirmation-required":{const t=Z(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt}),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.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:b(s.message)}:{type:"turn.failed",stage:"execute",message:b(e.interactionFailure)};break;case"error":r.terminalEvent??={type:"turn.failed",stage:s.stage,message:b(s.message)}}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:b(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:b(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)}}
|
|
@@ -10,19 +10,19 @@ export declare const scheduledThreadOriginSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
10
10
|
scheduledFor: z.ZodString;
|
|
11
11
|
ledgerDir: z.ZodString;
|
|
12
12
|
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
kind: "scheduled";
|
|
13
14
|
name: string;
|
|
14
15
|
cwd: string;
|
|
15
16
|
attempt: number;
|
|
16
|
-
kind: "scheduled";
|
|
17
17
|
scheduleId: string;
|
|
18
18
|
invocationId: string;
|
|
19
19
|
scheduledFor: string;
|
|
20
20
|
ledgerDir: string;
|
|
21
21
|
}, {
|
|
22
|
+
kind: "scheduled";
|
|
22
23
|
name: string;
|
|
23
24
|
cwd: string;
|
|
24
25
|
attempt: number;
|
|
25
|
-
kind: "scheduled";
|
|
26
26
|
scheduleId: string;
|
|
27
27
|
invocationId: string;
|
|
28
28
|
scheduledFor: string;
|
|
@@ -44,19 +44,19 @@ export declare const threadOriginSchema: z.ZodReadonly<z.ZodDiscriminatedUnion<"
|
|
|
44
44
|
scheduledFor: z.ZodString;
|
|
45
45
|
ledgerDir: z.ZodString;
|
|
46
46
|
}, "strict", z.ZodTypeAny, {
|
|
47
|
+
kind: "scheduled";
|
|
47
48
|
name: string;
|
|
48
49
|
cwd: string;
|
|
49
50
|
attempt: number;
|
|
50
|
-
kind: "scheduled";
|
|
51
51
|
scheduleId: string;
|
|
52
52
|
invocationId: string;
|
|
53
53
|
scheduledFor: string;
|
|
54
54
|
ledgerDir: string;
|
|
55
55
|
}, {
|
|
56
|
+
kind: "scheduled";
|
|
56
57
|
name: string;
|
|
57
58
|
cwd: string;
|
|
58
59
|
attempt: number;
|
|
59
|
-
kind: "scheduled";
|
|
60
60
|
scheduleId: string;
|
|
61
61
|
invocationId: string;
|
|
62
62
|
scheduledFor: string;
|
|
@@ -80,19 +80,19 @@ export declare const threadDerivedFromSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
80
80
|
scheduledFor: z.ZodString;
|
|
81
81
|
ledgerDir: z.ZodString;
|
|
82
82
|
}, "strict", z.ZodTypeAny, {
|
|
83
|
+
kind: "scheduled";
|
|
83
84
|
name: string;
|
|
84
85
|
cwd: string;
|
|
85
86
|
attempt: number;
|
|
86
|
-
kind: "scheduled";
|
|
87
87
|
scheduleId: string;
|
|
88
88
|
invocationId: string;
|
|
89
89
|
scheduledFor: string;
|
|
90
90
|
ledgerDir: string;
|
|
91
91
|
}, {
|
|
92
|
+
kind: "scheduled";
|
|
92
93
|
name: string;
|
|
93
94
|
cwd: string;
|
|
94
95
|
attempt: number;
|
|
95
|
-
kind: "scheduled";
|
|
96
96
|
scheduleId: string;
|
|
97
97
|
invocationId: string;
|
|
98
98
|
scheduledFor: string;
|
|
@@ -102,10 +102,10 @@ export declare const threadDerivedFromSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
102
102
|
}, "strict", z.ZodTypeAny, {
|
|
103
103
|
threadId: string;
|
|
104
104
|
origin: Readonly<{
|
|
105
|
+
kind: "scheduled";
|
|
105
106
|
name: string;
|
|
106
107
|
cwd: string;
|
|
107
108
|
attempt: number;
|
|
108
|
-
kind: "scheduled";
|
|
109
109
|
scheduleId: string;
|
|
110
110
|
invocationId: string;
|
|
111
111
|
scheduledFor: string;
|
|
@@ -117,10 +117,10 @@ export declare const threadDerivedFromSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
117
117
|
}, {
|
|
118
118
|
threadId: string;
|
|
119
119
|
origin: Readonly<{
|
|
120
|
+
kind: "scheduled";
|
|
120
121
|
name: string;
|
|
121
122
|
cwd: string;
|
|
122
123
|
attempt: number;
|
|
123
|
-
kind: "scheduled";
|
|
124
124
|
scheduleId: string;
|
|
125
125
|
invocationId: string;
|
|
126
126
|
scheduledFor: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ModelMessage } from "ai";
|
|
2
|
-
import { type RuntimeDurableEventV13, type RuntimeEventCursor, type RuntimeEventId } from "@roll-agent/protocol";
|
|
2
|
+
import { type AppOutputResult, type AppOutputDescriptor, type RuntimeDurableEventV13, type RuntimeDurableEventV15, type RuntimeEventCursor, type RuntimeEventId } from "@roll-agent/protocol";
|
|
3
3
|
import { type ToolExecutionPersistenceMetadata, type ToolExecutionRecord, type ToolExecutionRecordId } from "../tool-bridge/tool-execution-record.ts";
|
|
4
4
|
import { COMPACTION_TRANSCRIPT_COMPLETENESS, type ArchivedTranscriptMessage, type CompactionCheckpoint, type CompactionCheckpointDraftInput, type CompactionSemanticEvidenceWatermarks } from "../engine/compaction-checkpoint.ts";
|
|
5
5
|
import { type CompactionSemanticState } from "../engine/compaction-semantic-state.ts";
|
|
@@ -18,7 +18,7 @@ export interface AppendRuntimeEventInput {
|
|
|
18
18
|
readonly threadId: string;
|
|
19
19
|
readonly turnId?: string;
|
|
20
20
|
readonly timestamp: string;
|
|
21
|
-
readonly event: RuntimeDurableEventV13;
|
|
21
|
+
readonly event: RuntimeDurableEventV13 | RuntimeDurableEventV15;
|
|
22
22
|
}
|
|
23
23
|
export interface StoredRuntimeEvent {
|
|
24
24
|
readonly eventId: RuntimeEventId;
|
|
@@ -27,7 +27,7 @@ export interface StoredRuntimeEvent {
|
|
|
27
27
|
readonly threadId: string;
|
|
28
28
|
readonly turnId?: string;
|
|
29
29
|
readonly timestamp: string;
|
|
30
|
-
readonly event:
|
|
30
|
+
readonly event: RuntimeDurableEventV15;
|
|
31
31
|
}
|
|
32
32
|
export interface ResumeRuntimeEventsResult {
|
|
33
33
|
readonly events: readonly StoredRuntimeEvent[];
|
|
@@ -82,6 +82,11 @@ export interface ForkThreadSnapshotInput {
|
|
|
82
82
|
readonly model?: string;
|
|
83
83
|
}
|
|
84
84
|
export interface ThreadSnapshot {
|
|
85
|
+
readonly appOutputs?: readonly {
|
|
86
|
+
readonly operationId: string;
|
|
87
|
+
readonly result: AppOutputResult;
|
|
88
|
+
readonly expiresAt: string;
|
|
89
|
+
}[];
|
|
85
90
|
readonly thread: ThreadRecord;
|
|
86
91
|
readonly capturedAt: string;
|
|
87
92
|
readonly messages: readonly ModelMessage[];
|
|
@@ -219,6 +224,12 @@ export declare class ThreadStore {
|
|
|
219
224
|
getMessages(threadId: string): ModelMessage[];
|
|
220
225
|
private replaceMessagesInTransaction;
|
|
221
226
|
appendToolExecution(threadId: string, record: ToolExecutionRecord): number;
|
|
227
|
+
private readAppOutputRow;
|
|
228
|
+
private insertAppOutput;
|
|
229
|
+
private enforceAppOutputRetention;
|
|
230
|
+
/** Reads only a small descriptor; snapshots never materialize application bodies. */
|
|
231
|
+
getAppOutputDescriptor(threadId: string, operationId: string): AppOutputDescriptor | undefined;
|
|
232
|
+
getAppOutput(threadId: string, operationId: string): AppOutputResult | undefined;
|
|
222
233
|
private enforceToolExecutionRetentionInTransaction;
|
|
223
234
|
listToolExecutions(threadId: string, options?: ListToolExecutionsOptions): SequencedToolExecutionRecord[];
|
|
224
235
|
listUncoveredToolExecutions(threadId: string, options?: ListToolExecutionsOptions): SequencedToolExecutionRecord[];
|