@roll-agent/runtime 0.20.0 → 0.21.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/compaction-semantic-state.d.ts +2 -2
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/engine/model-catalog.d.ts +1 -1
- package/dist/engine/model-catalog.js +1 -1
- package/dist/tool-bridge/build-tools.js +1 -1
- package/dist/tool-bridge/tool-approval-continuation.d.ts +14 -0
- package/dist/tool-bridge/tool-approval-continuation.js +1 -0
- package/dist/types/cancellation.d.ts +2 -2
- package/package.json +2 -2
|
@@ -1565,13 +1565,13 @@ export declare const compactionSemanticReminderProjectionSchema: z.ZodReadonly<z
|
|
|
1565
1565
|
uncertainty: number;
|
|
1566
1566
|
}>>;
|
|
1567
1567
|
}, "strict", z.ZodTypeAny, {
|
|
1568
|
+
version: 1;
|
|
1568
1569
|
items: Readonly<{
|
|
1569
1570
|
text: string;
|
|
1570
1571
|
sourceQuotes: string[];
|
|
1571
1572
|
itemId: string & z.BRAND<"CompactionSemanticItemId">;
|
|
1572
1573
|
category: "resource" | "decision" | "running_session" | "goal" | "constraint" | "completed_work" | "pending_work" | "uncertainty";
|
|
1573
1574
|
}>[];
|
|
1574
|
-
version: 1;
|
|
1575
1575
|
omittedCounts: Readonly<{
|
|
1576
1576
|
resource: number;
|
|
1577
1577
|
decision: number;
|
|
@@ -1583,13 +1583,13 @@ export declare const compactionSemanticReminderProjectionSchema: z.ZodReadonly<z
|
|
|
1583
1583
|
uncertainty: number;
|
|
1584
1584
|
}>;
|
|
1585
1585
|
}, {
|
|
1586
|
+
version: 1;
|
|
1586
1587
|
items: Readonly<{
|
|
1587
1588
|
text: string;
|
|
1588
1589
|
sourceQuotes: string[];
|
|
1589
1590
|
itemId: string;
|
|
1590
1591
|
category: "resource" | "decision" | "running_session" | "goal" | "constraint" | "completed_work" | "pending_work" | "uncertainty";
|
|
1591
1592
|
}>[];
|
|
1592
|
-
version: 1;
|
|
1593
1593
|
omittedCounts: Readonly<{
|
|
1594
1594
|
resource: number;
|
|
1595
1595
|
decision: number;
|
|
@@ -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}from"@roll-agent/core/registry/agent-usage-lease";import{formatToolSchemaIssue as d,normalizeListedTools as h}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as u}from"@roll-agent/core/config/helpers";import{catalogPackageSpec as p,getAgentCatalog as g}from"@roll-agent/core/registry/catalog";import{resolveAgentCatalog as m}from"@roll-agent/core/registry/catalog-discovery";import{installAgent as v}from"@roll-agent/core/registry/install";import{createSkillLibrary as f}from"@roll-agent/core/skills/library";import{ROLL_RESOURCE_HINTS_META_KEY as w}from"../tool-bridge/build-tools.js";import{TOOL_RESOURCE_ACCESS_MODES as y,TOOL_RESOURCE_HINT_KINDS as S}from"../tool-bridge/tool-execution-coordinator.js";import{AgentSession as C}from"./agent-session.js";import{resolveModelContextWindow as b}from"./context-window.js";import{createDefaultModelCatalog as x}from"./model-catalog-default.js";import{createWorkspaceInstructionsSource as k,parseWorkspaceInstructionsSetting as A}from"./workspace-instructions.js";import{CAPABILITY_HOST_MODES as O,shouldOfferAgentInstall as E,shouldOfferScheduleCreate as P}from"./capability-manifest.js";import{}from"../tool-bridge/bash-tool.js";import{unknownCommandClassifier as M}from"../types/command-classification.js";import{resolveShellProfile as T}from"../bash/profile.js";import{inspectGitVcsContext as N}from"./vcs-context.js";import{AGENT_BOOTSTRAP_MAX_CONCURRENCY as I,mapWithBoundedConcurrency as L}from"./agent-bootstrap.js";import{createToolLedgerGapRecoveryMessage as R}from"./cancelled-turn-recovery.js";const U=80,B="ConversationEngine is closing",j=3e4,$=6e4,F={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 D(){return new Error(B)}function W(e){return e.reason??new Error("Operation aborted")}function H(e){if(!0===e?.aborted)throw W(e)}function q(e,t){return void 0===t?e:Math.max(1,Math.min(e,t-Date.now()))}function V(e){if(void 0!==e&&Date.now()>=e)throw new Error("Agent bootstrap deadline expired")}function _(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 K(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Y(e){return Object.values(S).some(t=>t===e)}function z(e){return Object.values(y).some(t=>t===e)}function G(e){return{hints:void 0,issue:e}}function J(e){if(!K(e)||!K(e._meta))return{hints:void 0,issue:void 0};if(!Object.hasOwn(e._meta,w))return{hints:void 0,issue:void 0};const t=e._meta[w];if(!Array.isArray(t))return G("必须是数组");const s=[];for(const[e,i]of t.entries()){if(!K(i)||"string"!=typeof i.field||0===i.field.trim().length||"string"!=typeof i.kind||!Y(i.kind)||void 0!==i.mode&&("string"!=typeof i.mode||!z(i.mode))||void 0!==i.namespace&&"string"!=typeof i.namespace||i.kind===S.custom&&("string"!=typeof i.namespace||0===i.namespace.trim().length))return G(`第 ${String(e+1)} 项字段无效`);const t=i.kind===S.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 Q(e){return"retry"===e.type?`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`:"warn"===e.type?`警告:${e.message}`:e.message}async function X(e,t,s,i){H(i),"core-managed"===e.runtime.ownership&&(void 0===n(t,e.skill.name)&&a(e,t,s),await l(e,{...i?{signal:i}:{}}))}function Z(e){if(null===e.workspaceInstructions)return;if(void 0!==e.workspaceInstructions)return e.workspaceInstructions;const t=process.cwd();return k({cwd:t,setting:A(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??x(),this.policy=e.policy,this.fileToolsEnabled=e.fileToolsEnabled??!0,this.maxSteps=e.maxSteps??U,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??O.embedded,this.scheduleTools=e.scheduleTools??void 0,this.resolveDynamicCapabilityContext=e.resolveDynamicCapabilityContext,this.sessionExecEnabled=e.sessionExecEnabled??!0,this.explicitShellProfile=e.shellProfile,this.resolveShellProfileFn=e.resolveShellProfileFn??T,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.workspaceInstructions=Z(e),this.onAgentBootstrapIssue=e.onAgentBootstrapIssue,this.installAgentFn=e.installAgentFn??v,this.resolveCatalogFn=e.resolveCatalogFn??m,this.inspectVcsContext=e.inspectVcsContext??N,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 X(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 D()}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:M:void 0,p=E(this.hostMode)?this.resolveAgentInstallBinding():void 0,g=this.scheduleTools,m=this.composeCapabilityContext(e.sources.length,l),v=new C({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:P(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,R);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(W(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:F.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 D();if(d(),!l)throw e}if(this.closing||this.shutdownController.signal.aborted)throw D();if(d(),0===s.length){const e=this.resolveSkillLibrary(s);return{model:t,sources:[],...e?{skillLibrary:e}:{}}}const e=await L(s,I,async e=>{const s=[],i=e=>{s.push(e)};try{d(),H(n.signal);const o=await this.connectAgentSource(e,t,i,{signal:n.signal,deadlineAt:r});return{kind:F.connected,source:o,issues:s}}catch(t){if(this.closing||this.shutdownController.signal.aborted)throw D();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:F.failed,issues:s})}},{signal:n.signal,onSkipped:e=>u(e)});if(this.closing||this.shutdownController.signal.aborted)throw D();const i=[];for(const t of e){for(const e of t.issues)this.onAgentBootstrapIssue?.(e);t.kind===F.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(),H(i.signal),V(i.deadlineAt);const o=r(e),n=u(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(),H(i.signal),V(i.deadlineAt);const r={timeoutMs:q(j,i.deadlineAt),samplingModel:t,...this.providerOptions?{samplingProviderOptions:this.providerOptions}:{},...n?{env:n}:{},...i.signal?{signal:i.signal}:{}},u=this.clientManager.connectWithOwnership;c="function"==typeof u?await u.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(),H(i.signal),V(i.deadlineAt);const p=(await c.client.listTools(void 0,{timeout:q($,i.deadlineAt),...i.signal?{signal:i.signal}:{}})).tools;this.assertAcceptingSessions(),H(i.signal),V(i.deadlineAt);const g=h(p,{onSchemaIssue:t=>s({agentName:e.skill.name,message:d(e.skill.name,t)})}).map((t,i)=>{const o=J(p[i]);return void 0!==o.issue&&s({agentName:e.skill.name,message:`Tool "${t.name}" 的 ${w} 无效(${o.issue}),已回退 Agent 级资源锁`}),{tool:t,annotations:_(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??g(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:p(s),skipBrowserSetup:!0,autoStart:!1,expectedSkillName:s.skillName},{agentsConfig:this.config.agents,installConfig:this.config.install,getStartEnv:e=>u(this.config,e),report:e=>t(Q(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:f({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 b({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(D());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()}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{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}}
|
|
@@ -67,6 +67,6 @@ export declare class ModelCatalog {
|
|
|
67
67
|
constructor(options: ModelCatalogOptions);
|
|
68
68
|
data(): ModelCatalogData;
|
|
69
69
|
lookup(provider: string, modelName: string): number | undefined;
|
|
70
|
-
refreshIfStale(): Promise<ModelCatalogRefreshResult>;
|
|
70
|
+
refreshIfStale(signal?: AbortSignal): Promise<ModelCatalogRefreshResult>;
|
|
71
71
|
}
|
|
72
72
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdirSync as t,readFileSync as e,renameSync as o,writeFileSync as n}from"node:fs";import{homedir as s}from"node:os";import{dirname as r,resolve as a}from"node:path";import{z as i}from"zod";export const MODEL_CATALOG_SOURCE_URL="https://models.dev/api.json";export const MODEL_CATALOG_PROVIDER_IDS=["openai","anthropic","google","deepseek","xai","alibaba-cn","alibaba"];const c={openai:["openai"],anthropic:["anthropic"],google:["google"],deepseek:["deepseek"],xai:["xai"],qwen:["alibaba-cn","alibaba"]},h=i.object({context:i.number().int().positive(),input:i.number().int().positive().optional()});export const modelCatalogDataSchema=i.object({fetchedAt:i.string().datetime(),providers:i.record(i.string(),i.record(i.string(),h))});const d=i.record(i.string(),i.object({models:i.record(i.string(),i.object({limit:i.unknown().optional()}))}));function p(t){return MODEL_CATALOG_PROVIDER_IDS.some(e=>e===t)}export function trimModelCatalog(t,e){const o=d.parse(t),n={};for(const[t,e]of Object.entries(o)){if(!p(t))continue;const o={};for(const[t,n]of Object.entries(e.models)){const e=h.safeParse(n.limit);e.success&&(o[t]={context:e.data.context,...void 0!==e.data.input?{input:e.data.input}:{}})}Object.keys(o).length>0&&(n[t]=o)}const s=modelCatalogDataSchema.parse({fetchedAt:e,providers:n});if(!u(s))throw new Error("model catalog contains no supported models");return s}function l(t){return Object.values(t.providers).reduce((t,e)=>t+Object.keys(e).length,0)}function u(t){return l(t)>0}const f=/-\d{8}$/u,m="-latest";function E(t){const e=[t];let o=t;for(;;){const t=o.endsWith(m)?o.slice(0,-7):f.test(o)?o.replace(f,""):void 0;if(void 0===t||0===t.length)return e;e.push(t),o=t}}export function lookupCatalogContextWindow(t,e,o){const n=c[e]??[e],s=E(o);for(const e of n){const o=t.providers[e];if(o)for(const t of s){const e=o[t];if(e)return e.input??e.context}}}export const MODEL_CATALOG_REFRESH_RESULTS={skipped:"skipped",refreshed:"refreshed",failed:"failed"};const
|
|
1
|
+
import{mkdirSync as t,readFileSync as e,renameSync as o,writeFileSync as n}from"node:fs";import{homedir as s}from"node:os";import{dirname as r,resolve as a}from"node:path";import{z as i}from"zod";export const MODEL_CATALOG_SOURCE_URL="https://models.dev/api.json";export const MODEL_CATALOG_PROVIDER_IDS=["openai","anthropic","google","deepseek","xai","alibaba-cn","alibaba"];const c={openai:["openai"],anthropic:["anthropic"],google:["google"],deepseek:["deepseek"],xai:["xai"],qwen:["alibaba-cn","alibaba"]},h=i.object({context:i.number().int().positive(),input:i.number().int().positive().optional()});export const modelCatalogDataSchema=i.object({fetchedAt:i.string().datetime(),providers:i.record(i.string(),i.record(i.string(),h))});const d=i.record(i.string(),i.object({models:i.record(i.string(),i.object({limit:i.unknown().optional()}))}));function p(t){return MODEL_CATALOG_PROVIDER_IDS.some(e=>e===t)}export function trimModelCatalog(t,e){const o=d.parse(t),n={};for(const[t,e]of Object.entries(o)){if(!p(t))continue;const o={};for(const[t,n]of Object.entries(e.models)){const e=h.safeParse(n.limit);e.success&&(o[t]={context:e.data.context,...void 0!==e.data.input?{input:e.data.input}:{}})}Object.keys(o).length>0&&(n[t]=o)}const s=modelCatalogDataSchema.parse({fetchedAt:e,providers:n});if(!u(s))throw new Error("model catalog contains no supported models");return s}function l(t){return Object.values(t.providers).reduce((t,e)=>t+Object.keys(e).length,0)}function u(t){return l(t)>0}const f=/-\d{8}$/u,m="-latest";function E(t){const e=[t];let o=t;for(;;){const t=o.endsWith(m)?o.slice(0,-7):f.test(o)?o.replace(f,""):void 0;if(void 0===t||0===t.length)return e;e.push(t),o=t}}export function lookupCatalogContextWindow(t,e,o){const n=c[e]??[e],s=E(o);for(const e of n){const o=t.providers[e];if(o)for(const t of s){const e=o[t];if(e)return e.input??e.context}}}export const MODEL_CATALOG_REFRESH_RESULTS={skipped:"skipped",refreshed:"refreshed",failed:"failed"};const S=864e5,O=1e4;export function defaultModelCatalogCachePath(){return a(s(),".roll-agent","cache","model-catalog.json")}function _(t){try{const o=JSON.parse(e(t,"utf8")),n=modelCatalogDataSchema.safeParse(o);return n.success&&u(n.data)?n.data:void 0}catch{return}}function g(t,e,o){const n=Date.parse(t.fetchedAt);return n<=o&&n>Date.parse(e.fetchedAt)}export class ModelCatalog{snapshot;cachePath;ttlMs;fetchImpl;now;timeoutMs;current;constructor(t){this.snapshot=t.snapshot,this.cachePath=t.cachePath,this.ttlMs=t.ttlMs??S,this.fetchImpl=t.fetchImpl??fetch,this.now=t.now??Date.now,this.timeoutMs=t.timeoutMs??O}data(){if(this.current)return this.current;const t=void 0===this.cachePath?void 0:_(this.cachePath);return this.current=t&&g(t,this.snapshot,this.now())?t:this.snapshot,this.current}lookup(t,e){return lookupCatalogContextWindow(this.data(),t,e)}async refreshIfStale(e){if(e?.aborted)return MODEL_CATALOG_REFRESH_RESULTS.skipped;const s=this.cachePath;if(void 0===s)return MODEL_CATALOG_REFRESH_RESULTS.skipped;const a=this.now(),i=a-Date.parse(this.data().fetchedAt);if(i>=0&&i<this.ttlMs)return MODEL_CATALOG_REFRESH_RESULTS.skipped;try{const i=await this.fetchImpl(MODEL_CATALOG_SOURCE_URL,{signal:e?AbortSignal.any([e,AbortSignal.timeout(this.timeoutMs)]):AbortSignal.timeout(this.timeoutMs)});if(!i.ok)return MODEL_CATALOG_REFRESH_RESULTS.failed;const c=trimModelCatalog(await i.json(),new Date(a).toISOString());e?.throwIfAborted(),t(r(s),{recursive:!0});const h=`${s}.tmp`;return n(h,JSON.stringify(c)),o(h,s),this.current=c,MODEL_CATALOG_REFRESH_RESULTS.refreshed}catch{return MODEL_CATALOG_REFRESH_RESULTS.failed}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{lstatSync as e,readdirSync as
|
|
1
|
+
import{lstatSync as e,readdirSync as n,readlinkSync as o,realpathSync as t,statSync as r}from"node:fs";import{inlineAcyclicLocalJsonSchemaReferences as i}from"@roll-agent/core/tool-runtime/json-schema-refs";import{basename as s,dirname as a,isAbsolute as c,resolve as u}from"node:path";import{jsonSchema as l,tool as f}from"ai";import{preflightToolCall as d}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as p}from"./naming.js";import{TOOL_OUTCOME_KINDS as m,failedToolResult as v,toolResultToModelOutput as g}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as h,TOOL_RESOURCE_HINT_KINDS as y,executeCoordinatedTool as S}from"./tool-execution-coordinator.js";import{executeWithToolApproval as $}from"./tool-approval-continuation.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function b(e,n){const o=new Map;for(const t of[...e??[],...n])o.set(`${t.path}\0${t.ref}`,t);return[...o.values()]}function w(e){return"object"!=typeof e||null===e||Array.isArray(e)?{}:e}function C(e){return`参数校验失败: ${e.map(e=>e.message).join("; ")}`}function N(e){return!0===e?.readOnlyHint&&!0!==e.destructiveHint?h.read:h.write}function k(e){return"string"==typeof e||"number"==typeof e?[e]:Array.isArray(e)&&0!==e.length&&e.every(e=>"string"==typeof e||"number"==typeof e)?e:void 0}const A=256,O=new Map;function j(e,n){for(let o=0;o<e.length;o+=1){const t=e.charCodeAt(o),r=t>=65&&t<=90?String.fromCharCode(t+32):t>=97&&t<=122?String.fromCharCode(t-32):void 0;if(void 0===r)continue;const i=`${e.slice(0,o)}${r}${e.slice(o+1)}`;if(!n.has(i))return i}}function x(e){return"object"==typeof e&&null!==e&&"code"in e?e.code:void 0}function T(n,o,t){const r=j(o,t);if(void 0!==r)try{const t=e(u(n,o)),i=e(u(n,r));return t.dev===i.dev&&t.ino===i.ino}catch(e){const n=x(e);return"ENOENT"!==n&&"ENOTDIR"!==n&&void 0}}function I(e,n){if(O.size>=A){const e=O.keys().next().value;void 0!==e&&O.delete(e)}return O.set(e,n),n}function L(o){const t=O.get(o);if(void 0!==t)return t;let r;try{r=e(o).dev}catch{return I(o,!0)}let i=o;for(;;){const t=O.get(i);if(void 0!==t)return I(o,t);try{const e=n(i),t=new Set(e);for(const n of e){const e=T(i,n,t);if(void 0!==e)return I(i,e),I(o,e)}}catch{}const s=a(i);if(s===i)break;try{if(e(s).dev!==r)break}catch{break}i=s}return I(o,!0)}function E(e){let n=e.normalize("NFC");for(let e=0;e<8;e+=1){const e=n.toUpperCase().toLowerCase().normalize("NFC");if(e===n)return n;n=e}return n}function M(e,n){try{const o=r(e,{bigint:!0}),t=n.map(e=>E(e));return`file-unresolved-alias:${o.dev.toString()}:${o.ino.toString()}:${JSON.stringify(t)}`}catch{return}}function R(n,r=new Set){let i=n;const c=[];for(;;)try{const e=t.native(i),n=c.reverse().map(e=>e.normalize("NFC"));if(0===n.length)return{path:e};const o=L(e),r=u(e,...o?n.map(e=>e.toLowerCase()):n),s=o?M(e,n):void 0;return{path:r,...void 0===s?{}:{unresolvedAliasKey:s}}}catch{try{if(e(i).isSymbolicLink()&&!r.has(i)){const e=new Set(r);e.add(i);const n=o(i);return R(u(a(i),n,...[...c].reverse()),e)}}catch{}const t=a(i);if(t===i)return{path:n};c.push(s(i)),i=t}}function z(e){try{const n=r(e,{bigint:!0});return`file-inode:${n.dev.toString()}:${n.ino.toString()}`}catch{return}}function H(e,n,o){const t=String(n).trim();if(0!==t.length)switch(e.kind){case y.file:{if(!c(t)&&void 0===o)return;const e=R(u(o??"/",t)),n=z(e.path);return[`file:${e.path}`,...void 0===e.unresolvedAliasKey?[]:[e.unresolvedAliasKey],...void 0===n?[]:[n]]}case y.browserSession:return[`browser-session:${t}`];case y.conversation:return[`conversation:${t}`];case y.custom:{const n=e.namespace?.trim();return n?[`${n}:${t}`]:void 0}}}function K(e,n,o,t,r){const i=N(t),s=w(o),a=[{key:`agent:${e}`,mode:i}],c=[];for(const e of r??[]){if(!Object.hasOwn(s,e.field))continue;const o=k(s[e.field]);if(void 0===o)return a;for(const t of o){const o=H(e,t,n);if(void 0===o||0===o.length)return a;c.push(...o.map(n=>({key:n,mode:e.mode??i})))}}return 0===c.length?a:[{key:`agent:${e}`,mode:h.read},...c]}export async function gateToolCall(e,n,o,t,r,i){if(!e.policy)return;const s=e.policy.check({agentName:n,toolName:o,input:t,...r?{annotations:r}:{}});if("deny"===s.action)return v(m.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=i?.memoryKey;if(void 0!==r&&e.approvalMemory?.isGranted(r))return;const a=await e.requestApproval({agentName:n,toolName:o,input:t,reason:!1===i?.includePolicyReason?void 0:s.reason,...void 0!==i?.explanation?{explanation:i.explanation}:{},...void 0!==i?.diff?{diff:i.diff}:{},...void 0!==r&&void 0!==i?.sessionGrantLabel?{sessionGrantLabel:i.sessionGrantLabel}:{}});if(!a.approved)return v(m.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{});void 0!==r&&"session"===a.scope&&e.approvalMemory?.grant(r)}}export function buildAgentToolset(e,n,o=new p){const t={},r={};for(const s of e){const{client:e,agentName:a,agentSource:c,transport:u,runtimeOwnership:p,resourceBaseDir:h}=s;for(const{tool:y,annotations:N,resourceHints:k}of s.tools){const s=i(y.inputSchema),A=b(y.schemaIssues,s.unresolved),O={...y,inputSchema:s.schema,...A.length>0?{schemaIssues:A}:{}},j=o.register(a,O.name,{...c?{agentSource:c}:{},...u?{transport:u}:{},...p?{runtimeOwnership:p}:{},...N?{annotations:N}:{}});A.length>0&&(r[j]=A);const x={prepare:async e=>{const o=w(e),t=d(O,o);return t.ok?gateToolCall(n,a,O.name,o,N):v(m.invalidInput,C(t.issues),{raw:t.issues})},resources:e=>K(a,h,e,N,k)};n.coordinator?.register(j,x),t[j]=f({description:O.description??`${O.name} (via ${a})`,inputSchema:l(O.inputSchema),toModelOutput:({output:e})=>g(e),execute:async(o,t)=>{const r=w(o);return S(n.coordinator,x,j,t.toolCallId,r,t.abortSignal,async()=>{const o=t.abortSignal?{signal:t.abortSignal}:void 0;return $({input:r,agentName:a,agentTool:O,annotations:N,ctx:n,signal:t.abortSignal,call:async n=>await e.callTool({name:O.name,arguments:n},void 0,o)})})}})}}return{tools:t,registry:o,schemaIssuesByToolId:r}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentTool } from "@roll-agent/core/types/agent";
|
|
2
|
+
import type { ToolAnnotations } from "../types/policy.ts";
|
|
3
|
+
import type { ToolBridgeContext } from "./build-tools.ts";
|
|
4
|
+
import { type NormalizedToolResult } from "./normalize-result.ts";
|
|
5
|
+
/** Only a producer's explicit pre-effect challenge can continue, once, after real user approval. */
|
|
6
|
+
export declare function executeWithToolApproval(options: {
|
|
7
|
+
input: Record<string, unknown>;
|
|
8
|
+
agentName: string;
|
|
9
|
+
agentTool: AgentTool;
|
|
10
|
+
annotations: ToolAnnotations | undefined;
|
|
11
|
+
ctx: ToolBridgeContext;
|
|
12
|
+
signal: AbortSignal | undefined;
|
|
13
|
+
call: (input: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
}): Promise<NormalizedToolResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as t}from"zod";import{preflightToolCall as e}from"@roll-agent/core/tool-runtime/preflight";import{createToolResult as r,failedToolResult as o,normalizeToolResult as n,TOOL_OUTCOME_KINDS as a}from"./normalize-result.js";const i=t.object({id:t.string().min(1).max(256)}).strict(),s=t.object({code:t.literal("needs_confirmation"),details:t.object({executionState:t.literal("not_executed"),approvalRequest:t.object({id:t.string().min(1).max(256),tool:t.string().min(1).max(256),expiresAt:t.string().datetime(),summary:t.string().max(2e3).optional(),retryInput:t.union([t.object({scriptApproval:i}).strict(),t.object({toolActionApproval:i}).strict()])})})});function c(e,r){const o=t.object({isError:t.literal(!0),content:t.array(t.object({type:t.literal("text"),text:t.string().max(65536)})).length(1)}).safeParse(e);if(!o.success)return;let n;try{n=JSON.parse(o.data.content[0].text)}catch{return}const a=s.safeParse(n);if(!a.success)return;const i=a.data.details.approvalRequest,c="scriptApproval"in i.retryInput?i.retryInput.scriptApproval:i.retryInput.toolActionApproval;return i.tool!==r||c.id!==i.id||Date.parse(i.expiresAt)<=Date.now()?void 0:i}function l(){return r({kind:a.cancelled,executionState:"not_executed"},"已取消审批;工具未执行操作")}async function u(t,e){if(void 0===e)return t;if(e.aborted)return;let r=()=>{};const o=new Promise(t=>{r=()=>t(void 0),e.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,o])}finally{e.removeEventListener("abort",r)}}export async function executeWithToolApproval(t){if(t.signal?.aborted)return l();const r=structuredClone(t.input),i=await t.call(structuredClone(r)),s=c(i,t.agentTool.name);if(void 0===s)return n(i);if(t.signal?.aborted)return l();const p={...structuredClone(r),...s.retryInput};if(!e(t.agentTool,p).ok)return n(i);const d=structuredClone(r);delete d.scriptApproval,delete d.toolActionApproval;const m=await u(t.ctx.requestApproval({agentName:t.agentName,toolName:t.agentTool.name,input:d,reason:"tool_requested_confirmation",explanation:s.summary??"工具尚未执行操作;仅批准本次完整参数,不自动重放已执行步骤。"}),t.signal);if(t.signal?.aborted||void 0===m)return l();if(!m.approved)return o(a.userRejected,"用户拒绝本次操作;工具未执行操作");if(Date.parse(s.expiresAt)<=Date.now())return n(i);const f=t.ctx.policy?.check({agentName:t.agentName,toolName:t.agentTool.name,input:structuredClone(p),...void 0===t.annotations?{}:{annotations:t.annotations}});return"deny"===f?.action?o(a.policyDenied,"审批期间策略已改变,未执行操作"):t.signal?.aborted?l():n(await t.call(p))}
|
|
@@ -12,11 +12,11 @@ export declare const turnCancellationMetadataV1Schema: z.ZodReadonly<z.ZodObject
|
|
|
12
12
|
reason: z.ZodEnum<["user", "timeout", "runtime"]>;
|
|
13
13
|
}, "strip", z.ZodTypeAny, {
|
|
14
14
|
version: 1;
|
|
15
|
-
reason: "
|
|
15
|
+
reason: "runtime" | "user" | "timeout";
|
|
16
16
|
kind: "turn-cancellation";
|
|
17
17
|
}, {
|
|
18
18
|
version: 1;
|
|
19
|
-
reason: "
|
|
19
|
+
reason: "runtime" | "user" | "timeout";
|
|
20
20
|
kind: "turn-cancellation";
|
|
21
21
|
}>>;
|
|
22
22
|
export declare function createTurnCancellationMessage(content: string, reason: SessionCancellationReason): AssistantModelMessage;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"zod": "^3.25.76"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@roll-agent/core": "0.
|
|
38
|
+
"@roll-agent/core": "0.38.3",
|
|
39
39
|
"@types/node": "^22.0.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|