@roll-agent/runtime 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bash/exec.js CHANGED
@@ -1 +1 @@
1
- import{spawn as e}from"node:child_process";import{existsSync as t}from"node:fs";import{constants as o}from"node:os";import{performance as r}from"node:perf_hooks";import{OutputSink as i}from"./output-buffer.js";import{EXEC_TIMEOUT_EXIT_CODE as n,normalizeExitCode as s}from"./format-result.js";const l=2e3,u=2500,a=1e3,m=130,c={timeout:"timeout",abort:"abort"},d={spawn:e,killTreeDeadlineMs:u,rootKillSettleTimeoutMs:a};function f(e){return e instanceof Error?e.message:String(e)}function T(e,t){const o=new i(0).collect();return{exitCode:1,timedOut:!1,timeoutMs:t,wallTimeMs:0,stdout:o,stderr:o,spawnError:e}}function p(e){const t=new i(0).collect();return{exitCode:m,timedOut:!1,timeoutMs:e,wallTimeMs:0,stdout:t,stderr:t}}export function runBashCommand(e,u=d){const{command:a,workdir:w,timeoutMs:v,maxCaptureBytes:b,profile:M,env:h,abortSignal:E,onDelta:g}=e;return new Promise(e=>{if(E?.aborted)return void e(p(v));if(!t(w))return void e(T(`工作目录不存在: ${w}`,v));let d;try{const e=M.buildSpawn(a,w,h??process.env);d=u.spawn(e.file,e.args,e.options)}catch(t){return void e(T(f(t),v))}const x=r.now(),C=new i(b),$=new i(b);let k,y,S,D,L,O,K=!1,P=!1,j=!1,A=!1,B=!1,I=!1,_=null,F=null,G=void 0!==d.pid,N=!1,R=!1;const q=e=>{O=O?`${O};${e}`:e},z=()=>{if(A||!B||!I)return;if(j&&!0===M.waitForTreeKillAfterRootExit&&!N&&!R)return;const e=null!==F?o.signals[F]:void 0,t=K?n:P?m:s({timedOut:!1,code:_,signalNumber:e});V({exitCode:t,timedOut:K,timeoutMs:v,wallTimeMs:r.now()-x,stdout:C.collect(),stderr:$.collect(),...O?{terminationError:O}:{}})},H=()=>{if(!A&&!B){for(const e of X)e.stream.destroy();d.unref(),V({exitCode:K?n:m,timedOut:K,timeoutMs:v,wallTimeMs:r.now()-x,stdout:C.collect(),stderr:$.collect(),terminationError:`${O??"进程树清理未完成"};根进程在强制终止请求后仍未确认退出`})}},J=e=>{if(!A){if(D&&(clearTimeout(D),D=void 0),!N){N=!0;const t=void 0===e?"进程树清理未完成":f(e);q(`${t};已回退仅向根进程发送强制终止请求,无法确认后代进程是否已清理`)}if(B)z();else if(void 0===L){L=setTimeout(H,u.rootKillSettleTimeoutMs);try{d.kill("SIGKILL")||q("根进程强制终止请求未被接受")}catch(e){q(`根进程强制终止请求失败: ${f(e)}`)}}}},Q=()=>{if(!j){j=!0,S=new AbortController,D=setTimeout(()=>J(new Error("进程树清理超过独立期限")),u.killTreeDeadlineMs);try{M.killTree(d.pid,"terminate",{signal:S.signal}).then(()=>{R=!0,B&&D&&(clearTimeout(D),D=void 0),z()}).catch(J)}catch(e){J(e)}}},U=()=>{void 0===k&&(k=c.abort,P=!0,clearTimeout(Y),Q())},V=t=>{A||(A=!0,clearTimeout(Y),y&&clearTimeout(y),D&&clearTimeout(D),L&&clearTimeout(L),S?.abort(),S=void 0,E?.removeEventListener("abort",U),e(t))},W=(e,t,o)=>{if(null===e)return;const r=new TextDecoder("utf-8");e.on("data",e=>{if(o.append(e),g){const o=r.decode(e,{stream:!0});o.length>0&&g(t,o)}});const i=new Promise(t=>{e.once("close",()=>t())});return{stream:e,closed:i}},X=[W(d.stdout,"stdout",C),W(d.stderr,"stderr",$)].filter(e=>void 0!==e);d.once("spawn",()=>{G=!0}),d.on("error",e=>{if(A)return;if(!j||!G)return void V(T(f(e),v));const t=`根进程终止请求失败: ${f(e)}`;N?q(t):J(new Error(t,{cause:e}))}),d.on("exit",(e,t)=>{A||B||(B=!0,_=e,F=t,clearTimeout(Y),E?.removeEventListener("abort",U),R&&D&&(clearTimeout(D),D=void 0),L&&(clearTimeout(L),L=void 0),y=setTimeout(()=>{for(const e of X)e.stream.destroy()},u.ioDrainTimeoutMs??l),Promise.all(X.map(e=>e.closed)).then(()=>{I=!0,z()}).catch(()=>{}))});const Y=setTimeout(()=>{void 0===k&&(k=c.timeout,K=!0,Q())},v);E&&(E.aborted?U():E.addEventListener("abort",U))})}
1
+ import{spawn as e}from"node:child_process";import{existsSync as t}from"node:fs";import{constants as o}from"node:os";import{performance as r}from"node:perf_hooks";import{OutputSink as i}from"./output-buffer.js";import{BASH_TERMINATION_CAUSES as n,EXEC_TIMEOUT_EXIT_CODE as s,normalizeExitCode as l}from"./format-result.js";import{isTimeoutAbortReason as a}from"../types/cancellation.js";const m=2e3,u=2500,c=1e3,d=130,f={spawn:e,killTreeDeadlineMs:u,rootKillSettleTimeoutMs:c};function p(e){return e instanceof Error?e.message:String(e)}function T(e,t){const o=new i(0).collect();return{exitCode:1,timedOut:!1,timeoutMs:t,wallTimeMs:0,stdout:o,stderr:o,spawnError:e}}function w(e,t){const o=new i(0).collect(),r=a(t);return{exitCode:r?s:d,timedOut:r,timeoutMs:e,wallTimeMs:0,stdout:o,stderr:o,terminationCause:r?n.timeout:n.abort}}export function runBashCommand(e,u=f){const{command:c,workdir:v,timeoutMs:M,maxCaptureBytes:b,profile:h,env:C,abortSignal:E,onDelta:g}=e;return new Promise(e=>{if(E?.aborted)return void e(w(M,E.reason));if(!t(v))return void e(T(`工作目录不存在: ${v}`,M));let f;try{const e=h.buildSpawn(c,v,C??process.env);f=u.spawn(e.file,e.args,e.options)}catch(t){return void e(T(p(t),M))}const x=r.now(),y=new i(b),$=new i(b);let k,S,D,L,O,K,j=!1,P=!1,A=!1,B=!1,I=!1,_=!1,F=null,G=null,N=void 0!==f.pid,R=!1,q=!1;const z=e=>{K=K?`${K};${e}`:e},H=()=>{if(B||!I||!_)return;if(A&&!0===h.waitForTreeKillAfterRootExit&&!R&&!q)return;const e=null!==G?o.signals[G]:void 0,t=j?s:P?d:l({timedOut:!1,code:F,signalNumber:e});W({exitCode:t,timedOut:j,timeoutMs:M,wallTimeMs:r.now()-x,stdout:y.collect(),stderr:$.collect(),...k?{terminationCause:k}:{},...K?{terminationError:K}:{}})},J=()=>{if(!B&&!I){for(const e of Y)e.stream.destroy();f.unref(),W({exitCode:j?s:d,timedOut:j,timeoutMs:M,wallTimeMs:r.now()-x,stdout:y.collect(),stderr:$.collect(),...k?{terminationCause:k}:{},terminationError:`${K??"进程树清理未完成"};根进程在强制终止请求后仍未确认退出`})}},Q=e=>{if(!B){if(L&&(clearTimeout(L),L=void 0),!R){R=!0;const t=void 0===e?"进程树清理未完成":p(e);z(`${t};已回退仅向根进程发送强制终止请求,无法确认后代进程是否已清理`)}if(I)H();else if(void 0===O){O=setTimeout(J,u.rootKillSettleTimeoutMs);try{f.kill("SIGKILL")||z("根进程强制终止请求未被接受")}catch(e){z(`根进程强制终止请求失败: ${p(e)}`)}}}},U=()=>{if(!A){A=!0,D=new AbortController,L=setTimeout(()=>Q(new Error("进程树清理超过独立期限")),u.killTreeDeadlineMs);try{h.killTree(f.pid,"terminate",{signal:D.signal}).then(()=>{q=!0,I&&L&&(clearTimeout(L),L=void 0),H()}).catch(Q)}catch(e){Q(e)}}},V=()=>{if(void 0!==k)return;const e=a(E?.reason);k=e?n.timeout:n.abort,j=e,P=!e,clearTimeout(Z),U()},W=t=>{B||(B=!0,clearTimeout(Z),S&&clearTimeout(S),L&&clearTimeout(L),O&&clearTimeout(O),D?.abort(),D=void 0,E?.removeEventListener("abort",V),e(t))},X=(e,t,o)=>{if(null===e)return;const r=new TextDecoder("utf-8");e.on("data",e=>{if(o.append(e),g){const o=r.decode(e,{stream:!0});o.length>0&&g(t,o)}});const i=new Promise(t=>{e.once("close",()=>t())});return{stream:e,closed:i}},Y=[X(f.stdout,"stdout",y),X(f.stderr,"stderr",$)].filter(e=>void 0!==e);f.once("spawn",()=>{N=!0}),f.on("error",e=>{if(B)return;if(!A||!N)return void W(T(p(e),M));const t=`根进程终止请求失败: ${p(e)}`;R?z(t):Q(new Error(t,{cause:e}))}),f.on("exit",(e,t)=>{B||I||(I=!0,F=e,G=t,clearTimeout(Z),E?.removeEventListener("abort",V),q&&L&&(clearTimeout(L),L=void 0),O&&(clearTimeout(O),O=void 0),S=setTimeout(()=>{for(const e of Y)e.stream.destroy()},u.ioDrainTimeoutMs??m),Promise.all(Y.map(e=>e.closed)).then(()=>{_=!0,H()}).catch(()=>{}))});const Z=setTimeout(()=>{void 0===k&&(k=n.timeout,j=!0,U())},M);E&&(E.aborted?V():E.addEventListener("abort",V))})}
@@ -2,6 +2,11 @@ import type { NormalizedToolResult } from "../tool-bridge/normalize-result.ts";
2
2
  import type { CapturedStream } from "./output-buffer.ts";
3
3
  export declare const EXEC_TIMEOUT_EXIT_CODE = 124;
4
4
  export declare const EXIT_CODE_SIGNAL_BASE = 128;
5
+ export declare const BASH_TERMINATION_CAUSES: {
6
+ readonly timeout: "timeout";
7
+ readonly abort: "abort";
8
+ };
9
+ export type BashTerminationCause = (typeof BASH_TERMINATION_CAUSES)[keyof typeof BASH_TERMINATION_CAUSES];
5
10
  export interface BashExecResult {
6
11
  readonly exitCode: number;
7
12
  readonly timedOut: boolean;
@@ -9,6 +14,7 @@ export interface BashExecResult {
9
14
  readonly wallTimeMs: number;
10
15
  readonly stdout: CapturedStream;
11
16
  readonly stderr: CapturedStream;
17
+ readonly terminationCause?: BashTerminationCause;
12
18
  readonly spawnError?: string;
13
19
  readonly terminationError?: string;
14
20
  }
@@ -1 +1 @@
1
- import{partitionModelBudget as t}from"./output-buffer.js";import{truncateMiddle as r}from"./truncate.js";export const EXEC_TIMEOUT_EXIT_CODE=124;export const EXIT_CODE_SIGNAL_BASE=128;const o=1;export function normalizeExitCode(t){return t.timedOut?124:null!==t.code?t.code:void 0!==t.signalNumber?128+t.signalNumber:1}function e(t,r){if(r.truncated)return`Warning: ${t} 输出已截断(原始 ${String(r.totalBytes)} 字节 / ${String(r.totalLines)} 行)`}function n(t,r){const o=r.length>0?r:void 0;return void 0!==o?`[${t}]\n${o}`:void 0}export function formatBashResult(o){const{result:i,maxModelOutputChars:s}=o;if(void 0!==i.spawnError)return{output:`命令无法启动: ${i.spawnError}`,isError:!0};const u=t(s,Array.from(i.stderr.text).length),d=r(i.stdout.text,u.stdout),a=r(i.stderr.text,u.stderr),m=[];i.timedOut&&m.push(i.terminationError?`命令超时(超过 ${String(i.timeoutMs)}ms),终止状态未确认`:`命令超时(超过 ${String(i.timeoutMs)}ms),已终止`),i.terminationError&&m.push(`终止失败: ${i.terminationError}`),m.push(`Exit code: ${String(i.exitCode)}`),m.push(`Wall time: ${(i.wallTimeMs/1e3).toFixed(1)} s`);const c=[i.stdout.truncated||d.truncated?e("stdout",i.stdout):void 0,i.stderr.truncated||a.truncated?e("stderr",i.stderr):void 0].filter(t=>void 0!==t);m.push(...c);const l=[n("stdout",d.text),n("stderr",a.text)].filter(t=>void 0!==t);return{output:[m.join("\n"),...l].join("\n\n"),isError:0!==i.exitCode||void 0!==i.terminationError}}
1
+ import{partitionModelBudget as t}from"./output-buffer.js";import{truncateMiddle as r}from"./truncate.js";export const EXEC_TIMEOUT_EXIT_CODE=124;export const EXIT_CODE_SIGNAL_BASE=128;export const BASH_TERMINATION_CAUSES={timeout:"timeout",abort:"abort"};const o=1;export function normalizeExitCode(t){return t.timedOut?124:null!==t.code?t.code:void 0!==t.signalNumber?128+t.signalNumber:1}function e(t,r){if(r.truncated)return`Warning: ${t} 输出已截断(原始 ${String(r.totalBytes)} 字节 / ${String(r.totalLines)} 行)`}function n(t,r){const o=r.length>0?r:void 0;return void 0!==o?`[${t}]\n${o}`:void 0}export function formatBashResult(o){const{result:i,maxModelOutputChars:s}=o;if(void 0!==i.spawnError)return{output:`命令无法启动: ${i.spawnError}`,isError:!0};const u=t(s,Array.from(i.stderr.text).length),d=r(i.stdout.text,u.stdout),a=r(i.stderr.text,u.stderr),m=[];i.terminationCause===BASH_TERMINATION_CAUSES.abort&&m.push("命令已中断(收到取消请求),不能视为正常完成"),i.timedOut&&m.push(i.terminationError?`命令超时(超过 ${String(i.timeoutMs)}ms),终止状态未确认`:`命令超时(超过 ${String(i.timeoutMs)}ms),已终止`),i.terminationError&&m.push(`终止失败: ${i.terminationError}`),m.push(`Exit code: ${String(i.exitCode)}`),m.push(`Wall time: ${(i.wallTimeMs/1e3).toFixed(1)} s`);const c=[i.stdout.truncated||d.truncated?e("stdout",i.stdout):void 0,i.stderr.truncated||a.truncated?e("stderr",i.stderr):void 0].filter(t=>void 0!==t);m.push(...c);const E=[n("stdout",d.text),n("stderr",a.text)].filter(t=>void 0!==t);return{output:[m.join("\n"),...E].join("\n\n"),isError:0!==i.exitCode||void 0!==i.terminationError}}
@@ -10,6 +10,7 @@ export interface SessionManagerOptions {
10
10
  readonly env: NodeJS.ProcessEnv;
11
11
  readonly bufferCapacity: number;
12
12
  readonly generateId?: () => number;
13
+ readonly interruptGraceMs?: number;
13
14
  }
14
15
  export interface SpawnRequest {
15
16
  readonly command: string;
@@ -24,7 +25,9 @@ export declare class SessionManager {
24
25
  get(id: number): ManagedSession | undefined;
25
26
  delete(id: number): void;
26
27
  size(): number;
28
+ interruptAll(): void;
27
29
  terminateAll(): void;
28
30
  private evictExited;
31
+ private interruptThenTerminate;
29
32
  private allocateId;
30
33
  }
@@ -1 +1 @@
1
- import{spawnSession as s}from"./session-exec.js";export class SessionCapError extends Error{constructor(s){super(`会话数已达上限 ${String(s)},且无空闲会话可回收`),this.name="SessionCapError"}}function e(){return 1e3+Math.floor(99e3*Math.random())}export class SessionManager{sessions=new Map;options;constructor(s){this.options=s}spawn(e){if(this.evictExited(),this.sessions.size>=this.options.maxSessions)throw new SessionCapError(this.options.maxSessions);const o=this.allocateId(),t=s({id:o,command:e.command,workdir:e.workdir,profile:this.options.profile,env:this.options.env,bufferCapacity:this.options.bufferCapacity,...e.onDelta?{onDelta:e.onDelta}:{}});return this.sessions.set(o,t),t}get(s){return this.sessions.get(s)}delete(s){this.sessions.delete(s)}size(){return this.sessions.size}terminateAll(){for(const s of this.sessions.values())s.profile.killTree(s.child.pid,"terminate").catch(()=>{});this.sessions.clear()}evictExited(){for(const[s,e]of this.sessions)void 0!==e.exitCode&&this.sessions.delete(s)}allocateId(){const s=this.options.generateId??e;for(let e=0;e<1e3;e+=1){const e=s();if(!this.sessions.has(e))return e}throw new Error("无法分配唯一会话 id")}}
1
+ import{spawnSession as s}from"./session-exec.js";const e=150;export class SessionCapError extends Error{constructor(s){super(`会话数已达上限 ${String(s)},且无空闲会话可回收`),this.name="SessionCapError"}}function t(){return 1e3+Math.floor(99e3*Math.random())}export class SessionManager{sessions=new Map;options;constructor(s){this.options=s}spawn(e){if(this.evictExited(),this.sessions.size>=this.options.maxSessions)throw new SessionCapError(this.options.maxSessions);const t=this.allocateId(),i=s({id:t,command:e.command,workdir:e.workdir,profile:this.options.profile,env:this.options.env,bufferCapacity:this.options.bufferCapacity,...e.onDelta?{onDelta:e.onDelta}:{}});return this.sessions.set(t,i),i}get(s){return this.sessions.get(s)}delete(s){this.sessions.delete(s)}size(){return this.sessions.size}interruptAll(){const s=[...this.sessions.values()];this.sessions.clear();for(const e of s)this.interruptThenTerminate(e).catch(()=>{})}terminateAll(){for(const s of this.sessions.values())s.profile.killTree(s.child.pid,"terminate").catch(()=>{});this.sessions.clear()}evictExited(){for(const[s,e]of this.sessions)void 0!==e.exitCode&&this.sessions.delete(s)}async interruptThenTerminate(s){if(s.profile.killTree(s.child.pid,"interrupt").catch(()=>{}),void 0!==s.exitCode)return;await i(s,this.options.interruptGraceMs??e)||void 0!==s.exitCode||await s.profile.killTree(s.child.pid,"terminate").catch(()=>{})}allocateId(){const s=this.options.generateId??t;for(let e=0;e<1e3;e+=1){const e=s();if(!this.sessions.has(e))return e}throw new Error("无法分配唯一会话 id")}}function i(s,e){return new Promise(t=>{const i=setTimeout(()=>t(!1),e);i.unref(),s.waitExit().then(()=>{clearTimeout(i),t(!0)})})}
@@ -28,6 +28,8 @@ export interface AgentSessionOptions {
28
28
  readonly compaction?: SessionCompactionSettings;
29
29
  readonly turnTimeoutMs?: number;
30
30
  readonly providerOptions?: SharedV4ProviderOptions;
31
+ /** `setProviderOptions()` 生效后触发;ConversationEngine 用它同步子 Agent Sampling。 */
32
+ readonly onProviderOptionsChange?: (providerOptions: SharedV4ProviderOptions | undefined) => void;
31
33
  readonly debugEvents?: boolean;
32
34
  readonly systemPrompt?: string;
33
35
  readonly skillLibrary?: SkillLibrary;
@@ -69,6 +71,7 @@ export declare class AgentSession {
69
71
  private readonly compaction;
70
72
  private readonly turnTimeoutMs;
71
73
  private providerOptions;
74
+ private readonly onProviderOptionsChange;
72
75
  private readonly debugEvents;
73
76
  private readonly policy;
74
77
  private systemPrompt;
@@ -104,6 +107,10 @@ export declare class AgentSession {
104
107
  private runCompaction;
105
108
  private recoverFromContextError;
106
109
  private isTurnAborted;
110
+ private cancellationMessage;
111
+ private emitCancellation;
112
+ private persistCancelledTurn;
113
+ cancel(): boolean;
107
114
  abort(): void;
108
115
  private debug;
109
116
  private scheduleDebug;
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{buildAgentInstallToolset as i}from"../tool-bridge/agent-install-tool.js";import{buildSkillToolset as r}from"../tool-bridge/skill-tool.js";import{buildBashToolset as n}from"../tool-bridge/bash-tool.js";import{buildSessionExecToolset as a}from"../tool-bridge/session-exec-tool.js";import{SessionManager as l}from"../bash/session/session-manager.js";import{withCleanEnv as u}from"../bash/clean-env.js";import{ToolRegistry as p}from"../tool-bridge/naming.js";import{buildChatSystemPrompt as h}from"./system-prompt.js";import{readIsError as c}from"../tool-bridge/normalize-result.js";import{ApprovalGate as m}from"../approval/approval-gate.js";import{compactMessages as g}from"./compactor.js";import{AsyncEventQueue as d}from"./event-queue.js";function b(e){return e instanceof Error?e.message:String(e)}function v(e){return"object"==typeof e&&null!==e&&"output"in e}function k(e){return v(e)?b(e.output):b(e)}function T(e){const t=k(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function y(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(b(e))}function f(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.inputTokenDetails?.cacheWriteTokens,o=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{cacheWriteTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function x(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function w(e,t){const s=x(e.cachedInputTokens,t.cachedInputTokens),o=x(e.cacheWriteTokens,t.cacheWriteTokens),i=x(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{cacheWriteTokens:o}:{},...void 0!==i?{reasoningTokens:i}:{}}}function C(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function S(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function N(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;debugEvents;policy;systemPrompt;skillSummaries;skillLibrary;skillToolBuilt=!1;toolSourceAgentNames;gate=new m;tools;registry;sessionManager;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?N(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.debugEvents=e.debugEvents??!1,this.policy=e.policy,this.systemPrompt=e.systemPrompt??h(),this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary?.list()??[],this.toolSourceAgentNames=new Set(e.sources.map(e=>e.agentName));const t=new p,s=e.skillLibrary?this.buildSkillTools(t):{},r={...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e),emitEvent:e=>this.emit?.(e)},c=e.bashClassifier?{classifier:e.bashClassifier}:{},m=e.bash?n(e.bash,t,r,c):{};e.bashSession&&(this.sessionManager=new l({maxSessions:e.bashSession.maxSessions,profile:e.bashSession.profile,env:u(process.env),bufferCapacity:e.bashSession.bufferCapacity}));const g=e.bashSession&&this.sessionManager?a({workdir:e.bashSession.workdir,defaultYieldMs:e.bashSession.defaultYieldMs,maxOutputTokens:e.bashSession.maxOutputTokens},this.sessionManager,t,r,c):{},d=e.agentInstall,b=d?i({catalog:d.catalog,install:async(e,t)=>{const s=await d.install(e,t);return s.outcome.ok&&s.refresh?(this.applyAgentRefresh(s.refresh),{...s.outcome,refreshApplied:!0}):s.outcome}},t,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)}):{},v=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)},t);this.tools={...s,...m,...g,...b,...v.tools},this.registry=v.registry}buildSkillTools(e){return this.skillToolBuilt=!0,r(()=>{if(!this.skillLibrary)throw new Error("skill library 不可用");return this.skillLibrary},e)}applyAgentRefresh(e){if(!this.toolSourceAgentNames.has(e.source.agentName)){const t=o([e.source],{...this.policy?{policy:this.policy}:{},requestApproval:e=>this.requestApproval(e)},this.registry);this.tools={...this.tools,...t.tools},this.toolSourceAgentNames.add(e.source.agentName)}e.skillLibrary&&(this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary.list(),this.skillToolBuilt||(this.tools={...this.tools,...this.buildSkillTools(this.registry)})),this.systemPrompt=e.systemPrompt}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new d;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:b(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new d;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:b(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,r){const n=Date.now();let a;try{if(this.debug(o,"turn","start",n,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",n,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:b(e)})}}if(i.aborted||i.abortController.signal.aborted)return void o.push({type:"error",stage:"execute",message:"aborted"});a=this.messages.length,this.messages.push({role:"user",content:r}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",n,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}});const l=s({model:this.model,system:this.systemPrompt,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==this.turnTimeoutMs?{timeout:{totalMs:this.turnTimeoutMs}}:{}});this.debug(o,"model","streamText returned",n);let u,p,h,m,g,d,v="",x="",A=!1,I=0,M=!1;const W=this.scheduleDebug(o,"model","waiting for first stream event",n,{messages:this.messages.length});try{for await(const e of l.fullStream){switch(M||(M=!0,this.clearDebugTimer(W),this.debug(o,"model","first stream event",n,{part:e.type})),e.type){case"text-delta":if(!A){const t=x+e.text;if(S(t,r)){x=t;break}if(x.length>0){const t=x+e.text;x="",v+=t,o.push({type:"text-delta",delta:t});break}}v+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{A=!0,x="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=c(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(d=T(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=k(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(d=T(e.error));break}case"finish-step":{const t=f(e.usage);p=C(p,t.inputTokens),I+=1,m=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(h=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!A&&x.length>0&&(v+=x,o.push({type:"text-delta",delta:x}),x=""),u=f(e.totalUsage);break;case"error":y(e.error)&&(this.needsCompaction=!0),g=b(e.error),o.push({type:"error",stage:"execute",message:g});break;case"abort":i.aborted=!0}if(void 0!==d)break}}finally{this.clearDebugTimer(W)}if(this.debug(o,"model","fullStream finished",n,{textChars:v.length}),void 0!==g)return this.messages.splice(a),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return this.messages.splice(a),void o.push({type:"error",stage:"execute",message:"aborted"});if(void 0!==d){const e=d,t=0===v.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",n,{appendedMessages:this.messages.length-a}),this.onPersist?.(this.messages.slice(a)),this.debug(o,"persist","messages persisted",n,{totalMessages:this.messages.length}),u&&(this.sessionUsage=w(this.sessionUsage,u));const s=p??u?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...u?{totalUsage:u}:{},sessionUsage:{...this.sessionUsage},...void 0!==p?{contextInputTokens:p}:{},...void 0!==h?{outputTokensPerSecond:h}:{}})}let R;this.debug(o,"model","awaiting response messages",n);const j=this.scheduleDebug(o,"model","still awaiting response messages",n);try{R=(await l.response).messages}catch(e){return this.clearDebugTimer(j),y(e)&&(this.needsCompaction=!0),this.messages.splice(a),o.push({type:"error",stage:"execute",message:b(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i))}finally{this.clearDebugTimer(j)}this.debug(o,"model","response messages ready",n,{responseMessages:R.length});const E=N(R);this.messages.push(...E),this.debug(o,"persist","persisting messages",n,{appendedMessages:this.messages.length-a}),this.onPersist?.(this.messages.slice(a)),this.debug(o,"persist","messages persisted",n,{totalMessages:this.messages.length}),u&&(this.sessionUsage=w(this.sessionUsage,u));const P=p??u?.inputTokens;void 0!==P&&(this.lastInputTokens=P);const D=I>=this.maxSteps&&"tool-calls"===m;o.push({type:"message-finish",text:v,...u?{totalUsage:u}:{},sessionUsage:{...this.sessionUsage},...void 0!==p?{contextInputTokens:p}:{},...void 0!==h?{outputTokensPerSecond:h}:{},...D?{stoppedAtStepLimit:!0}:{}})}catch(e){void 0!==a&&this.messages.splice(a),y(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",n,{message:b(e)}),o.push({type:"error",stage:"execute",message:b(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}getSkillSummaries(){return this.skillSummaries}setProviderOptions(e){this.providerOptions=e}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:b(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),i=this.compaction,r=i?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if(!i)return void e.push({type:"context-compacted",reason:t,strategy:r,removed:0,kept:this.messages.length});const n=this.lastInputTokens;let a=i.strategy;const l=s?.abortController.signal;let u;try{u=await g({messages:this.messages,strategy:a,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}catch(t){if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if("summarize"!==a)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:b(t)}),a="truncate",u=await g({messages:this.messages,strategy:a,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});const p=u.removed>0||u.truncatedTools>0;p&&(this.onReplace?.(u.messages),this.messages.splice(0,this.messages.length,...u.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:a,removed:u.removed,kept:u.kept,...u.truncatedTools>0?{truncatedTools:u.truncatedTools}:{},...void 0!==n?{beforeInputTokens:n}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:a,removed:u.removed,kept:u.kept,truncatedTools:u.truncatedTools,progressed:p})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:b(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}abort(){this.activeTurn&&(this.activeTurn.aborted=!0),this.gate.abortAll(),this.activeTurn?.abortController.abort(),this.sessionManager?.terminateAll()}debug(e,t,s,o,i){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==i?{data:i}:{}})}scheduleDebug(e,t,s,o,i){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,i)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}
1
+ import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{buildAgentInstallToolset as i}from"../tool-bridge/agent-install-tool.js";import{buildSkillToolset as n}from"../tool-bridge/skill-tool.js";import{buildBashToolset as a}from"../tool-bridge/bash-tool.js";import{buildSessionExecToolset as r}from"../tool-bridge/session-exec-tool.js";import{SessionManager as l}from"../bash/session/session-manager.js";import{withCleanEnv as c}from"../bash/clean-env.js";import{RUNTIME_CANCELLATION_ABORT_REASON as u,SESSION_CANCELLATION_REASONS as h,TURN_TIMEOUT_ABORT_REASON as p,USER_CANCELLATION_ABORT_REASON as m,isTurnTimeoutAbortReason as d}from"../types/cancellation.js";import{ToolRegistry as g}from"../tool-bridge/naming.js";import{buildChatSystemPrompt as b}from"./system-prompt.js";import{readIsError as v}from"../tool-bridge/normalize-result.js";import{ApprovalGate as T}from"../approval/approval-gate.js";import{compactMessages as k}from"./compactor.js";import{AsyncEventQueue as y}from"./event-queue.js";function f(e){return e instanceof Error?e.message:String(e)}function C(e){return"object"==typeof e&&null!==e&&"output"in e}function x(e){return C(e)?f(e.output):f(e)}function w(e){const t=x(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function S(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(f(e))}function A(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.inputTokenDetails?.cacheWriteTokens,o=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{cacheWriteTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function M(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function R(e,t){const s=M(e.cachedInputTokens,t.cachedInputTokens),o=M(e.cacheWriteTokens,t.cacheWriteTokens),i=M(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{cacheWriteTokens:o}:{},...void 0!==i?{reasoningTokens:i}:{}}}function N(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function I(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function E(e,t){return void 0!==e?e:d(t)?h.timeout:h.runtime}function P(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;onProviderOptionsChange;debugEvents;policy;systemPrompt;skillSummaries;skillLibrary;skillToolBuilt=!1;toolSourceAgentNames;gate=new T;tools;registry;sessionManager;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?P(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.onProviderOptionsChange=e.onProviderOptionsChange,this.debugEvents=e.debugEvents??!1,this.policy=e.policy,this.systemPrompt=e.systemPrompt??b(),this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary?.list()??[],this.toolSourceAgentNames=new Set(e.sources.map(e=>e.agentName));const t=new g,s=e.skillLibrary?this.buildSkillTools(t):{},n={...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e),emitEvent:e=>this.emit?.(e)},u=e.bashClassifier?{classifier:e.bashClassifier}:{},h=e.bash?a(e.bash,t,n,u):{};e.bashSession&&(this.sessionManager=new l({maxSessions:e.bashSession.maxSessions,profile:e.bashSession.profile,env:c(process.env),bufferCapacity:e.bashSession.bufferCapacity}));const p=e.bashSession&&this.sessionManager?r({workdir:e.bashSession.workdir,defaultYieldMs:e.bashSession.defaultYieldMs,maxOutputTokens:e.bashSession.maxOutputTokens},this.sessionManager,t,n,u):{},m=e.agentInstall,d=m?i({catalog:m.catalog,install:async(e,t)=>{const s=await m.install(e,t);return s.outcome.ok&&s.refresh?(this.applyAgentRefresh(s.refresh),{...s.outcome,refreshApplied:!0}):s.outcome}},t,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)}):{},v=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)},t);this.tools={...s,...h,...p,...d,...v.tools},this.registry=v.registry}buildSkillTools(e){return this.skillToolBuilt=!0,n(()=>{if(!this.skillLibrary)throw new Error("skill library 不可用");return this.skillLibrary},e)}applyAgentRefresh(e){if(!this.toolSourceAgentNames.has(e.source.agentName)){const t=o([e.source],{...this.policy?{policy:this.policy}:{},requestApproval:e=>this.requestApproval(e)},this.registry);this.tools={...this.tools,...t.tools},this.toolSourceAgentNames.add(e.source.agentName)}e.skillLibrary&&(this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary.list(),this.skillToolBuilt||(this.tools={...this.tools,...this.buildSkillTools(this.registry)})),this.systemPrompt=e.systemPrompt}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new y;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1,cancellationEventEmitted:!1,cancellationPersisted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:f(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new y;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1,cancellationEventEmitted:!1,cancellationPersisted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:f(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,n){const a=Date.now();let r,l;try{if(this.debug(o,"turn","start",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",a,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:f(e)})}}if(i.aborted||i.abortController.signal.aborted)return r=this.messages.length,this.messages.push({role:"user",content:n}),void this.persistCancelledTurn(o,i,r,[],a);r=this.messages.length,this.messages.push({role:"user",content:n}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}}),void 0!==this.turnTimeoutMs&&(l=setTimeout(()=>{i.abortController.signal.aborted||(i.aborted=!0,i.cancellationReason=h.timeout,this.gate.abortAll("本轮运行超时"),i.abortController.abort(p))},this.turnTimeoutMs));let c=[];const u=s({model:this.model,system:this.systemPrompt,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},onAbort:({steps:e})=>{c=e.flatMap(e=>e.response.messages)}});this.debug(o,"model","streamText returned",a);let m,g,b,T,k,y,C="",M="",W=!1,j=0,O=!1;const D=this.scheduleDebug(o,"model","waiting for first stream event",a,{messages:this.messages.length});try{for await(const e of u.fullStream){switch(O||(O=!0,this.clearDebugTimer(D),this.debug(o,"model","first stream event",a,{part:e.type})),e.type){case"text-delta":if(!W){const t=M+e.text;if(I(t,n)){M=t;break}if(M.length>0){const t=M+e.text;M="",C+=t,o.push({type:"text-delta",delta:t});break}}C+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{W=!0,M="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=v(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(y=w(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=x(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(y=w(e.error));break}case"finish-step":{const t=A(e.usage);g=N(g,t.inputTokens),j+=1,T=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(b=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!W&&M.length>0&&(C+=M,o.push({type:"text-delta",delta:M}),M=""),m=A(e.totalUsage);break;case"error":if(this.isTurnAborted(i)||d(e.error)){i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e.error);break}S(e.error)&&(this.needsCompaction=!0),k=f(e.error),o.push({type:"error",stage:"execute",message:k});break;case"abort":i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e.reason)}if(void 0!==y)break}}finally{this.clearDebugTimer(D)}if(this.debug(o,"model","fullStream finished",a,{textChars:C.length}),void 0!==k)return this.messages.splice(r),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return i.cancellationReason=E(i.cancellationReason,i.abortController.signal.reason),void this.persistCancelledTurn(o,i,r,c,a);if(void 0!==y){const e=y,t=0===C.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-r}),this.onPersist?.(this.messages.slice(r)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),m&&(this.sessionUsage=R(this.sessionUsage,m));const s=g??m?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...m?{totalUsage:m}:{},sessionUsage:{...this.sessionUsage},...void 0!==g?{contextInputTokens:g}:{},...void 0!==b?{outputTokensPerSecond:b}:{}})}let U;this.debug(o,"model","awaiting response messages",a);const q=this.scheduleDebug(o,"model","still awaiting response messages",a);try{U=(await u.response).messages}catch(e){return this.clearDebugTimer(q),this.isTurnAborted(i)||d(e)?(i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e),void this.persistCancelledTurn(o,i,r,c,a)):(S(e)&&(this.needsCompaction=!0),this.messages.splice(r),o.push({type:"error",stage:"execute",message:f(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i)))}finally{this.clearDebugTimer(q)}this.debug(o,"model","response messages ready",a,{responseMessages:U.length});const L=P(U);this.messages.push(...L),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-r}),this.onPersist?.(this.messages.slice(r)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),m&&(this.sessionUsage=R(this.sessionUsage,m));const F=g??m?.inputTokens;void 0!==F&&(this.lastInputTokens=F);const z=j>=this.maxSteps&&"tool-calls"===T;o.push({type:"message-finish",text:C,...m?{totalUsage:m}:{},sessionUsage:{...this.sessionUsage},...void 0!==g?{contextInputTokens:g}:{},...void 0!==b?{outputTokensPerSecond:b}:{},...z?{stoppedAtStepLimit:!0}:{}})}catch(e){if(this.isTurnAborted(i)||d(e))return i.aborted=!0,i.cancellationReason=E(i.cancellationReason,i.abortController.signal.reason??e),void(void 0!==r?this.persistCancelledTurn(o,i,r,[],a):this.emitCancellation(o,i));void 0!==r&&this.messages.splice(r),S(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",a,{message:f(e)}),o.push({type:"error",stage:"execute",message:f(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{void 0!==l&&clearTimeout(l),this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}getSkillSummaries(){return this.skillSummaries}setProviderOptions(e){this.providerOptions=e,this.onProviderOptionsChange?.(e)}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:f(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),i=this.compaction,n=i?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));if(!i)return void e.push({type:"context-compacted",reason:t,strategy:n,removed:0,kept:this.messages.length});const a=this.lastInputTokens;let r=i.strategy;const l=s?.abortController.signal;let c;try{c=await k({messages:this.messages,strategy:r,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}catch(t){if(this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));if("summarize"!==r)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:f(t)}),r="truncate",c=await k({messages:this.messages,strategy:r,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}if(this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));const u=c.removed>0||c.truncatedTools>0;u&&(this.onReplace?.(c.messages),this.messages.splice(0,this.messages.length,...c.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:r,removed:c.removed,kept:c.kept,...c.truncatedTools>0?{truncatedTools:c.truncatedTools}:{},...void 0!==a?{beforeInputTokens:a}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:r,removed:c.removed,kept:c.kept,truncatedTools:c.truncatedTools,progressed:u})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:f(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}cancellationMessage(e){switch(e.cancellationReason??h.runtime){case h.user:return"已取消本轮;正在运行的模型或工具已收到中断请求,已发生的外部副作用不会自动回滚。";case h.timeout:return`本轮因运行超时${void 0!==this.turnTimeoutMs?`(${String(this.turnTimeoutMs)}ms)`:""}而中断;未返回成功 tool result 或 Exit code: 0 的操作不能视为正常完成。`;case h.runtime:return"本轮被运行时中断;未返回成功 tool result 或 Exit code: 0 的操作状态未知。"}}emitCancellation(e,t){t.cancellationEventEmitted||(t.cancellationReason??=h.runtime,t.cancellationEventEmitted=!0,e.push({type:"turn-cancelled",reason:t.cancellationReason,message:this.cancellationMessage(t)}))}persistCancelledTurn(e,t,s,o,i){t.cancellationPersisted||(this.messages.splice(s+1),this.messages.push(...P(o)),this.messages.push({role:"assistant",content:this.cancellationMessage(t)}),t.cancellationPersisted=!0,this.debug(e,"persist","persisting cancelled turn",i,{appendedMessages:this.messages.length-s}),this.onPersist?.(this.messages.slice(s))),this.emitCancellation(e,t)}cancel(){return!!this.activeTurn&&(this.activeTurn.aborted=!0,this.activeTurn.cancellationReason=h.user,this.gate.abortAll("用户取消本轮"),this.sessionManager?.interruptAll(),this.activeTurn.abortController.abort(m),!0)}abort(){this.activeTurn&&(this.activeTurn.aborted=!0,this.activeTurn.cancellationReason??=h.runtime,this.gate.abortAll(),this.activeTurn.abortController.abort(u)),this.sessionManager?.terminateAll()}debug(e,t,s,o,i){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==i?{data:i}:{}})}scheduleDebug(e,t,s,o,i){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,i)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}
@@ -50,7 +50,7 @@ export declare class ConversationEngine {
50
50
  private readonly store;
51
51
  private readonly policy;
52
52
  private readonly maxSteps;
53
- private readonly providerOptions;
53
+ private providerOptions;
54
54
  private readonly ensureAgentReady;
55
55
  private readonly debugEvents;
56
56
  private readonly explicitAgents;
@@ -77,6 +77,7 @@ export declare class ConversationEngine {
77
77
  private resolveShellSettings;
78
78
  private resolveSessionExecSettings;
79
79
  private buildSession;
80
+ private syncProviderOptions;
80
81
  private ensureReady;
81
82
  private bootstrap;
82
83
  private connectAgentSource;
@@ -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}from"@roll-agent/core/llm/providers";import{AgentStore as i}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as o}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as r,startAgent as n,waitForAgentReady as l}from"@roll-agent/core/registry/process-manager";import{normalizeListedTools as a}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as c}from"@roll-agent/core/config/helpers";import{catalogPackageSpec as h,getAgentCatalog as m}from"@roll-agent/core/registry/catalog";import{resolveAgentCatalog as g}from"@roll-agent/core/registry/catalog-discovery";import{installAgent as p}from"@roll-agent/core/registry/install";import{createSkillLibrary as u}from"@roll-agent/core/skills/library";import{AGENT_INSTALL_TOOL_ID as d}from"../tool-bridge/agent-install-tool.js";import{AgentSession as f}from"./agent-session.js";import{resolveContextWindow as y}from"./context-window.js";import{buildChatSystemPrompt as v}from"./system-prompt.js";import{BASH_TOOL_ID as S,POWERSHELL_TOOL_ID as b}from"../tool-bridge/bash-tool.js";import{EXEC_COMMAND_ID as x,EXEC_POLL_ID as w}from"../tool-bridge/session-exec-tool.js";import{unknownCommandClassifier as k}from"../types/command-classification.js";import{resolveShellProfile as C}from"../bash/profile.js";const A=80;function M(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 P(e){return"retry"===e.type?`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`:"warn"===e.type?`警告:${e.message}`:e.message}async function E(e,t,s){"core-managed"===e.runtime.ownership&&(void 0===r(t,e.skill.name)&&n(e,t,s),await l(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;onAgentBootstrapIssue;sessionExecEnabled;explicitShellProfile;resolveShellProfileFn;installAgentFn;resolveCatalogFn;ready;refreshChain=Promise.resolve();resolvedCatalog;shellProfileResolution;shellUnsupportedWarned=!1;shellSessionUnsupportedWarned=!1;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.policy=e.policy,this.maxSteps=e.maxSteps??A,this.providerOptions=e.providerOptions,this.ensureAgentReady=e.ensureAgentReady??((e,t)=>E(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.sessionExecEnabled=e.sessionExecEnabled??!0,this.explicitShellProfile=e.shellProfile,this.resolveShellProfileFn=e.resolveShellProfileFn??C,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue,this.installAgentFn=e.installAgentFn??p,this.resolveCatalogFn=e.resolveCatalogFn??g}async createSession(t={}){const s=await this.ensureReady(),i=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(s,i,[])}async resumeSession(e){if(!this.store)throw new Error("resumeSession requires a ThreadStore");if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=await this.ensureReady();return this.buildSession(t,e,this.store.getMessages(e))}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:process.env}));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){const t=this.config.runtime.shell;return{workdir:process.cwd(),defaultTimeoutMs:t.defaultTimeoutMs,maxTimeoutMs:t.maxTimeoutMs,turnTimeoutMs:this.config.runtime.turnTimeoutMs,maxCaptureBytes:t.maxCaptureBytes,maxModelOutputChars:t.maxModelOutputChars,profile:e}}resolveSessionExecSettings(e){if(!this.sessionExecEnabled)return;const t=this.config.runtime.shell;if(t.enabled&&t.session.enabled){if(e.supportsSessionExec)return{workdir:process.cwd(),profile:e,maxSessions:t.session.maxSessions,defaultYieldMs:t.session.defaultYieldMs,maxOutputTokens:t.session.maxOutputTokens,bufferCapacity:t.maxCaptureBytes};this.shellSessionUnsupportedWarned||(this.shellSessionUnsupportedWarned=!0,process.stderr.write("roll chat: Windows 原生 session exec 暂未支持,已跳过 roll__exec_command / roll__exec_poll\n"))}}buildSession(e,t,s){const i=this.store,o=y(this.resolveModelName(),this.config.runtime.contextWindow),r=e.skillLibrary?.list()??[],n=r.length>0?e.skillLibrary:void 0,l=this.resolveRuntimeShellProfile(),a=l?this.resolveShellSettings(l):void 0,c=l?this.resolveSessionExecSettings(l):void 0,h=l?l.supportsSafeCommandClassification&&this.config.runtime.shell.autoApproveSafe?l:k:void 0,m=this.composeSystemPrompt(r,e.sources.length,l,c),g=this.resolveAgentInstallBinding();return new f({id:t,model:e.model,sources:e.sources,systemPrompt:m,...n?{skillLibrary:n}:{},...a?{bash:a}:{},...h?{bashClassifier:h}:{},...c?{bashSession:c}:{},...g?{agentInstall:g}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==o?{contextWindow:o}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...i?{onPersist:e=>i.appendMessages(t,e),onReplace:e=>i.replaceMessages(t,e)}:{}})}ensureReady(){return this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:e,sources:this.explicitSources,...this.explicitSkillLibrary?{skillLibrary:this.explicitSkillLibrary}:{}};const t=this.explicitAgents??new i(this.config.agents.dataDir).list();this.agentInstallEnabled()&&(this.resolvedCatalog=await this.resolveCatalogFn(this.config,{allowNetwork:!1,...this.config.install.registry?{registry:this.config.install.registry}:{}}));const s=[];for(const i of t)try{s.push(await this.connectAgentSource(i,e))}catch(e){this.onAgentBootstrapIssue?.({agentName:i.skill.name,message:e instanceof Error?e.message:String(e)})}const o=this.resolveSkillLibrary(t);return{model:e,sources:s,...o?{skillLibrary:o}:{}}}async connectAgentSource(e,t){const s=o(e),i=c(this.config,e.skill.name);await this.ensureAgentReady(e,i);const r=await this.clientManager.connect(e.skill.name,s,e.installPath,{samplingModel:t,...i?{env:i}:{}}),n=(await r.listTools()).tools,l=a(n).map((e,t)=>({tool:e,annotations:M(n[t])}));return{agentName:e.skill.name,client:r,tools:l}}async prepareAgentRefresh(e){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,t.model),o=[...t.sources.filter(e=>e.agentName!==s.agentName),s],r=this.explicitAgents??new i(this.config.agents.dataDir).list(),n=this.resolveSkillLibrary(r);this.ready=Promise.resolve({model:t.model,sources:o,...n?{skillLibrary:n}:{}});const l=n?.list()??[],a=n&&l.length>0?n:void 0,c=this.resolveRuntimeShellProfile(),h=c?this.resolveSessionExecSettings(c):void 0;return{source:s,...a?{skillLibrary:a}:{},systemPrompt:this.composeSystemPrompt(l,o.length,c,h)}}composeSystemPrompt(e,t,s,i){const o=this.resolveAgentOnboardingInfo();return v({skills:e,...s?{shellToolId:"powershell"===s.toolName?b:S,shellHints:s.systemPromptHints()}:{},...i?{sessionExecToolIds:{command:x,poll:w}}:{},agentCount:t,...o?{agentOnboarding:o}:{}})}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?{installToolId:d,catalog: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:h(s),skipBrowserSetup:!0,autoStart:!0,expectedSkillName:s.skillName},{agentsConfig:this.config.agents,installConfig:this.config.install,getStartEnv:e=>c(this.config,e),report:e=>t(P(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,l={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:l};try{return{outcome:l,refresh:await this.prepareAgentRefresh(o)}}catch(e){return t(`接入新 Agent 失败:${e instanceof Error?e.message:String(e)}`),{outcome:l}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:u({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.config.runtime.provider??this.config.llm.defaultProvider}resolveModelName(){return this.config.runtime.model??this.config.llm.defaultModel}async getContextSummary(){const e=await this.ensureReady();return{agentCount:e.sources.length,toolCount:e.sources.reduce((e,t)=>e+t.tools.length,0),skillCount:e.skillLibrary?.list().length??0}}async dispose(){await this.clientManager.disconnectAll()}}
1
+ import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as s}from"@roll-agent/core/llm/providers";import{AgentStore as i}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as o}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as r,startAgent as n,waitForAgentReady as l}from"@roll-agent/core/registry/process-manager";import{normalizeListedTools as a}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as c}from"@roll-agent/core/config/helpers";import{catalogPackageSpec as h,getAgentCatalog as p}from"@roll-agent/core/registry/catalog";import{resolveAgentCatalog as m}from"@roll-agent/core/registry/catalog-discovery";import{installAgent as d}from"@roll-agent/core/registry/install";import{createSkillLibrary as g}from"@roll-agent/core/skills/library";import{AGENT_INSTALL_TOOL_ID as u}from"../tool-bridge/agent-install-tool.js";import{AgentSession as f}from"./agent-session.js";import{resolveContextWindow as y}from"./context-window.js";import{buildChatSystemPrompt as v}from"./system-prompt.js";import{BASH_TOOL_ID as S,POWERSHELL_TOOL_ID as b}from"../tool-bridge/bash-tool.js";import{EXEC_COMMAND_ID as x,EXEC_POLL_ID as w}from"../tool-bridge/session-exec-tool.js";import{unknownCommandClassifier as k}from"../types/command-classification.js";import{resolveShellProfile as C}from"../bash/profile.js";const M=80;function A(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 P(e){return"retry"===e.type?`安装遇到网络问题,${Math.round(e.delayMs/1e3)}s 后重试(第 ${e.attempt+1} 次)...`:"warn"===e.type?`警告:${e.message}`:e.message}async function E(e,t,s){"core-managed"===e.runtime.ownership&&(void 0===r(t,e.skill.name)&&n(e,t,s),await l(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;onAgentBootstrapIssue;sessionExecEnabled;explicitShellProfile;resolveShellProfileFn;installAgentFn;resolveCatalogFn;ready;refreshChain=Promise.resolve();resolvedCatalog;shellProfileResolution;shellUnsupportedWarned=!1;shellSessionUnsupportedWarned=!1;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.policy=e.policy,this.maxSteps=e.maxSteps??M,this.providerOptions=e.providerOptions,this.ensureAgentReady=e.ensureAgentReady??((e,t)=>E(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.sessionExecEnabled=e.sessionExecEnabled??!0,this.explicitShellProfile=e.shellProfile,this.resolveShellProfileFn=e.resolveShellProfileFn??C,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue,this.installAgentFn=e.installAgentFn??d,this.resolveCatalogFn=e.resolveCatalogFn??m}async createSession(t={}){const s=await this.ensureReady(),i=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(s,i,[])}async resumeSession(e){if(!this.store)throw new Error("resumeSession requires a ThreadStore");if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=await this.ensureReady();return this.buildSession(t,e,this.store.getMessages(e))}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:process.env}));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){const t=this.config.runtime.shell;return{workdir:process.cwd(),defaultTimeoutMs:t.defaultTimeoutMs,maxTimeoutMs:t.maxTimeoutMs,turnTimeoutMs:this.config.runtime.turnTimeoutMs,maxCaptureBytes:t.maxCaptureBytes,maxModelOutputChars:t.maxModelOutputChars,profile:e}}resolveSessionExecSettings(e){if(!this.sessionExecEnabled)return;const t=this.config.runtime.shell;if(t.enabled&&t.session.enabled){if(e.supportsSessionExec)return{workdir:process.cwd(),profile:e,maxSessions:t.session.maxSessions,defaultYieldMs:t.session.defaultYieldMs,maxOutputTokens:t.session.maxOutputTokens,bufferCapacity:t.maxCaptureBytes};this.shellSessionUnsupportedWarned||(this.shellSessionUnsupportedWarned=!0,process.stderr.write("roll chat: Windows 原生 session exec 暂未支持,已跳过 roll__exec_command / roll__exec_poll\n"))}}buildSession(e,t,s){const i=this.store,o=y(this.resolveModelName(),this.config.runtime.contextWindow),r=e.skillLibrary?.list()??[],n=r.length>0?e.skillLibrary:void 0,l=this.resolveRuntimeShellProfile(),a=l?this.resolveShellSettings(l):void 0,c=l?this.resolveSessionExecSettings(l):void 0,h=l?l.supportsSafeCommandClassification&&this.config.runtime.shell.autoApproveSafe?l:k:void 0,p=this.composeSystemPrompt(r,e.sources.length,l,c),m=this.resolveAgentInstallBinding();return new f({id:t,model:e.model,sources:e.sources,systemPrompt:p,...n?{skillLibrary:n}:{},...a?{bash:a}:{},...h?{bashClassifier:h}:{},...c?{bashSession:c}:{},...m?{agentInstall:m}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},onProviderOptionsChange:e=>this.syncProviderOptions(e),...void 0!==o?{contextWindow:o}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...i?{onPersist:e=>i.appendMessages(t,e),onReplace:e=>i.replaceMessages(t,e)}:{}})}syncProviderOptions(e){this.providerOptions=e,this.clientManager.setSamplingProviderOptions(e)}ensureReady(){return this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:e,sources:this.explicitSources,...this.explicitSkillLibrary?{skillLibrary:this.explicitSkillLibrary}:{}};const t=this.explicitAgents??new i(this.config.agents.dataDir).list();this.agentInstallEnabled()&&(this.resolvedCatalog=await this.resolveCatalogFn(this.config,{allowNetwork:!1,...this.config.install.registry?{registry:this.config.install.registry}:{}}));const s=[];for(const i of t)try{s.push(await this.connectAgentSource(i,e))}catch(e){this.onAgentBootstrapIssue?.({agentName:i.skill.name,message:e instanceof Error?e.message:String(e)})}const o=this.resolveSkillLibrary(t);return{model:e,sources:s,...o?{skillLibrary:o}:{}}}async connectAgentSource(e,t){const s=o(e),i=c(this.config,e.skill.name);await this.ensureAgentReady(e,i);const r=await this.clientManager.connect(e.skill.name,s,e.installPath,{samplingModel:t,...this.providerOptions?{samplingProviderOptions:this.providerOptions}:{},...i?{env:i}:{}}),n=(await r.listTools()).tools,l=a(n).map((e,t)=>({tool:e,annotations:A(n[t])}));return{agentName:e.skill.name,client:r,tools:l}}async prepareAgentRefresh(e){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,t.model),o=[...t.sources.filter(e=>e.agentName!==s.agentName),s],r=this.explicitAgents??new i(this.config.agents.dataDir).list(),n=this.resolveSkillLibrary(r);this.ready=Promise.resolve({model:t.model,sources:o,...n?{skillLibrary:n}:{}});const l=n?.list()??[],a=n&&l.length>0?n:void 0,c=this.resolveRuntimeShellProfile(),h=c?this.resolveSessionExecSettings(c):void 0;return{source:s,...a?{skillLibrary:a}:{},systemPrompt:this.composeSystemPrompt(l,o.length,c,h)}}composeSystemPrompt(e,t,s,i){const o=this.resolveAgentOnboardingInfo();return v({skills:e,...s?{shellToolId:"powershell"===s.toolName?b:S,shellHints:s.systemPromptHints()}:{},...i?{sessionExecToolIds:{command:x,poll:w}}:{},agentCount:t,...o?{agentOnboarding:o}:{}})}agentInstallEnabled(){return void 0===this.explicitSources&&void 0===this.explicitAgents}currentCatalog(){return this.resolvedCatalog??p(this.config)}resolveAgentOnboardingInfo(){if(!this.agentInstallEnabled())return;const e=this.currentCatalog();return 0!==e.length?{installToolId:u,catalog: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:h(s),skipBrowserSetup:!0,autoStart:!0,expectedSkillName:s.skillName},{agentsConfig:this.config.agents,installConfig:this.config.install,getStartEnv:e=>c(this.config,e),report:e=>t(P(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,l={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:l};try{return{outcome:l,refresh:await this.prepareAgentRefresh(o)}}catch(e){return t(`接入新 Agent 失败:${e instanceof Error?e.message:String(e)}`),{outcome:l}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:g({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.config.runtime.provider??this.config.llm.defaultProvider}resolveModelName(){return this.config.runtime.model??this.config.llm.defaultModel}async getContextSummary(){const e=await this.ensureReady();return{agentCount:e.sources.length,toolCount:e.sources.reduce((e,t)=>e+t.tools.length,0),skillCount:e.skillLibrary?.list().length??0}}async dispose(){await this.clientManager.disconnectAll()}}
@@ -1 +1 @@
1
- import{SKILL_TOOL_ID as n}from"../tool-bridge/skill-tool.js";const o=240,t="你是花卷 Roll 的会话助手,运行在 roll chat 里。";function e(n){return t+(n?"你通过已注册 Agent 提供的工具(MCP)观察和操作外部世界,并有一个内建 shell 工具可以在本机执行命令。":"你通过已注册 Agent 提供的工具(MCP)观察和操作外部世界;你没有独立的文件系统或 shell,工具就是你的全部执行手段。")}const l=["# 工具使用纪律","- 一切对外部世界的读取和操作都必须通过真实的工具调用完成。绝不虚构工具调用或其结果,也不要用文本描述来代替真正的调用。","- 只有当本会话中出现了对应的成功工具结果,才能说某个操作已完成。没有调用过工具,就如实说明尚未执行。","- 批量任务(例如给多个人回复)必须逐项执行:每一项都真实调用工具、等到结果后再处理下一项;最后按真实结果逐项汇报成功、失败或未执行,不要掩盖失败。","- 工具返回错误时,如实报告错误内容,再决定重试、换方案或向用户求助。不要把失败说成成功,也不要凭空猜测答案。","- 需要确认的工具调用被用户拒绝时,尊重用户的决定,不要换个方式绕过。"].join("\n"),i=["# 任务推进","- 接到任务后持续推进,直到完成或真正被阻塞,不要停在分析或计划阶段。个别工具调用失败不代表任务失败,先尝试自行恢复。","- 除非用户明确只要建议或分析,否则默认用户希望你实际执行。","- 多步任务先用一两句话说明打算怎么做,然后逐步执行,不要把计划本身当成结果。"].join("\n"),s=["# 输出","- 你可以用 thinking/reasoning 做内部推理,但给用户看的最终回复必须写入普通 text 输出通道,不要只写在 reasoning 里。","- 工具调用完成后,在 text 通道给出简洁结论;最终回复不要重复,也不要复述用户输入。","- 像可靠的同事一样汇报:先结论,后必要细节,保持简洁。"].join("\n");function r(n,o){return n.length<=o?n:`${n.slice(0,o-1)}…`}function a(n,t){return["# Skills",`以下是可用的技能说明书(skill)。当任务涉及某个 skill 的领域时,先调用 ${t} 工具(传 name)加载它的完整内容,按其中的流程和约束行事;skill 中的指导优先于你的默认做法。`,n.map(n=>`- ${n.name}: ${r(n.description.replace(/\s+/g," ").trim(),o)}`).join("\n"),`skill 正文提到 references/ 下的文件时,可再次调用 ${t} 并传 reference 参数读取对应文件。`].join("\n")}function c(n){return["# Agent 安装","当前没有任何已注册的子 Agent,对外部系统的操作能力受限。可安装的官方 Agent:",n.catalog.map(n=>`- ${n.shortName}: ${r(n.description.replace(/\s+/g," ").trim(),o)}`).join("\n"),`当用户的需求涉及上述 Agent 的能力时,先说明它的用途并征得用户同意,再调用 ${n.installToolId} 安装(安装会执行 npm install,用户还需在界面上二次确认)。新 Agent 的工具从下一轮对话开始可用。`,"绝不在用户未明确同意的情况下自行安装。"].join("\n")}function g(n,o,t){const e=o?[`- 预计跑几十秒以上的命令(构建、批处理脚本)不要用 ${n}(会被单轮超时杀掉),改用 ${o.command} 后台执行。`,`- ${o.command} 未结束时会返回 session_id;用 ${o.poll}(chars 留空)轮询进度直到拿到退出码,需要中断时 chars 传 "\\u0003"。`]:["- 预计耗时较长的命令(如构建、脚本)要显式调大 timeout_ms。"];return["# Shell 工具",`- 需要在本机执行命令时调用 ${n};用 workdir 参数指定工作目录,不要在 command 里用 cd。`,...t.map(n=>`- ${n}`),"- 输出会被截断,优先用精确过滤或预览命令,而不是全量 dump 大文件。","- 优先使用只读命令;有副作用或破坏性的命令可能需要用户确认,被拒绝时不要绕过。",...e].join("\n")}export function buildChatSystemPrompt(o={}){const t=o.shellToolId??o.bashToolId,r=[e(void 0!==t),l,i];0===o.agentCount&&void 0!==o.agentOnboarding&&o.agentOnboarding.catalog.length>0&&r.push(c(o.agentOnboarding));const d=o.skills??[];return d.length>0&&r.push(a(d,o.skillToolId??n)),void 0!==t&&r.push(g(t,o.sessionExecToolIds,o.shellHints??[])),r.push(s),r.join("\n\n")}
1
+ import{SKILL_TOOL_ID as n}from"../tool-bridge/skill-tool.js";const o=240,e="你是花卷 Roll 的会话助手,运行在 roll chat 里。";function t(n){return e+(n?"你通过已注册 Agent 提供的工具(MCP)观察和操作外部世界,并有一个内建 shell 工具可以在本机执行命令。":"你通过已注册 Agent 提供的工具(MCP)观察和操作外部世界;你没有独立的文件系统或 shell,工具就是你的全部执行手段。")}const l=["# 工具使用纪律","- 一切对外部世界的读取和操作都必须通过真实的工具调用完成。绝不虚构工具调用或其结果,也不要用文本描述来代替真正的调用。","- 只有当本会话中出现了对应的成功工具结果,才能说某个操作已完成。没有调用过工具,就如实说明尚未执行。","- 批量任务(例如给多个人回复)必须逐项执行:每一项都真实调用工具、等到结果后再处理下一项;最后按真实结果逐项汇报成功、失败或未执行,不要掩盖失败。","- 工具返回错误时,如实报告错误内容,再决定重试、换方案或向用户求助。不要把失败说成成功,也不要凭空猜测答案。","- 需要确认的工具调用被用户拒绝时,尊重用户的决定,不要换个方式绕过。"].join("\n"),i=["# 任务推进","- 接到任务后持续推进,直到完成或真正被阻塞,不要停在分析或计划阶段。个别工具调用失败不代表任务失败,先尝试自行恢复。","- 除非用户明确只要建议或分析,否则默认用户希望你实际执行。","- 多步任务先用一两句话说明打算怎么做,然后逐步执行,不要把计划本身当成结果。"].join("\n"),s=["# 输出","- 你可以用 thinking/reasoning 做内部推理,但给用户看的最终回复必须写入普通 text 输出通道,不要只写在 reasoning 里。","- 工具调用完成后,在 text 通道给出简洁结论;最终回复不要重复,也不要复述用户输入。","- 像可靠的同事一样汇报:先结论,后必要细节,保持简洁。"].join("\n");function r(n,o){return n.length<=o?n:`${n.slice(0,o-1)}…`}function a(n,e){return["# Skills",`以下是可用的技能说明书(skill)。当任务涉及某个 skill 的领域时,先调用 ${e} 工具(传 name)加载它的完整内容,按其中的流程和约束行事;skill 中的指导优先于你的默认做法。`,n.map(n=>`- ${n.name}: ${r(n.description.replace(/\s+/g," ").trim(),o)}`).join("\n"),"加载结果中的 SKILL_ROOT 是该 skill 的 canonical absolute root。正文里的 scripts/、references/ 等相对路径一律相对 SKILL_ROOT 解析;执行脚本时把 workdir 设为 SKILL_ROOT,不要再搜索 .roll、.claude、.agents 或其它目录猜路径。",`skill 正文提到 references/ 下的文件时,可再次调用 ${e} 并传 reference 参数读取对应文件。`].join("\n")}function c(n){return["# Agent 安装","当前没有任何已注册的子 Agent,对外部系统的操作能力受限。可安装的官方 Agent:",n.catalog.map(n=>`- ${n.shortName}: ${r(n.description.replace(/\s+/g," ").trim(),o)}`).join("\n"),`当用户的需求涉及上述 Agent 的能力时,先说明它的用途并征得用户同意,再调用 ${n.installToolId} 安装(安装会执行 npm install,用户还需在界面上二次确认)。新 Agent 的工具从下一轮对话开始可用。`,"绝不在用户未明确同意的情况下自行安装。"].join("\n")}function g(n,o,e){const t=o?[`- 预计跑几十秒以上的命令(构建、批处理脚本)不要用 ${n}(会被单轮超时杀掉),改用 ${o.command} 后台执行。`,`- ${o.command} 未结束时会返回 session_id;用 ${o.poll}(chars 留空)轮询进度直到拿到退出码,需要中断时 chars 传 "\\u0003"。`]:["- 预计耗时较长的命令(如构建、脚本)要显式调大 timeout_ms。"];return["# Shell 工具",`- 需要在本机执行命令时调用 ${n};用 workdir 参数指定工作目录,不要在 command 里用 cd。`,...e.map(n=>`- ${n}`),"- 输出会被截断,优先用精确过滤或预览命令,而不是全量 dump 大文件。","- 优先使用只读命令;有副作用或破坏性的命令可能需要用户确认,被拒绝时不要绕过。",...t].join("\n")}export function buildChatSystemPrompt(o={}){const e=o.shellToolId??o.bashToolId,r=[t(void 0!==e),l,i];0===o.agentCount&&void 0!==o.agentOnboarding&&o.agentOnboarding.catalog.length>0&&r.push(c(o.agentOnboarding));const d=o.skills??[];return d.length>0&&r.push(a(d,o.skillToolId??n)),void 0!==e&&r.push(g(e,o.sessionExecToolIds,o.shellHints??[])),r.push(s),r.join("\n\n")}
package/dist/index.d.ts CHANGED
@@ -30,3 +30,5 @@ export { createStdioConnection } from "./server/transport/stdio.ts";
30
30
  export { RpcMethod, EVENT_NOTIFICATION, isRequest } from "./server/protocol.ts";
31
31
  export type { JsonRpcConnection, JsonRpcMessage, JsonRpcRequest, JsonRpcNotification, JsonRpcId, } from "./server/protocol.ts";
32
32
  export type { SessionEvent, SessionEventStage, SessionTokenUsage, ContextCompactionReason, ContextCompactionStrategy, } from "./types/events.ts";
33
+ export { SESSION_CANCELLATION_REASONS, USER_CANCELLATION_ABORT_REASON, } from "./types/cancellation.ts";
34
+ export type { SessionCancellationReason } from "./types/cancellation.ts";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{ConversationEngine}from"./engine/conversation-engine.js";export{AgentSession}from"./engine/agent-session.js";export{resolveContextWindow,lookupContextWindow}from"./engine/context-window.js";export{compactMessages,findTurnBoundaries,SUMMARY_PREFIX,SUMMARY_ACK}from"./engine/compactor.js";export{buildAgentToolset}from"./tool-bridge/build-tools.js";export{COMMAND_CLASSIFICATIONS,unknownCommandClassifier}from"./types/command-classification.js";export{ruleBasedClassifier}from"./bash/classifier/index.js";export{buildPowerShellEncodedCommand,resolveShellProfile,SHELL_PROFILE_IDS,SHELL_TOOL_NAMES}from"./bash/profile.js";export{buildSessionExecToolset,EXEC_COMMAND_ID,EXEC_POLL_ID}from"./tool-bridge/session-exec-tool.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{normalizeToolResult}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";
1
+ export{ConversationEngine}from"./engine/conversation-engine.js";export{AgentSession}from"./engine/agent-session.js";export{resolveContextWindow,lookupContextWindow}from"./engine/context-window.js";export{compactMessages,findTurnBoundaries,SUMMARY_PREFIX,SUMMARY_ACK}from"./engine/compactor.js";export{buildAgentToolset}from"./tool-bridge/build-tools.js";export{COMMAND_CLASSIFICATIONS,unknownCommandClassifier}from"./types/command-classification.js";export{ruleBasedClassifier}from"./bash/classifier/index.js";export{buildPowerShellEncodedCommand,resolveShellProfile,SHELL_PROFILE_IDS,SHELL_TOOL_NAMES}from"./bash/profile.js";export{buildSessionExecToolset,EXEC_COMMAND_ID,EXEC_POLL_ID}from"./tool-bridge/session-exec-tool.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{normalizeToolResult}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";export{SESSION_CANCELLATION_REASONS,USER_CANCELLATION_ABORT_REASON}from"./types/cancellation.js";
@@ -38,6 +38,7 @@ export declare const RpcMethod: {
38
38
  readonly Approve: "session.approve";
39
39
  readonly Reject: "session.reject";
40
40
  readonly Abort: "session.abort";
41
+ readonly Close: "session.close";
41
42
  readonly Messages: "session.messages";
42
43
  readonly Compact: "session.compact";
43
44
  };
@@ -96,6 +97,13 @@ export declare const abortParamsSchema: z.ZodObject<{
96
97
  }, {
97
98
  sessionId: string;
98
99
  }>;
100
+ export declare const closeParamsSchema: z.ZodObject<{
101
+ sessionId: z.ZodString;
102
+ }, "strip", z.ZodTypeAny, {
103
+ sessionId: string;
104
+ }, {
105
+ sessionId: string;
106
+ }>;
99
107
  export declare const messagesParamsSchema: z.ZodObject<{
100
108
  sessionId: z.ZodString;
101
109
  }, "strip", z.ZodTypeAny, {
@@ -1 +1 @@
1
- import{z as s}from"zod";export const RpcMethod={Create:"session.create",Resume:"session.resume",Send:"session.send",Approve:"session.approve",Reject:"session.reject",Abort:"session.abort",Messages:"session.messages",Compact:"session.compact"};export const EVENT_NOTIFICATION="session.event";export const createParamsSchema=s.object({title:s.string().optional()});export const resumeParamsSchema=s.object({threadId:s.string()});export const sendParamsSchema=s.object({sessionId:s.string(),input:s.string()});export const approveParamsSchema=s.object({sessionId:s.string(),approvalId:s.string()});export const rejectParamsSchema=s.object({sessionId:s.string(),approvalId:s.string(),reason:s.string().optional()});export const abortParamsSchema=s.object({sessionId:s.string()});export const messagesParamsSchema=s.object({sessionId:s.string()});export const compactParamsSchema=s.object({sessionId:s.string()});export function isRequest(s){return"method"in s&&"id"in s}
1
+ import{z as s}from"zod";export const RpcMethod={Create:"session.create",Resume:"session.resume",Send:"session.send",Approve:"session.approve",Reject:"session.reject",Abort:"session.abort",Close:"session.close",Messages:"session.messages",Compact:"session.compact"};export const EVENT_NOTIFICATION="session.event";export const createParamsSchema=s.object({title:s.string().optional()});export const resumeParamsSchema=s.object({threadId:s.string()});export const sendParamsSchema=s.object({sessionId:s.string(),input:s.string()});export const approveParamsSchema=s.object({sessionId:s.string(),approvalId:s.string()});export const rejectParamsSchema=s.object({sessionId:s.string(),approvalId:s.string(),reason:s.string().optional()});export const abortParamsSchema=s.object({sessionId:s.string()});export const closeParamsSchema=s.object({sessionId:s.string()});export const messagesParamsSchema=s.object({sessionId:s.string()});export const compactParamsSchema=s.object({sessionId:s.string()});export function isRequest(s){return"method"in s&&"id"in s}
@@ -1 +1 @@
1
- import{EVENT_NOTIFICATION as s,RpcMethod as e,abortParamsSchema as n,approveParamsSchema as t,compactParamsSchema as o,createParamsSchema as r,isRequest as i,messagesParamsSchema as a,rejectParamsSchema as c,resumeParamsSchema as d,sendParamsSchema as p}from"./protocol.js";function h(s){return s instanceof Error?s.message:String(s)}export class RuntimeServer{engine;connection;sessions=new Map;constructor(s,e){this.engine=s,this.connection=e,this.connection.onMessage(s=>this.handleMessage(s))}abortAll(){for(const s of this.sessions.values())s.abort();this.sessions.clear()}handleMessage(s){i(s)&&this.dispatch(s).then(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,result:e})}).catch(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,error:{code:-32e3,message:h(e)}})})}requireSession(s){const e=this.sessions.get(s);if(!e)throw new Error(`Session "${s}" 不存在`);return e}async dispatch(i){switch(i.method){case e.Create:{const s=r.parse(i.params),e=await this.engine.createSession(s.title?{title:s.title}:{});return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Resume:{const s=d.parse(i.params),e=await this.engine.resumeSession(s.threadId);return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Send:{const e=p.parse(i.params),n=this.requireSession(e.sessionId);for await(const t of n.send(e.input))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}case e.Approve:{const s=t.parse(i.params);return{resolved:this.requireSession(s.sessionId).approve(s.approvalId)}}case e.Reject:{const s=c.parse(i.params);return{resolved:this.requireSession(s.sessionId).reject(s.approvalId,s.reason)}}case e.Abort:{const s=n.parse(i.params);return this.requireSession(s.sessionId).abort(),{ok:!0}}case e.Messages:{const s=a.parse(i.params);return{messages:this.requireSession(s.sessionId).getMessages()}}case e.Compact:{const e=o.parse(i.params),n=this.requireSession(e.sessionId);for await(const t of n.compact("manual"))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}default:throw new Error(`Unknown method: ${i.method}`)}}}
1
+ import{EVENT_NOTIFICATION as s,RpcMethod as e,abortParamsSchema as n,approveParamsSchema as t,closeParamsSchema as o,compactParamsSchema as r,createParamsSchema as i,isRequest as a,messagesParamsSchema as c,rejectParamsSchema as d,resumeParamsSchema as p,sendParamsSchema as h}from"./protocol.js";function u(s){return s instanceof Error?s.message:String(s)}export class RuntimeServer{engine;connection;sessions=new Map;constructor(s,e){this.engine=s,this.connection=e,this.connection.onMessage(s=>this.handleMessage(s))}abortAll(){for(const s of this.sessions.values())s.abort();this.sessions.clear()}handleMessage(s){a(s)&&this.dispatch(s).then(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,result:e})}).catch(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,error:{code:-32e3,message:u(e)}})})}requireSession(s){const e=this.sessions.get(s);if(!e)throw new Error(`Session "${s}" 不存在`);return e}async dispatch(a){switch(a.method){case e.Create:{const s=i.parse(a.params),e=await this.engine.createSession(s.title?{title:s.title}:{});return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Resume:{const s=p.parse(a.params),e=await this.engine.resumeSession(s.threadId);return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Send:{const e=h.parse(a.params),n=this.requireSession(e.sessionId);for await(const t of n.send(e.input))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}case e.Approve:{const s=t.parse(a.params);return{resolved:this.requireSession(s.sessionId).approve(s.approvalId)}}case e.Reject:{const s=d.parse(a.params);return{resolved:this.requireSession(s.sessionId).reject(s.approvalId,s.reason)}}case e.Abort:{const s=n.parse(a.params);return{ok:!0,cancelled:this.requireSession(s.sessionId).cancel()}}case e.Close:{const s=o.parse(a.params);return this.requireSession(s.sessionId).abort(),this.sessions.delete(s.sessionId),{closed:!0}}case e.Messages:{const s=c.parse(a.params);return{messages:this.requireSession(s.sessionId).getMessages()}}case e.Compact:{const e=r.parse(a.params),n=this.requireSession(e.sessionId);for await(const t of n.compact("manual"))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}default:throw new Error(`Unknown method: ${a.method}`)}}}
@@ -1 +1 @@
1
- import{existsSync as e}from"node:fs";import{resolve as o}from"node:path";import{performance as t}from"node:perf_hooks";import{tool as i}from"ai";import{z as n}from"zod";import{CLASSIFICATION_ANNOTATIONS as r,unknownCommandClassifier as s}from"../types/command-classification.js";import{isWithinWorkdirRoot as m}from"../bash/workdir.js";import{SessionCapError as l}from"../bash/session/session-manager.js";import{pollUntilDeadline as c}from"../bash/session/yield-loop.js";import{gateToolCall as a}from"./build-tools.js";import{ToolRegistry as d}from"./naming.js";export const EXEC_AGENT_NAME="roll";export const EXEC_COMMAND_NAME="exec_command";export const EXEC_POLL_NAME="exec_poll";export const EXEC_COMMAND_ID="roll__exec_command";export const EXEC_POLL_ID="roll__exec_poll";const u=String.fromCharCode(3),p=250,_=3e4,x=5e3,f=3e5,h=8192,E=4096,g=4,C=n.object({command:n.string().min(1).describe("要在后台会话中执行的 shell 命令(单字符串)"),workdir:n.string().min(1).optional().describe("工作目录绝对路径,默认 roll chat 当前目录,不要用 cd"),yield_time_ms:n.number().int().min(p).max(_).optional().describe("本次等待输出的毫秒数(默认 10000,范围 250-30000);未结束会返回 session_id 供 exec_poll 续查"),max_output_tokens:n.number().int().min(256).max(5e4).optional().describe("本次返回输出的 token 预算")}),k=n.object({session_id:n.number().int().describe("exec_command 返回的会话 id"),chars:n.string().default("").describe('留空表示纯轮询进度;"\\u0003"(Ctrl-C) 表示中断该会话;不支持其它交互输入'),yield_time_ms:n.number().int().min(x).max(f).optional().describe("空轮询等待的毫秒数(默认 10000,范围 5000-300000)")});function b(e,o,t){return Math.min(Math.max(e,o),t)}function w(e,o,t){const i=e.emitEvent;if(!i)return;let n=0;return(e,r)=>{if(n>=h)return;n+=1;const s=r.length>E?r.slice(0,E):r;i({type:"tool-output-delta",toolCallId:o,agentName:"roll",toolName:t,stream:e,delta:s})}}async function y(e,o,t){if(e.policy)return a(e,"roll","exec_command",o,t);const i=await e.requestApproval({agentName:"roll",toolName:"exec_command",input:o,reason:"shell 命令需确认"});return i.approved?void 0:{output:"已取消执行"+(i.reason?`: ${i.reason}`:""),isError:!0}}function M(e){const o=["running"===e.kind?`Session: ${String(e.sessionId)} (running)`:`Exit code: ${String(e.exitCode)}`,`Wall time: ${(e.wallTimeMs/1e3).toFixed(1)} s`];e.omitted>0&&o.push(`(省略中间 ${String(e.omitted)} 字符)`);const t=e.output.length>0?`\n\n${e.output}`:"";return{output:o.join("\n")+t,isError:"exited"===e.kind&&0!==e.exitCode}}export function buildSessionExecToolset(n,a,d,h,E={}){const g=d.register("roll","exec_command"),N=d.register("roll","exec_poll"),S=E.classifier??s;return{[g]:i({description:"在后台会话中执行一条命令,等待一段时间后返回输出。若命令未结束会返回 session_id,用 exec_poll 续查进度、读取退出码。适合运行时间超过单轮预算的长脚本。命令继承 roll 进程的环境变量。",inputSchema:C,execute:async(i,s)=>{const d=o(n.workdir,i.workdir??".");if(!e(d))return{output:`工作目录不存在: ${d}`,isError:!0};const u=b(i.yield_time_ms??n.defaultYieldMs,p,_),x=m(n.workdir,d)?S.classify(i.command,d):"unknown",f=r[x],E={command:i.command,workdir:d,yield_time_ms:u},g=await y(h,E,f);if(g)return g;const C=4*(i.max_output_tokens??n.maxOutputTokens),k=w(h,s.toolCallId,"exec_command");let N;try{N=a.spawn({command:i.command,workdir:d,...k?{onDelta:k}:{}})}catch(e){return{output:e instanceof l?e.message:`无法启动会话: ${String(e)}`,isError:!0}}const j=await c(N,t.now()+u,C);return"exited"===j.kind&&a.delete(N.id),M(j)}}),[N]:i({description:'轮询或中断一个 exec_command 会话。session_id 为 exec_command 返回的 id;chars 留空表示纯轮询进度,"\\u0003" 表示发送 Ctrl-C 中断。返回最新输出,进程结束时返回退出码。',inputSchema:k,execute:async(e,o)=>{const i=a.get(e.session_id);if(!i)return{output:`会话 ${String(e.session_id)} 不存在或已结束`,isError:!0};if(e.chars===u)i.profile.killTree(i.child.pid,"interrupt").catch(()=>{});else if(""!==e.chars)return{output:"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)",isError:!0};const r=w(h,o.toolCallId,"exec_poll");r&&(i.onDelta=r);const s=b(e.yield_time_ms??Math.max(n.defaultYieldMs,x),x,f),m=4*n.maxOutputTokens,l=await c(i,t.now()+s,m);return"exited"===l.kind&&a.delete(e.session_id),M(l)}})}}
1
+ import{existsSync as e}from"node:fs";import{resolve as t}from"node:path";import{performance as o}from"node:perf_hooks";import{tool as i}from"ai";import{z as n}from"zod";import{CLASSIFICATION_ANNOTATIONS as r,unknownCommandClassifier as s}from"../types/command-classification.js";import{isWithinWorkdirRoot as l}from"../bash/workdir.js";import{SessionCapError as a}from"../bash/session/session-manager.js";import{pollUntilDeadline as c}from"../bash/session/yield-loop.js";import{gateToolCall as m}from"./build-tools.js";import{ToolRegistry as d}from"./naming.js";import{isUserCancellationSignal as u}from"../types/cancellation.js";export const EXEC_AGENT_NAME="roll";export const EXEC_COMMAND_NAME="exec_command";export const EXEC_POLL_NAME="exec_poll";export const EXEC_COMMAND_ID="roll__exec_command";export const EXEC_POLL_ID="roll__exec_poll";const p=String.fromCharCode(3),_=250,f=3e4,x=5e3,h=3e5,E=8192,g=4096,b=4,k=n.object({command:n.string().min(1).describe("要在后台会话中执行的 shell 命令(单字符串)"),workdir:n.string().min(1).optional().describe("工作目录绝对路径,默认 roll chat 当前目录,不要用 cd"),yield_time_ms:n.number().int().min(_).max(f).optional().describe("本次等待输出的毫秒数(默认 10000,范围 250-30000);未结束会返回 session_id 供 exec_poll 续查"),max_output_tokens:n.number().int().min(256).max(5e4).optional().describe("本次返回输出的 token 预算")}),C=n.object({session_id:n.number().int().describe("exec_command 返回的会话 id"),chars:n.string().default("").describe('留空表示纯轮询进度;"\\u0003"(Ctrl-C) 表示中断该会话;不支持其它交互输入'),yield_time_ms:n.number().int().min(x).max(h).optional().describe("空轮询等待的毫秒数(默认 10000,范围 5000-300000)")});function w(e,t,o){return Math.min(Math.max(e,t),o)}function y(e,t,o){const i=e.emitEvent;if(!i)return;let n=0;return(e,r)=>{if(n>=E)return;n+=1;const s=r.length>g?r.slice(0,g):r;i({type:"tool-output-delta",toolCallId:t,agentName:"roll",toolName:o,stream:e,delta:s})}}async function M(e,t,o){if(e.policy)return m(e,"roll","exec_command",t,o);const i=await e.requestApproval({agentName:"roll",toolName:"exec_command",input:t,reason:"shell 命令需确认"});return i.approved?void 0:{output:"已取消执行"+(i.reason?`: ${i.reason}`:""),isError:!0}}function S(e){const t=["running"===e.kind?`Session: ${String(e.sessionId)} (running)`:`Exit code: ${String(e.exitCode)}`,`Wall time: ${(e.wallTimeMs/1e3).toFixed(1)} s`];e.omitted>0&&t.push(`(省略中间 ${String(e.omitted)} 字符)`);const o=e.output.length>0?`\n\n${e.output}`:"";return{output:t.join("\n")+o,isError:"exited"===e.kind&&0!==e.exitCode}}function j(e,t,o){if(void 0===e)return()=>{};const i=()=>{u(e)&&o.get(t.id)===t&&t.profile.killTree(t.child.pid,"interrupt").then(()=>t.waitExit()).then(()=>o.delete(t.id)).catch(()=>{})};return e.addEventListener("abort",i,{once:!0}),e.aborted&&i(),()=>e.removeEventListener("abort",i)}export function buildSessionExecToolset(n,m,d,u,E={}){const g=d.register("roll","exec_command"),b=d.register("roll","exec_poll"),N=E.classifier??s;return{[g]:i({description:"在后台会话中执行一条命令,等待一段时间后返回输出。若命令未结束会返回 session_id,用 exec_poll 续查进度、读取退出码。适合运行时间超过单轮预算的长脚本。命令继承 roll 进程的环境变量。",inputSchema:k,execute:async(i,s)=>{const d=t(n.workdir,i.workdir??".");if(!e(d))return{output:`工作目录不存在: ${d}`,isError:!0};const p=w(i.yield_time_ms??n.defaultYieldMs,_,f),x=l(n.workdir,d)?N.classify(i.command,d):"unknown",h=r[x],E={command:i.command,workdir:d,yield_time_ms:p},g=await M(u,E,h);if(g)return g;const b=4*(i.max_output_tokens??n.maxOutputTokens),k=y(u,s.toolCallId,"exec_command");let C;try{C=m.spawn({command:i.command,workdir:d,...k?{onDelta:k}:{}})}catch(e){return{output:e instanceof a?e.message:`无法启动会话: ${String(e)}`,isError:!0}}const $=j(s.abortSignal,C,m),v=await c(C,o.now()+p,b).finally($);return"exited"===v.kind&&m.delete(C.id),S(v)}}),[b]:i({description:'轮询或中断一个 exec_command 会话。session_id 为 exec_command 返回的 id;chars 留空表示纯轮询进度,"\\u0003" 表示发送 Ctrl-C 中断。返回最新输出,进程结束时返回退出码。',inputSchema:C,execute:async(e,t)=>{const i=m.get(e.session_id);if(!i)return{output:`会话 ${String(e.session_id)} 不存在或已结束`,isError:!0};if(e.chars===p)i.profile.killTree(i.child.pid,"interrupt").catch(()=>{});else if(""!==e.chars)return{output:"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)",isError:!0};const r=y(u,t.toolCallId,"exec_poll");r&&(i.onDelta=r);const s=w(e.yield_time_ms??Math.max(n.defaultYieldMs,x),x,h),l=4*n.maxOutputTokens,a=j(t.abortSignal,i,m),d=await c(i,o.now()+s,l).finally(a);return"exited"===d.kind&&m.delete(e.session_id),S(d)}})}}
@@ -1 +1 @@
1
- import{tool as e}from"ai";import{z as r}from"zod";import{SKILL_TOOL_ID as n}from"@roll-agent/core/skills/library";export const SKILL_TOOL_AGENT_NAME="roll";export const SKILL_TOOL_NAME="skill";export{n as SKILL_TOOL_ID};const t=6e4,o=r.object({name:r.string().min(1).describe("要加载的 skill 名称(见 system prompt 中的 Skills 目录)"),reference:r.string().min(1).optional().describe("可选:加载该 skill 的 references/ 下的某个文件,传相对路径如 references/workflows.md")});function i(e){return e.length<=t?e:`${e.slice(0,t)}\n\n[内容过长已截断,共 ${String(e.length)} 字符]`}function l(e,r){const n=e.load(r);if(!n){return{output:`skill "${r}" 不存在。可用 skill: ${e.list().map(e=>e.name).join(", ")}`,isError:!0}}const t=n.referencePaths.length>0?`\n\n可用 references(传 reference 参数加载):\n${n.referencePaths.map(e=>`- ${e}`).join("\n")}`:"";return{output:`${i(n.content)}${t}`,isError:!1}}function s(e,r,n){const t=e.loadReference(r,n);return void 0===t?{output:`skill "${r}" 中不存在 reference "${n}"(仅支持 skill 目录内 references/ 下的文件)`,isError:!0}:{output:i(t),isError:!1}}export function executeSkillTool(e,r){return void 0!==r.reference?s(e,r.name,r.reference):l(e,r.name)}export function buildSkillToolset(r,n){const t="function"==typeof r?r:()=>r,i=n.register("roll","skill");return{[i]:e({description:"加载一个 skill(技能说明书)的完整内容,或其 references/ 下的文件。执行涉及某个 skill 领域的任务前,先用它读取流程与约束。",inputSchema:o,execute:e=>executeSkillTool(t(),e)})}}
1
+ import{tool as e}from"ai";import{z as r}from"zod";import{SKILL_TOOL_ID as n}from"@roll-agent/core/skills/library";export const SKILL_TOOL_AGENT_NAME="roll";export const SKILL_TOOL_NAME="skill";export{n as SKILL_TOOL_ID};const o=6e4,t=r.object({name:r.string().min(1).describe("要加载的 skill 名称(见 system prompt 中的 Skills 目录)"),reference:r.string().min(1).optional().describe("可选:加载该 skill 的 references/ 下的某个文件,传相对路径如 references/workflows.md")});function i(e){return e.length<=o?e:`${e.slice(0,o)}\n\n[内容过长已截断,共 ${String(e.length)} 字符]`}function l(e,r){return void 0===e?r:[`SKILL_ROOT=${e}`,"这是该 skill 的 canonical absolute root。所有 scripts/、references/ 和其它相对路径必须相对 SKILL_ROOT 解析;执行脚本时将 workdir 设为 SKILL_ROOT,不要搜索其它 skill 目录。","",r].join("\n")}function s(e,r){return{output:`skill "${r}" 不存在。可用 skill: ${e.list().map(e=>e.name).join(", ")}`,isError:!0}}function c(e,r){const n=e.load(r);if(!n)return s(e,r);const o=n.referencePaths.length>0?`\n\n可用 references(传 reference 参数加载):\n${n.referencePaths.map(e=>`- ${e}`).join("\n")}`:"";return{output:l(n.skillRoot,`${i(n.content)}${o}`),isError:!1}}function u(e,r,n){if(!e.list().some(e=>e.name===r))return s(e,r);const o=void 0===e.loadReferenceDocument?void 0:e.loadReferenceDocument(r,n),t=void 0===e.loadReferenceDocument?e.loadReference(r,n):o?.content;return void 0===t?{output:`skill "${r}" 中不存在 reference "${n}"(仅支持 skill 目录内 references/ 下的文件)`,isError:!0}:{output:l(o?.skillRoot,i(t)),isError:!1}}export function executeSkillTool(e,r){return void 0!==r.reference?u(e,r.name,r.reference):c(e,r.name)}export function buildSkillToolset(r,n){const o="function"==typeof r?r:()=>r,i=n.register("roll","skill");return{[i]:e({description:"加载一个 skill(技能说明书)的完整内容,或其 references/ 下的文件。执行涉及某个 skill 领域的任务前,先用它读取流程与约束。",inputSchema:t,execute:e=>executeSkillTool(o(),e)})}}
@@ -0,0 +1,12 @@
1
+ export declare const SESSION_CANCELLATION_REASONS: {
2
+ readonly user: "user";
3
+ readonly timeout: "timeout";
4
+ readonly runtime: "runtime";
5
+ };
6
+ export type SessionCancellationReason = (typeof SESSION_CANCELLATION_REASONS)[keyof typeof SESSION_CANCELLATION_REASONS];
7
+ export declare const USER_CANCELLATION_ABORT_REASON = "roll:user-cancelled";
8
+ export declare const RUNTIME_CANCELLATION_ABORT_REASON = "roll:runtime-aborted";
9
+ export declare const TURN_TIMEOUT_ABORT_REASON = "roll:turn-timeout";
10
+ export declare function isTurnTimeoutAbortReason(reason: unknown): boolean;
11
+ export declare function isTimeoutAbortReason(reason: unknown): boolean;
12
+ export declare function isUserCancellationSignal(signal: AbortSignal | undefined): boolean;
@@ -0,0 +1 @@
1
+ export const SESSION_CANCELLATION_REASONS={user:"user",timeout:"timeout",runtime:"runtime"};export const USER_CANCELLATION_ABORT_REASON="roll:user-cancelled";export const RUNTIME_CANCELLATION_ABORT_REASON="roll:runtime-aborted";export const TURN_TIMEOUT_ABORT_REASON="roll:turn-timeout";export function isTurnTimeoutAbortReason(t){return"roll:turn-timeout"===t}export function isTimeoutAbortReason(t){return isTurnTimeoutAbortReason(t)||t instanceof DOMException&&"TimeoutError"===t.name}export function isUserCancellationSignal(t){return!0===t?.aborted&&"roll:user-cancelled"===t.reason}
@@ -1,3 +1,4 @@
1
+ import type { SessionCancellationReason } from "./cancellation.ts";
1
2
  export interface SessionTokenUsage {
2
3
  readonly inputTokens?: number;
3
4
  readonly outputTokens?: number;
@@ -71,6 +72,10 @@ export type SessionEvent = {
71
72
  readonly kept: number;
72
73
  readonly truncatedTools?: number;
73
74
  readonly beforeInputTokens?: number;
75
+ } | {
76
+ readonly type: "turn-cancelled";
77
+ readonly reason: SessionCancellationReason;
78
+ readonly message: string;
74
79
  } | {
75
80
  readonly type: "error";
76
81
  readonly stage: SessionEventStage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.0.0",
36
- "@roll-agent/core": "0.16.0"
36
+ "@roll-agent/core": "0.17.0"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",