@roll-agent/runtime 0.4.0 → 0.5.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/classifier/compound.d.ts +3 -0
- package/dist/bash/classifier/compound.js +1 -0
- package/dist/bash/classifier/dangerous.d.ts +1 -0
- package/dist/bash/classifier/dangerous.js +1 -0
- package/dist/bash/classifier/flag-audit.d.ts +3 -0
- package/dist/bash/classifier/flag-audit.js +1 -0
- package/dist/bash/classifier/git-audit.d.ts +2 -0
- package/dist/bash/classifier/git-audit.js +1 -0
- package/dist/bash/classifier/index.d.ts +3 -0
- package/dist/bash/classifier/index.js +1 -0
- package/dist/bash/classifier/lookup-key.d.ts +1 -0
- package/dist/bash/classifier/lookup-key.js +1 -0
- package/dist/bash/classifier/path-audit.d.ts +3 -0
- package/dist/bash/classifier/path-audit.js +1 -0
- package/dist/bash/classifier/safe-list.d.ts +1 -0
- package/dist/bash/classifier/safe-list.js +1 -0
- package/dist/bash/classifier/tokenize.d.ts +10 -0
- package/dist/bash/classifier/tokenize.js +1 -0
- package/dist/bash/classifier/types.d.ts +2 -0
- package/dist/bash/classifier/types.js +1 -0
- package/dist/bash/clean-env.d.ts +8 -0
- package/dist/bash/clean-env.js +1 -0
- package/dist/bash/exec.d.ts +22 -0
- package/dist/bash/exec.js +1 -0
- package/dist/bash/format-result.d.ts +25 -0
- package/dist/bash/format-result.js +1 -0
- package/dist/bash/kill.d.ts +3 -0
- package/dist/bash/kill.js +1 -0
- package/dist/bash/output-buffer.d.ts +23 -0
- package/dist/bash/output-buffer.js +1 -0
- package/dist/bash/profile.d.ts +43 -0
- package/dist/bash/profile.js +1 -0
- package/dist/bash/session/head-tail-buffer.d.ts +17 -0
- package/dist/bash/session/head-tail-buffer.js +1 -0
- package/dist/bash/session/session-exec.d.ts +2 -0
- package/dist/bash/session/session-exec.js +1 -0
- package/dist/bash/session/session-manager.d.ts +30 -0
- package/dist/bash/session/session-manager.js +1 -0
- package/dist/bash/session/types.d.ts +39 -0
- package/dist/bash/session/types.js +1 -0
- package/dist/bash/session/yield-loop.d.ts +2 -0
- package/dist/bash/session/yield-loop.js +1 -0
- package/dist/bash/shell.d.ts +6 -0
- package/dist/bash/shell.js +1 -0
- package/dist/bash/truncate.d.ts +6 -0
- package/dist/bash/truncate.js +1 -0
- package/dist/bash/workdir.d.ts +1 -0
- package/dist/bash/workdir.js +1 -0
- package/dist/engine/agent-session.d.ts +39 -3
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/conversation-engine.d.ts +31 -1
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/engine/system-prompt.d.ts +18 -0
- package/dist/engine/system-prompt.js +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/tool-bridge/agent-install-tool.d.ts +27 -0
- package/dist/tool-bridge/agent-install-tool.js +1 -0
- package/dist/tool-bridge/bash-tool.d.ts +45 -0
- package/dist/tool-bridge/bash-tool.js +1 -0
- package/dist/tool-bridge/build-tools.d.ts +2 -0
- package/dist/tool-bridge/build-tools.js +1 -1
- package/dist/tool-bridge/session-exec-tool.d.ts +52 -0
- package/dist/tool-bridge/session-exec-tool.js +1 -0
- package/dist/tool-bridge/skill-tool.d.ts +1 -1
- package/dist/tool-bridge/skill-tool.js +1 -1
- package/dist/types/command-classification.d.ts +8 -0
- package/dist/types/command-classification.js +1 -0
- package/dist/types/events.d.ts +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{auditFlags as n,FLAG_AUDITORS as o}from"./flag-audit.js";import{auditGit as t}from"./git-audit.js";import{auditPathArgs as r}from"./path-audit.js";import{isDangerous as e}from"./dangerous.js";import{executableLookupKey as s}from"./lookup-key.js";import{isSafeExecutable as u}from"./safe-list.js";import{tokenizeScript as f}from"./tokenize.js";const i=["$","`",">","<","(",")","{","}","\\"];export function containsDangerousMetachar(n){for(const o of i)if(n.includes(o))return!0;return n.replace(/&&/g,"").includes("&")}function a(f,i){const a=f[0];if(void 0===a)return"unknown";if(e(f,i))return"dangerous";const c=s(a,i);return"safe"!==r(c,f)?"unknown":"git"===c?"safe"===t(f)?"known-safe":"unknown":Object.hasOwn(o,c)?"safe"===n(c,f)?"known-safe":"unknown":u(c,i)?"known-safe":"unknown"}export function classifyScript(n,o){if(containsDangerousMetachar(n))return"unknown";const t=f(n);if(null===t)return"unknown";const r=[],e=[];let s=[];for(const n of t)"op"===n.kind?(r.push(s),e.push(n.value),s=[]):s.push(n.value);r.push(s);const u=r[r.length-1];if(void 0!==u&&0===u.length&&";"===e[e.length-1]&&r.pop(),0===r.length||r.some(n=>0===n.length))return"unknown";let i=!0;for(const n of r){const t=a(n,o);if("dangerous"===t)return"dangerous";"known-safe"!==t&&(i=!1)}return i?"known-safe":"unknown"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isDangerous(argv: readonly string[], platform: NodeJS.Platform): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{executableLookupKey as o}from"./lookup-key.js";const r=new Set(["-f","-rf"]);export function isDangerous(n,s){const t=n[0];if(void 0===t)return!1;const e=o(t,s);if("rm"===e){const o=n[1];return void 0!==o&&r.has(o)}return"sudo"===e&&isDangerous(n.slice(1),s)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isEscapingPathArg as t}from"./path-audit.js";const e=new Set(["-exec","-execdir","-ok","-okdir","-delete","-fls","-fprint","-fprint0","-fprintf"]),r=t=>{for(const r of t.slice(1))if(e.has(r))return"reject";return"safe"};function n(e,r){for(let n=1;n<e.length;n+=1){const s=e[n];if(void 0!==s)for(const f of r){if(s===f){const r=e[n+1];if(void 0===r||t(r))return!0;n+=1;break}if(s.startsWith(`${f}=`)&&t(s.slice(f.length+1)))return!0;if(2===f.length&&s.startsWith(f)&&t(s.slice(2)))return!0}}return!1}const s=["-f","--file","--exclude-from"],f=t=>n(t,s)?"reject":"safe",o=new Set(["--search-zip","-z"]),i=["--pre","--hostname-bin"],c=["-f","--file","--ignore-file"],u=t=>{for(const e of t.slice(1)){if(o.has(e))return"reject";for(const t of i)if(e===t||e.startsWith(`${t}=`))return"reject"}return n(t,c)?"reject":"safe"},a=t=>{for(const e of t.slice(1))if("--output"===e||e.startsWith("--output=")||e.startsWith("-o"))return"reject";return"safe"},l=/^(\d+,)?\d+p$/,h=t=>{const e=t[2];return t.length<=4&&"-n"===t[1]&&void 0!==e&&l.test(e)?"safe":"reject"};export const FLAG_AUDITORS={find:r,grep:f,rg:u,base64:a,sed:h};export function auditFlags(t,e){const r=FLAG_AUDITORS[t];return r?r(e):"safe"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=new Set(["status","log","diff","show","branch"]),e=new Set(["-C","-c","-p","--paginate","--config-env","--exec-path","--git-dir","--namespace","--super-prefix","--work-tree"]),r=["--config-env=","--exec-path=","--git-dir=","--namespace=","--super-prefix=","--work-tree="];function n(t){return!!e.has(t)||(!!((t.startsWith("-C")||t.startsWith("-c"))&&t.length>2)||r.some(e=>t.startsWith(e)))}function s(t){return"--output"===t||t.startsWith("--output=")||"--ext-diff"===t||"--textconv"===t||"--exec"===t||t.startsWith("--exec=")}const i=new Set(["--list","-l","--show-current","-a","--all","-r","--remotes","-v","-vv","--verbose"]);function o(t){for(const e of t)if(!i.has(e)&&!e.startsWith("--format="))return"reject";return"safe"}export function auditGit(e){const r=e.slice(1);let i=0;for(;i<r.length;){const t=r[i];if(void 0===t)return"reject";if(n(t))return"reject";if(!t.startsWith("-"))break;i+=1}const c=r[i];if(void 0===c||!t.has(c))return"reject";const a=r.slice(i+1);for(const t of a)if(s(t))return"reject";return"branch"===c?o(a):"safe"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{classifyScript as r}from"./compound.js";import{executableLookupKey as e}from"./lookup-key.js";import{tokenizeScript as o}from"./tokenize.js";const n=new Set(["bash","sh","zsh"]),s=new Set(["-c","-lc","-lic"]);export function unwrapShellWrapper(r,t){const l=o(r);if(null===l||3!==l.length)return r;const[p,a,i]=l;return"word"===p?.kind&&"word"===a?.kind&&"word"===i?.kind&&n.has(e(p.value,t))&&s.has(a.value)?i.value:r}export const ruleBasedClassifier={classify(e){const o=process.platform;return r(unwrapShellWrapper(e,o),o)}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executableLookupKey(argv0: string, platform: NodeJS.Platform): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=[".exe",".cmd",".bat",".com"];function t(e){const t=e.replace(/\\/g,"/"),n=t.endsWith("/")?t.slice(0,-1):t,o=n.lastIndexOf("/");return-1===o?n:n.slice(o+1)}export function executableLookupKey(n,o){const c=t(n);if("win32"!==o)return c;const r=c.toLowerCase();for(const t of e)if(r.endsWith(t))return r.slice(0,-t.length);return r}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=new Set(["echo","expr","false","true","id","pwd","seq","tr","uname","whoami","which"]),e=new Set(["grep","rg"]);export function isEscapingPathArg(t){return!(!t.startsWith("/")&&!t.startsWith("~"))||t.split("/").includes("..")}export function auditPathArgs(r,s){if(t.has(r))return"safe";let i=!e.has(r);for(const t of s.slice(1))if(!t.startsWith("-"))if(i){if(isEscapingPathArg(t))return"reject"}else i=!0;return"safe"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isSafeExecutable(key: string, platform: NodeJS.Platform): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=["cat","cd","cut","echo","expr","false","grep","head","id","ls","nl","paste","pwd","rev","seq","stat","tail","tr","true","uname","uniq","wc","which","whoami"],t=["numfmt","tac"],a=new Set(e),n=new Set(t);export function isSafeExecutable(e,t){return!!a.has(e)||"linux"===t&&n.has(e)}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WordLexeme {
|
|
2
|
+
readonly kind: "word";
|
|
3
|
+
readonly value: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OperatorLexeme {
|
|
6
|
+
readonly kind: "op";
|
|
7
|
+
readonly value: "&&" | "||" | ";" | "|";
|
|
8
|
+
}
|
|
9
|
+
export type Lexeme = WordLexeme | OperatorLexeme;
|
|
10
|
+
export declare function tokenizeScript(script: string): readonly Lexeme[] | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function tokenizeScript(n){const i=[];let e="",u=!1,o=0;const t=()=>{u&&(i.push({kind:"word",value:e}),e="",u=!1)};for(;o<n.length;){const l=n[o];if("'"===l){const i=n.indexOf("'",o+1);if(-1===i)return null;e+=n.slice(o+1,i),u=!0,o=i+1;continue}if('"'===l){const i=n.indexOf('"',o+1);if(-1===i)return null;e+=n.slice(o+1,i),u=!0,o=i+1;continue}if(" "!==l&&"\t"!==l){if("\n"===l||"\r"===l){t();const n=i[i.length-1];void 0!==n&&"word"===n.kind&&i.push({kind:"op",value:";"}),o+=1;continue}if("&"===l){if("&"===n[o+1]){t(),i.push({kind:"op",value:"&&"}),o+=2;continue}return null}"|"!==l?";"!==l?(e+=l??"",u=!0,o+=1):(t(),i.push({kind:"op",value:";"}),o+=1):(t(),"|"===n[o+1]?(i.push({kind:"op",value:"||"}),o+=2):(i.push({kind:"op",value:"|"}),o+=1))}else t(),o+=1}return t(),i}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CLEAN_EXEC_ENV={NO_COLOR:"1",TERM:"dumb",PAGER:"cat",GIT_PAGER:"cat",LANG:"C.UTF-8"};export function withCleanEnv(E){return{...E,...CLEAN_EXEC_ENV}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type BashExecResult } from "./format-result.ts";
|
|
3
|
+
import type { ShellProfile } from "./profile.ts";
|
|
4
|
+
interface RunBashCommandDeps {
|
|
5
|
+
readonly spawn: typeof spawn;
|
|
6
|
+
readonly killTreeDeadlineMs: number;
|
|
7
|
+
readonly rootKillSettleTimeoutMs: number;
|
|
8
|
+
readonly ioDrainTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export type BashStreamName = "stdout" | "stderr";
|
|
11
|
+
export interface RunBashOptions {
|
|
12
|
+
readonly command: string;
|
|
13
|
+
readonly workdir: string;
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
readonly maxCaptureBytes: number;
|
|
16
|
+
readonly profile: ShellProfile;
|
|
17
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
18
|
+
readonly abortSignal?: AbortSignal;
|
|
19
|
+
readonly onDelta?: (stream: BashStreamName, delta: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function runBashCommand(options: RunBashOptions, deps?: RunBashCommandDeps): Promise<BashExecResult>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +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))})}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NormalizedToolResult } from "../tool-bridge/normalize-result.ts";
|
|
2
|
+
import type { CapturedStream } from "./output-buffer.ts";
|
|
3
|
+
export declare const EXEC_TIMEOUT_EXIT_CODE = 124;
|
|
4
|
+
export declare const EXIT_CODE_SIGNAL_BASE = 128;
|
|
5
|
+
export interface BashExecResult {
|
|
6
|
+
readonly exitCode: number;
|
|
7
|
+
readonly timedOut: boolean;
|
|
8
|
+
readonly timeoutMs: number;
|
|
9
|
+
readonly wallTimeMs: number;
|
|
10
|
+
readonly stdout: CapturedStream;
|
|
11
|
+
readonly stderr: CapturedStream;
|
|
12
|
+
readonly spawnError?: string;
|
|
13
|
+
readonly terminationError?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NormalizeExitCodeParams {
|
|
16
|
+
readonly timedOut: boolean;
|
|
17
|
+
readonly code: number | null;
|
|
18
|
+
readonly signalNumber: number | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare function normalizeExitCode(params: NormalizeExitCodeParams): number;
|
|
21
|
+
export interface FormatBashResultInput {
|
|
22
|
+
readonly result: BashExecResult;
|
|
23
|
+
readonly maxModelOutputChars: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function formatBashResult(input: FormatBashResultInput): NormalizedToolResult;
|
|
@@ -0,0 +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}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_KILL_GRACE_MS=150;export function killProcessGroup(o,r){if(void 0!==o)try{process.kill(-o,r)}catch{}}export function escalateKillGroup(o,r=150){return killProcessGroup(o,"SIGTERM"),setTimeout(()=>killProcessGroup(o,"SIGKILL"),r)}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CapturedStream {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
readonly totalBytes: number;
|
|
4
|
+
readonly totalLines: number;
|
|
5
|
+
readonly truncated: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class OutputSink {
|
|
8
|
+
private readonly maxBytes;
|
|
9
|
+
private readonly chunks;
|
|
10
|
+
private storedBytes;
|
|
11
|
+
private totalBytes;
|
|
12
|
+
private newlineCount;
|
|
13
|
+
private lastByte;
|
|
14
|
+
constructor(maxBytes: number);
|
|
15
|
+
append(chunk: Buffer): void;
|
|
16
|
+
collect(): CapturedStream;
|
|
17
|
+
private countLines;
|
|
18
|
+
}
|
|
19
|
+
export interface StreamBudget {
|
|
20
|
+
readonly stdout: number;
|
|
21
|
+
readonly stderr: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function partitionModelBudget(total: number, stderrChars: number): StreamBudget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=10;export class OutputSink{maxBytes;chunks=[];storedBytes=0;totalBytes=0;newlineCount=0;lastByte;constructor(t){this.maxBytes=t}append(s){if(0===s.length)return;this.totalBytes+=s.length;for(const e of s)e===t&&(this.newlineCount+=1);this.lastByte=s[s.length-1];const e=this.maxBytes-this.storedBytes;e<=0||(s.length<=e?(this.chunks.push(s),this.storedBytes+=s.length):(this.chunks.push(s.subarray(0,e)),this.storedBytes+=e))}collect(){return{text:Buffer.concat(this.chunks,this.storedBytes).toString("utf8"),totalBytes:this.totalBytes,totalLines:this.countLines(),truncated:this.storedBytes<this.totalBytes}}countLines(){if(0===this.totalBytes)return 0;const s=this.lastByte===t?0:1;return this.newlineCount+s}}export function partitionModelBudget(t,s){const e=Math.floor(2*t/3),n=Math.min(s,e);return{stdout:t-n,stderr:n}}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn, spawnSync, type SpawnOptions } from "node:child_process";
|
|
2
|
+
import type { CommandClassification } from "../types/command-classification.ts";
|
|
3
|
+
export declare const SHELL_PROFILE_IDS: readonly ["posix", "powershell"];
|
|
4
|
+
export type ShellProfileId = (typeof SHELL_PROFILE_IDS)[number];
|
|
5
|
+
export declare const SHELL_TOOL_NAMES: readonly ["bash", "powershell"];
|
|
6
|
+
export type ShellToolName = (typeof SHELL_TOOL_NAMES)[number];
|
|
7
|
+
export interface ShellSpawnSpec {
|
|
8
|
+
readonly file: string;
|
|
9
|
+
readonly args: readonly string[];
|
|
10
|
+
readonly options: SpawnOptions;
|
|
11
|
+
}
|
|
12
|
+
export interface ShellKillOptions {
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
export interface ShellProfile {
|
|
16
|
+
readonly id: ShellProfileId;
|
|
17
|
+
readonly toolName: ShellToolName;
|
|
18
|
+
readonly supportsSessionExec: boolean;
|
|
19
|
+
readonly supportsSafeCommandClassification: boolean;
|
|
20
|
+
readonly waitForTreeKillAfterRootExit?: boolean;
|
|
21
|
+
buildSpawn(command: string, workdir: string, env: NodeJS.ProcessEnv): ShellSpawnSpec;
|
|
22
|
+
classify(command: string, workdir: string): CommandClassification;
|
|
23
|
+
killTree(pid: number | undefined, intent: "interrupt" | "terminate", options?: ShellKillOptions): Promise<void>;
|
|
24
|
+
systemPromptHints(): readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ShellProfileResolutionDeps {
|
|
27
|
+
readonly platform: NodeJS.Platform;
|
|
28
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
29
|
+
readonly fileExists?: (path: string) => boolean;
|
|
30
|
+
readonly spawnSync?: typeof spawnSync;
|
|
31
|
+
readonly spawn?: typeof spawn;
|
|
32
|
+
readonly now?: () => number;
|
|
33
|
+
readonly taskkillTimeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export type ShellProfileResolutionResult = {
|
|
36
|
+
readonly supported: true;
|
|
37
|
+
readonly profile: ShellProfile;
|
|
38
|
+
} | {
|
|
39
|
+
readonly supported: false;
|
|
40
|
+
readonly reason: "pwsh-not-found" | "pwsh-version-unsupported";
|
|
41
|
+
};
|
|
42
|
+
export declare function buildPowerShellEncodedCommand(command: string): string;
|
|
43
|
+
export declare function resolveShellProfile(deps: ShellProfileResolutionDeps): ShellProfileResolutionResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as e,spawnSync as o}from"node:child_process";import{existsSync as t}from"node:fs";import{win32 as r}from"node:path";import{performance as n}from"node:perf_hooks";import{unknownCommandClassifier as i}from"../types/command-classification.js";import{ruleBasedClassifier as s}from"./classifier/index.js";import{DEFAULT_KILL_GRACE_MS as l,killProcessGroup as a}from"./kill.js";import{resolveUserShell as c}from"./shell.js";export const SHELL_PROFILE_IDS=["posix","powershell"];export const SHELL_TOOL_NAMES=["bash","powershell"];const u=["-NoLogo","-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-EncodedCommand"],f=32767,d=1024,p=5e3,m=2e3,w=128,S=["PowerShell","7","pwsh.exe"],h=["System32","taskkill.exe"];function g(e,o){return o?.aborted?Promise.resolve(!1):new Promise(t=>{let r=!1;const n=e=>{r||(r=!0,clearTimeout(s),o?.removeEventListener("abort",i),t(e))},i=()=>n(!1),s=setTimeout(()=>n(!0),e);o?.addEventListener("abort",i,{once:!0})})}function E(e){const o=e.fileExists??t;return{id:"posix",toolName:"bash",supportsSessionExec:!0,supportsSafeCommandClassification:!0,waitForTreeKillAfterRootExit:!1,buildSpawn:(t,r,n)=>({file:c({platform:e.platform,env:e.env,fileExists:o}),args:["-c",t],options:{cwd:r,detached:!0,stdio:["ignore","pipe","pipe"],env:n}}),classify:(e,o)=>s.classify(e,o),async killTree(e,o,t={}){t.signal?.aborted||("interrupt"!==o?(a(e,"SIGTERM"),await g(l,t.signal)&&a(e,"SIGKILL")):a(e,"SIGINT"))},systemPromptHints:()=>["当前 shell 后端是 POSIX shell;优先使用 macOS/Linux 常见命令语法。","过滤和预览输出时可使用 grep、sed、head、tail 等 POSIX 工具。"]}}function v(e,o,t,r,n={}){return void 0===e||n.signal?.aborted?Promise.resolve():void 0===o?Promise.reject(new Error("无法从绝对 SystemRoot 路径解析 taskkill.exe,不能安全清理进程树")):new Promise((i,s)=>{let l,a=!1;const c=e=>{a||(a=!0,clearTimeout(f),n.signal?.removeEventListener("abort",u),e?s(e):i())},u=()=>{try{l.kill()}catch{}try{l.unref()}catch{}c()},f=setTimeout(()=>{try{l.kill()}catch{}try{l.unref()}catch{}c(new Error(`taskkill.exe 在 ${String(r)}ms 内未结束`))},r);try{l=t(o,["/PID",String(e),"/T","/F"],{stdio:"ignore",windowsHide:!0})}catch(e){return void c(new Error(`无法启动 taskkill.exe: ${x(e)}`,{cause:e}))}l.once("error",e=>{c(new Error(`无法启动 taskkill.exe: ${e.message}`,{cause:e}))}),l.once("close",(e,o)=>{if(0===e||e===w)return void c();const t=null===e?`signal=${o??"unknown"}`:`exitCode=${String(e)}`;c(new Error(`taskkill.exe 执行失败: ${t}`))}),n.signal?.addEventListener("abort",u,{once:!0}),n.signal?.aborted&&u()})}function x(e){return e instanceof Error?e.message:String(e)}export function buildPowerShellEncodedCommand(e){const o=["[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","$OutputEncoding = [Console]::OutputEncoding","$ErrorActionPreference = 'Stop'","try {",e," if (Test-Path variable:LASTEXITCODE) { exit $LASTEXITCODE }","} catch {"," [Console]::Error.WriteLine([string]$_)"," exit 1","}"].join("\n");return Buffer.from(o,"utf16le").toString("base64")}function P(e,o){return[e,...o].reduce((e,o)=>e+o.length+1,0)}function b(e,o){const t=buildPowerShellEncodedCommand(e),r=[...u,t],n=P(o,r)+d;if(n>f)throw new Error(`PowerShell 命令过长:编码后命令行约 ${String(n)} 字符,超过 Windows CreateProcess 上限 ${String(f)};请拆分命令或改为执行脚本文件。`);return r}function k(e,o){const t=o.toLowerCase();for(const[o,r]of Object.entries(e))if(o.toLowerCase()===t&&void 0!==r&&r.length>0)return r}function y(e){const o=e.trim();return o.startsWith('"')&&o.endsWith('"')?o.slice(1,-1).trim():o}function C(e){return r.isAbsolute(e)&&r.parse(e).root.length>1}function T(e){const o=[],t=new Set,n=e=>{if(!C(e))return;const n=r.normalize(e).toLowerCase();t.has(n)||(t.add(n),o.push(r.normalize(e)))},i=k(e.env,"Path");for(const e of i?.split(r.delimiter)??[]){const o=y(e);C(o)&&n(r.join(o,"pwsh.exe"))}const s=[k(e.env,"ProgramFiles"),k(e.env,"ProgramW6432"),k(e.env,"ProgramFiles(x86)")];for(const e of s){if(void 0===e)continue;const o=y(e);C(o)&&n(r.join(o,...S))}return o}function L(e){const o=k(e,"SystemRoot");if(void 0===o)return;const t=y(o);return C(t)?r.join(t,...h):void 0}function I(e,o,t){const r=o(e,["-NoLogo","-NoProfile","-NonInteractive","-Command","$PSVersionTable.PSVersion.Major"],{encoding:"utf8",timeout:t,windowsHide:!0});if(r.error||0!==r.status)return;const n=`${r.stdout??""}`.trim(),i=Number.parseInt(n,10);return Number.isFinite(i)?i:void 0}function $(e,o){const r=e.fileExists??t,i=e.now??(()=>n.now()),s=i()+p;let l=!1;for(const t of T(e)){if(s-i()<=0)break;if(!r(t))continue;const e=Math.ceil(s-i());if(e<=0)break;const n=I(t,o,e);if(void 0!==n){if(n>=7)return{supported:!0,executable:t};l=!0}}return{supported:!1,reason:l?"pwsh-version-unsupported":"pwsh-not-found"}}function O(t){const r=$(t,t.spawnSync??o);if(!r.supported)return r;const n=t.spawn??e,s=L(t.env),l=t.taskkillTimeoutMs??m;return{supported:!0,profile:{id:"powershell",toolName:"powershell",supportsSessionExec:!1,supportsSafeCommandClassification:!1,waitForTreeKillAfterRootExit:!0,buildSpawn:(e,o,t)=>({file:r.executable,args:b(e,r.executable),options:{cwd:o,detached:!1,stdio:["ignore","pipe","pipe"],env:t,windowsHide:!0}}),classify:(e,o)=>i.classify(e,o),async killTree(e,o,t={}){await v(e,s,n,l,t)},systemPromptHints:()=>["当前 shell 后端是 PowerShell 7;请使用 PowerShell 语法,例如 Get-ChildItem、Get-Content、Select-String。","过滤和预览输出时优先使用 Select-String、Select-Object -First、Get-Content -TotalCount。"]}}}export function resolveShellProfile(e){return"win32"===e.platform?O(e):{supported:!0,profile:E(e)}}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DrainResult {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
readonly omitted: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class HeadTailBuffer {
|
|
6
|
+
private head;
|
|
7
|
+
private tailChunks;
|
|
8
|
+
private tailLength;
|
|
9
|
+
private omittedMiddle;
|
|
10
|
+
private readonly headCapacity;
|
|
11
|
+
private readonly tailCapacity;
|
|
12
|
+
constructor(capacity: number);
|
|
13
|
+
append(text: string): void;
|
|
14
|
+
private trimTail;
|
|
15
|
+
hasPending(): boolean;
|
|
16
|
+
drain(maxChars: number): DrainResult;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{truncateMiddle as t}from"../truncate.js";export class HeadTailBuffer{head="";tailChunks=[];tailLength=0;omittedMiddle=0;headCapacity;tailCapacity;constructor(t){this.headCapacity=Math.ceil(t/2),this.tailCapacity=Math.max(t-this.headCapacity,1)}append(t){if(0===t.length)return;let i=t;if(0===this.tailLength&&this.head.length<this.headCapacity){const t=this.headCapacity-this.head.length;this.head+=i.slice(0,t),i=i.slice(t)}0!==i.length&&(this.tailChunks.push(i),this.tailLength+=i.length,this.trimTail())}trimTail(){for(;this.tailLength>this.tailCapacity&&this.tailChunks.length>1;){const t=this.tailChunks.shift()??"";this.tailLength-=t.length,this.omittedMiddle+=t.length}if(this.tailLength>this.tailCapacity&&1===this.tailChunks.length){const t=this.tailChunks[0]??"",i=t.slice(t.length-this.tailCapacity);this.omittedMiddle+=t.length-i.length,this.tailChunks=[i],this.tailLength=i.length}}hasPending(){return this.head.length>0||this.tailChunks.length>0}drain(i){const h=this.tailChunks.join(""),a=this.omittedMiddle,e=a>0?`${this.head}\n…${String(a)} chars truncated…\n${h}`:this.head+h;this.head="",this.tailChunks=[],this.tailLength=0,this.omittedMiddle=0;const s=t(e,i);return{text:s.text,omitted:a+s.removed}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as e}from"node:child_process";import{constants as o}from"node:os";import{performance as t}from"node:perf_hooks";import{normalizeExitCode as n}from"../format-result.js";import{HeadTailBuffer as r}from"./head-tail-buffer.js";class Gate{opened=!1;waiters=[];open(){if(this.opened)return;this.opened=!0;const e=this.waiters;this.waiters=[];for(const o of e)o()}wait(){return this.opened?Promise.resolve():new Promise(e=>{this.waiters.push(e)})}}export function spawnSession(i){const s=i.profile.buildSpawn(i.command,i.workdir,i.env),a=e(s.file,s.args,s.options),d=new r(i.bufferCapacity),p=t.now(),l=new Gate,f=new Gate,m={id:i.id,profile:i.profile,child:a,buffer:d,startedAt:p,exitCode:void 0,lastUsedAt:p,onDelta:i.onDelta,waitExit:()=>l.wait(),waitClose:()=>f.wait()},u=(e,o)=>{if(null===e)return;const t=new TextDecoder("utf-8");e.on("data",e=>{const n=t.decode(e,{stream:!0});n.length>0&&(d.append(n),m.onDelta?.(o,n))})};return u(a.stdout,"stdout"),u(a.stderr,"stderr"),a.on("exit",(e,t)=>{const r=null!==t?o.signals[t]:void 0;m.exitCode=n({timedOut:!1,code:e,signalNumber:r}),l.open()}),a.on("close",()=>{f.open()}),a.on("error",e=>{d.append(`无法启动进程: ${e.message}\n`),m.exitCode=m.exitCode??1,l.open(),f.open()}),m}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { BashStreamName } from "../exec.ts";
|
|
2
|
+
import type { ShellProfile } from "../profile.ts";
|
|
3
|
+
import type { ManagedSession } from "./types.ts";
|
|
4
|
+
export declare class SessionCapError extends Error {
|
|
5
|
+
constructor(maxSessions: number);
|
|
6
|
+
}
|
|
7
|
+
export interface SessionManagerOptions {
|
|
8
|
+
readonly maxSessions: number;
|
|
9
|
+
readonly profile: ShellProfile;
|
|
10
|
+
readonly env: NodeJS.ProcessEnv;
|
|
11
|
+
readonly bufferCapacity: number;
|
|
12
|
+
readonly generateId?: () => number;
|
|
13
|
+
}
|
|
14
|
+
export interface SpawnRequest {
|
|
15
|
+
readonly command: string;
|
|
16
|
+
readonly workdir: string;
|
|
17
|
+
readonly onDelta?: (stream: BashStreamName, delta: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare class SessionManager {
|
|
20
|
+
private readonly sessions;
|
|
21
|
+
private readonly options;
|
|
22
|
+
constructor(options: SessionManagerOptions);
|
|
23
|
+
spawn(request: SpawnRequest): ManagedSession;
|
|
24
|
+
get(id: number): ManagedSession | undefined;
|
|
25
|
+
delete(id: number): void;
|
|
26
|
+
size(): number;
|
|
27
|
+
terminateAll(): void;
|
|
28
|
+
private evictExited;
|
|
29
|
+
private allocateId;
|
|
30
|
+
}
|
|
@@ -0,0 +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")}}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
import type { BashStreamName } from "../exec.ts";
|
|
3
|
+
import type { ShellProfile } from "../profile.ts";
|
|
4
|
+
import type { HeadTailBuffer } from "./head-tail-buffer.ts";
|
|
5
|
+
export type SessionDeltaHandler = (stream: BashStreamName, delta: string) => void;
|
|
6
|
+
export interface ManagedSession {
|
|
7
|
+
readonly id: number;
|
|
8
|
+
readonly profile: ShellProfile;
|
|
9
|
+
readonly child: ChildProcess;
|
|
10
|
+
readonly buffer: HeadTailBuffer;
|
|
11
|
+
readonly startedAt: number;
|
|
12
|
+
exitCode: number | undefined;
|
|
13
|
+
lastUsedAt: number;
|
|
14
|
+
onDelta: SessionDeltaHandler | undefined;
|
|
15
|
+
waitExit(): Promise<void>;
|
|
16
|
+
waitClose(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export type SessionPollResult = {
|
|
19
|
+
readonly kind: "exited";
|
|
20
|
+
readonly output: string;
|
|
21
|
+
readonly omitted: number;
|
|
22
|
+
readonly wallTimeMs: number;
|
|
23
|
+
readonly exitCode: number;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "running";
|
|
26
|
+
readonly output: string;
|
|
27
|
+
readonly omitted: number;
|
|
28
|
+
readonly wallTimeMs: number;
|
|
29
|
+
readonly sessionId: number;
|
|
30
|
+
};
|
|
31
|
+
export interface SpawnSessionInput {
|
|
32
|
+
readonly id: number;
|
|
33
|
+
readonly command: string;
|
|
34
|
+
readonly workdir: string;
|
|
35
|
+
readonly profile: ShellProfile;
|
|
36
|
+
readonly env: NodeJS.ProcessEnv;
|
|
37
|
+
readonly bufferCapacity: number;
|
|
38
|
+
readonly onDelta?: SessionDeltaHandler;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{performance as e}from"node:perf_hooks";const t=250;function i(e){let t;return{promise:new Promise(i=>{t=setTimeout(i,e)}),cancel:()=>{t&&clearTimeout(t)}}}export async function pollUntilDeadline(o,n,s){const d=n-e.now();if(void 0===o.exitCode&&d>0){const e=i(d);await Promise.race([o.waitExit(),e.promise]),e.cancel()}if(void 0!==o.exitCode){const e=i(t);await Promise.race([o.waitClose(),e.promise]),e.cancel()}o.lastUsedAt=e.now();const r=o.buffer.drain(s),a=e.now()-o.startedAt;return void 0!==o.exitCode?{kind:"exited",output:r.text,omitted:r.omitted,wallTimeMs:a,exitCode:o.exitCode}:{kind:"running",output:r.text,omitted:r.omitted,wallTimeMs:a,sessionId:o.id}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface ShellResolutionDeps {
|
|
2
|
+
readonly platform: NodeJS.Platform;
|
|
3
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
4
|
+
readonly fileExists: (path: string) => boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveUserShell(deps: ShellResolutionDeps): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=["/bin/zsh","/bin/bash","/bin/sh"],s="/bin/sh";export function resolveUserShell(e){const i=e.env.SHELL;if(void 0!==i&&i.length>0&&e.fileExists(i))return i;for(const s of n)if(e.fileExists(s))return s;return s}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function truncateMiddle(t,e){const n=Array.from(t);if(n.length<=e)return{text:t,truncated:!1,removed:0};if(e<=0)return{text:"",truncated:!0,removed:n.length};const r=Math.ceil(e/2),c=Math.floor(e/2),o=n.length-r-c,i=n.slice(0,r).join(""),a=n.slice(n.length-c).join("");return{text:`${i}\n…${String(o)} chars truncated…\n${a}`,truncated:!0,removed:o}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isWithinWorkdirRoot(root: string, target: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isAbsolute as t,relative as o}from"node:path";export function isWithinWorkdirRoot(r,i){const n=o(r,i);return""===n||!n.startsWith("..")&&!t(n)}
|
|
@@ -4,6 +4,10 @@ import type { SkillLibrary, SkillSummary } from "@roll-agent/core/skills/library
|
|
|
4
4
|
import type { ContextCompactionReason, ContextCompactionStrategy, SessionEvent, SessionTokenUsage } from "../types/events.ts";
|
|
5
5
|
import type { ToolPolicy } from "../types/policy.ts";
|
|
6
6
|
import { type AgentToolSource } from "../tool-bridge/build-tools.ts";
|
|
7
|
+
import { type AgentInstallToolCatalogEntry, type AgentInstallToolOutcome } from "../tool-bridge/agent-install-tool.ts";
|
|
8
|
+
import { type SessionBashSettings } from "../tool-bridge/bash-tool.ts";
|
|
9
|
+
import type { ShellProfile } from "../bash/profile.ts";
|
|
10
|
+
import type { CommandClassifier } from "../types/command-classification.ts";
|
|
7
11
|
export interface SessionCompactionSettings {
|
|
8
12
|
readonly enabled: boolean;
|
|
9
13
|
readonly strategy: ContextCompactionStrategy;
|
|
@@ -27,6 +31,31 @@ export interface AgentSessionOptions {
|
|
|
27
31
|
readonly debugEvents?: boolean;
|
|
28
32
|
readonly systemPrompt?: string;
|
|
29
33
|
readonly skillLibrary?: SkillLibrary;
|
|
34
|
+
readonly bash?: SessionBashSettings;
|
|
35
|
+
readonly bashClassifier?: CommandClassifier;
|
|
36
|
+
readonly bashSession?: AgentSessionBashSession;
|
|
37
|
+
readonly agentInstall?: AgentSessionAgentInstall;
|
|
38
|
+
}
|
|
39
|
+
export interface SessionAgentRefresh {
|
|
40
|
+
readonly source: AgentToolSource;
|
|
41
|
+
readonly skillLibrary?: SkillLibrary;
|
|
42
|
+
readonly systemPrompt: string;
|
|
43
|
+
}
|
|
44
|
+
export interface AgentInstallSessionResult {
|
|
45
|
+
readonly outcome: AgentInstallToolOutcome;
|
|
46
|
+
readonly refresh?: SessionAgentRefresh;
|
|
47
|
+
}
|
|
48
|
+
export interface AgentSessionAgentInstall {
|
|
49
|
+
readonly catalog: readonly AgentInstallToolCatalogEntry[];
|
|
50
|
+
readonly install: (shortName: string, report: (line: string) => void) => Promise<AgentInstallSessionResult>;
|
|
51
|
+
}
|
|
52
|
+
export interface AgentSessionBashSession {
|
|
53
|
+
readonly workdir: string;
|
|
54
|
+
readonly profile: ShellProfile;
|
|
55
|
+
readonly maxSessions: number;
|
|
56
|
+
readonly defaultYieldMs: number;
|
|
57
|
+
readonly maxOutputTokens: number;
|
|
58
|
+
readonly bufferCapacity: number;
|
|
30
59
|
}
|
|
31
60
|
export type SessionSkillSummary = SkillSummary;
|
|
32
61
|
export declare class AgentSession {
|
|
@@ -41,17 +70,24 @@ export declare class AgentSession {
|
|
|
41
70
|
private readonly turnTimeoutMs;
|
|
42
71
|
private providerOptions;
|
|
43
72
|
private readonly debugEvents;
|
|
44
|
-
private readonly
|
|
45
|
-
private
|
|
73
|
+
private readonly policy;
|
|
74
|
+
private systemPrompt;
|
|
75
|
+
private skillSummaries;
|
|
76
|
+
private skillLibrary;
|
|
77
|
+
private skillToolBuilt;
|
|
78
|
+
private readonly toolSourceAgentNames;
|
|
46
79
|
private readonly gate;
|
|
47
|
-
private
|
|
80
|
+
private tools;
|
|
48
81
|
private readonly registry;
|
|
82
|
+
private readonly sessionManager;
|
|
49
83
|
private emit;
|
|
50
84
|
private activeTurn;
|
|
51
85
|
private sessionUsage;
|
|
52
86
|
private lastInputTokens;
|
|
53
87
|
private needsCompaction;
|
|
54
88
|
constructor(options: AgentSessionOptions);
|
|
89
|
+
private buildSkillTools;
|
|
90
|
+
applyAgentRefresh(refresh: SessionAgentRefresh): void;
|
|
55
91
|
send(input: string): AsyncIterable<SessionEvent>;
|
|
56
92
|
compact(reason?: ContextCompactionReason): AsyncIterable<SessionEvent>;
|
|
57
93
|
private runTurn;
|
|
@@ -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{buildSkillToolset as i}from"../tool-bridge/skill-tool.js";import{ToolRegistry as n}from"../tool-bridge/naming.js";import{buildChatSystemPrompt as r}from"./system-prompt.js";import{readIsError as a}from"../tool-bridge/normalize-result.js";import{ApprovalGate as l}from"../approval/approval-gate.js";import{compactMessages as u}from"./compactor.js";import{AsyncEventQueue as p}from"./event-queue.js";function h(e){return e instanceof Error?e.message:String(e)}function c(e){return"object"==typeof e&&null!==e&&"output"in e}function m(e){return c(e)?h(e.output):h(e)}function d(e){const t=m(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function g(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(h(e))}function v(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 T(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function b(e,t){const s=T(e.cachedInputTokens,t.cachedInputTokens),o=T(e.cacheWriteTokens,t.cacheWriteTokens),i=T(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 k(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function y(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function f(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;systemPrompt;skillSummaries;gate=new l;tools;registry;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?f(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.systemPrompt=e.systemPrompt??r(),this.skillSummaries=e.skillLibrary?.list()??[];const t=new n,s=e.skillLibrary?i(e.skillLibrary,t):{},a=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)},t);this.tools={...s,...a.tools},this.registry=a.registry}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new p;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:h(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 p;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:h(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 r=Date.now();let l;try{if(this.debug(o,"turn","start",r,{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",r,{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:h(e)})}}if(i.aborted||i.abortController.signal.aborted)return void o.push({type:"error",stage:"execute",message:"aborted"});l=this.messages.length,this.messages.push({role:"user",content:n}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",r,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}});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}:{},...void 0!==this.turnTimeoutMs?{timeout:{totalMs:this.turnTimeoutMs}}:{}});this.debug(o,"model","streamText returned",r);let p,c,T,x,w,C,I="",N="",S=!1,M=0,W=!1;const A=this.scheduleDebug(o,"model","waiting for first stream event",r,{messages:this.messages.length});try{for await(const e of u.fullStream){switch(W||(W=!0,this.clearDebugTimer(A),this.debug(o,"model","first stream event",r,{part:e.type})),e.type){case"text-delta":if(!S){const t=N+e.text;if(y(t,n)){N=t;break}if(N.length>0){const t=N+e.text;N="",I+=t,o.push({type:"text-delta",delta:t});break}}I+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{S=!0,N="";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=a(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||(C=d(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=m(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||(C=d(e.error));break}case"finish-step":{const t=v(e.usage);c=k(c,t.inputTokens),M+=1,x=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(T=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!S&&N.length>0&&(I+=N,o.push({type:"text-delta",delta:N}),N=""),p=v(e.totalUsage);break;case"error":g(e.error)&&(this.needsCompaction=!0),w=h(e.error),o.push({type:"error",stage:"execute",message:w});break;case"abort":i.aborted=!0}if(void 0!==C)break}}finally{this.clearDebugTimer(A)}if(this.debug(o,"model","fullStream finished",r,{textChars:I.length}),void 0!==w)return this.messages.splice(l),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return this.messages.splice(l),void o.push({type:"error",stage:"execute",message:"aborted"});if(void 0!==C){const e=C,t=0===I.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-l}),this.onPersist?.(this.messages.slice(l)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),p&&(this.sessionUsage=b(this.sessionUsage,p));const s=c??p?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...p?{totalUsage:p}:{},sessionUsage:{...this.sessionUsage},...void 0!==c?{contextInputTokens:c}:{},...void 0!==T?{outputTokensPerSecond:T}:{}})}let R;this.debug(o,"model","awaiting response messages",r);const D=this.scheduleDebug(o,"model","still awaiting response messages",r);try{R=(await u.response).messages}catch(e){return this.clearDebugTimer(D),g(e)&&(this.needsCompaction=!0),this.messages.splice(l),o.push({type:"error",stage:"execute",message:h(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i))}finally{this.clearDebugTimer(D)}this.debug(o,"model","response messages ready",r,{responseMessages:R.length});const E=f(R);this.messages.push(...E),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-l}),this.onPersist?.(this.messages.slice(l)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),p&&(this.sessionUsage=b(this.sessionUsage,p));const P=c??p?.inputTokens;void 0!==P&&(this.lastInputTokens=P);const U=M>=this.maxSteps&&"tool-calls"===x;o.push({type:"message-finish",text:I,...p?{totalUsage:p}:{},sessionUsage:{...this.sessionUsage},...void 0!==c?{contextInputTokens:c}:{},...void 0!==T?{outputTokensPerSecond:T}:{},...U?{stoppedAtStepLimit:!0}:{}})}catch(e){void 0!==l&&this.messages.splice(l),g(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",r,{message:h(e)}),o.push({type:"error",stage:"execute",message:h(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:h(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 e.push({type:"error",stage:"plan",message:"aborted"});if(!i)return void e.push({type:"context-compacted",reason:t,strategy:n,removed:0,kept:this.messages.length});const r=this.lastInputTokens;let a=i.strategy;const l=s?.abortController.signal;let p;try{p=await u({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:h(t)}),a="truncate",p=await u({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 c=p.removed>0||p.truncatedTools>0;c&&(this.onReplace?.(p.messages),this.messages.splice(0,this.messages.length,...p.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:a,removed:p.removed,kept:p.kept,...p.truncatedTools>0?{truncatedTools:p.truncatedTools}:{},...void 0!==r?{beforeInputTokens:r}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:a,removed:p.removed,kept:p.kept,truncatedTools:p.truncatedTools,progressed:c})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:h(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()}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 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,12 +1,15 @@
|
|
|
1
1
|
import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
|
|
2
2
|
import { McpClientManager } from "@roll-agent/core/mcp/client-manager";
|
|
3
|
+
import { resolveAgentCatalog } from "@roll-agent/core/registry/catalog-discovery";
|
|
4
|
+
import { installAgent } from "@roll-agent/core/registry/install";
|
|
3
5
|
import type { RollConfig } from "@roll-agent/core/config/schema";
|
|
4
6
|
import type { RegisteredAgent } from "@roll-agent/core/types/agent";
|
|
5
7
|
import { type SkillLibrary } from "@roll-agent/core/skills/library";
|
|
6
8
|
import type { AgentToolSource } from "../tool-bridge/build-tools.ts";
|
|
7
9
|
import type { ToolPolicy } from "../types/policy.ts";
|
|
8
10
|
import type { ThreadStore } from "../store/thread-store.ts";
|
|
9
|
-
import { AgentSession } from "./agent-session.ts";
|
|
11
|
+
import { AgentSession, type SessionAgentRefresh } from "./agent-session.ts";
|
|
12
|
+
import { resolveShellProfile, type ShellProfile } from "../bash/profile.ts";
|
|
10
13
|
export type EnsureAgentReady = (agent: RegisteredAgent, env: Readonly<Record<string, string>> | undefined) => Promise<void>;
|
|
11
14
|
export interface ConversationEngineOptions {
|
|
12
15
|
readonly config: RollConfig;
|
|
@@ -23,6 +26,11 @@ export interface ConversationEngineOptions {
|
|
|
23
26
|
readonly onAgentBootstrapIssue?: (issue: AgentBootstrapIssue) => void;
|
|
24
27
|
readonly skillLibrary?: SkillLibrary | null;
|
|
25
28
|
readonly onSkillLibraryIssue?: (message: string) => void;
|
|
29
|
+
readonly sessionExecEnabled?: boolean;
|
|
30
|
+
readonly shellProfile?: ShellProfile | null;
|
|
31
|
+
readonly resolveShellProfileFn?: typeof resolveShellProfile;
|
|
32
|
+
readonly installAgentFn?: typeof installAgent;
|
|
33
|
+
readonly resolveCatalogFn?: typeof resolveAgentCatalog;
|
|
26
34
|
}
|
|
27
35
|
export interface CreateSessionInput {
|
|
28
36
|
readonly title?: string;
|
|
@@ -51,13 +59,35 @@ export declare class ConversationEngine {
|
|
|
51
59
|
private readonly explicitSkillLibrary;
|
|
52
60
|
private readonly onSkillLibraryIssue;
|
|
53
61
|
private readonly onAgentBootstrapIssue;
|
|
62
|
+
private readonly sessionExecEnabled;
|
|
63
|
+
private readonly explicitShellProfile;
|
|
64
|
+
private readonly resolveShellProfileFn;
|
|
65
|
+
private readonly installAgentFn;
|
|
66
|
+
private readonly resolveCatalogFn;
|
|
54
67
|
private ready;
|
|
68
|
+
private refreshChain;
|
|
69
|
+
private resolvedCatalog;
|
|
70
|
+
private shellProfileResolution;
|
|
71
|
+
private shellUnsupportedWarned;
|
|
72
|
+
private shellSessionUnsupportedWarned;
|
|
55
73
|
constructor(options: ConversationEngineOptions);
|
|
56
74
|
createSession(input?: CreateSessionInput): Promise<AgentSession>;
|
|
57
75
|
resumeSession(threadId: string): Promise<AgentSession>;
|
|
76
|
+
private resolveRuntimeShellProfile;
|
|
77
|
+
private resolveShellSettings;
|
|
78
|
+
private resolveSessionExecSettings;
|
|
58
79
|
private buildSession;
|
|
59
80
|
private ensureReady;
|
|
60
81
|
private bootstrap;
|
|
82
|
+
private connectAgentSource;
|
|
83
|
+
prepareAgentRefresh(agent: RegisteredAgent): Promise<SessionAgentRefresh>;
|
|
84
|
+
private runAgentRefresh;
|
|
85
|
+
private composeSystemPrompt;
|
|
86
|
+
private agentInstallEnabled;
|
|
87
|
+
private currentCatalog;
|
|
88
|
+
private resolveAgentOnboardingInfo;
|
|
89
|
+
private resolveAgentInstallBinding;
|
|
90
|
+
private installCatalogAgent;
|
|
61
91
|
private resolveSkillLibrary;
|
|
62
92
|
private resolveModel;
|
|
63
93
|
private resolveProviderName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as i}from"@roll-agent/core/llm/providers";import{AgentStore as s}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as r}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as o,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{createSkillLibrary as d}from"@roll-agent/core/skills/library";import{AgentSession as p}from"./agent-session.js";import{resolveContextWindow as h}from"./context-window.js";import{buildChatSystemPrompt as u}from"./system-prompt.js";const m=80;function g(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const i={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(i.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(i.destructiveHint=t.destructiveHint),void 0===i.readOnlyHint&&void 0===i.destructiveHint?void 0:i}async function y(e,t,i){"core-managed"===e.runtime.ownership&&(void 0===o(t,e.skill.name)&&n(e,t,i),await l(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;onAgentBootstrapIssue;ready;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)=>y(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue}async createSession(t={}){const i=await this.ensureReady(),s=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(i,s,[])}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))}buildSession(e,t,i){const s=this.store,r=h(this.resolveModelName(),this.config.runtime.contextWindow),o=e.skillLibrary?.list()??[],n=o.length>0?e.skillLibrary:void 0,l=u({skills:o});return new p({id:t,model:e.model,sources:e.sources,systemPrompt:l,...n?{skillLibrary:n}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==r?{contextWindow:r}:{},...this.policy?{policy:this.policy}:{},initialMessages:i,...s?{onPersist:e=>s.appendMessages(t,e),onReplace:e=>s.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 s(this.config.agents.dataDir).list(),i=[];for(const s of t)try{const t=r(s),o=c(this.config,s.skill.name);await this.ensureAgentReady(s,o);const n=await this.clientManager.connect(s.skill.name,t,s.installPath,{samplingModel:e,...o?{env:o}:{}}),l=(await n.listTools()).tools,d=a(l).map((e,t)=>({tool:e,annotations:g(l[t])}));i.push({agentName:s.skill.name,client:n,tools:d})}catch(e){this.onAgentBootstrapIssue?.({agentName:s.skill.name,message:e instanceof Error?e.message:String(e)})}const o=this.resolveSkillLibrary(t);return{model:e,sources:i,...o?{skillLibrary:o}:{}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:d({agents:e,extraDirs:this.config.skills.dirs,...this.onSkillLibraryIssue?{onIssue:this.onSkillLibraryIssue}:{}})}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),s=this.config.llm.providers[e];if(!s)throw new Error(`LLM provider "${e}" 未配置`);return i(e,t,s.apiKey,s.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 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()}}
|
|
@@ -2,8 +2,26 @@ export interface SkillPromptSummary {
|
|
|
2
2
|
readonly name: string;
|
|
3
3
|
readonly description: string;
|
|
4
4
|
}
|
|
5
|
+
export interface SessionExecToolIds {
|
|
6
|
+
readonly command: string;
|
|
7
|
+
readonly poll: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentOnboardingCatalogEntry {
|
|
10
|
+
readonly shortName: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AgentOnboardingPromptInfo {
|
|
14
|
+
readonly installToolId: string;
|
|
15
|
+
readonly catalog: readonly AgentOnboardingCatalogEntry[];
|
|
16
|
+
}
|
|
5
17
|
export interface BuildChatSystemPromptOptions {
|
|
6
18
|
readonly skills?: readonly SkillPromptSummary[];
|
|
7
19
|
readonly skillToolId?: string;
|
|
20
|
+
readonly bashToolId?: string;
|
|
21
|
+
readonly shellToolId?: string;
|
|
22
|
+
readonly shellHints?: readonly string[];
|
|
23
|
+
readonly sessionExecToolIds?: SessionExecToolIds;
|
|
24
|
+
readonly agentCount?: number;
|
|
25
|
+
readonly agentOnboarding?: AgentOnboardingPromptInfo;
|
|
8
26
|
}
|
|
9
27
|
export declare function buildChatSystemPrompt(options?: BuildChatSystemPromptOptions): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SKILL_TOOL_ID as n}from"../tool-bridge/skill-tool.js";const
|
|
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")}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export { compactMessages, findTurnBoundaries, SUMMARY_PREFIX, SUMMARY_ACK, } fro
|
|
|
7
7
|
export type { CompactionInput, CompactionResult } from "./engine/compactor.ts";
|
|
8
8
|
export { buildAgentToolset } from "./tool-bridge/build-tools.ts";
|
|
9
9
|
export type { AgentToolSource, SourceTool, ApprovalRequest, ToolBridgeContext, BuiltToolset, } from "./tool-bridge/build-tools.ts";
|
|
10
|
+
export type { SessionBashSettings } from "./tool-bridge/bash-tool.ts";
|
|
11
|
+
export { type CommandClassifier, type CommandClassification, COMMAND_CLASSIFICATIONS, unknownCommandClassifier, } from "./types/command-classification.ts";
|
|
12
|
+
export { ruleBasedClassifier } from "./bash/classifier/index.ts";
|
|
13
|
+
export { buildPowerShellEncodedCommand, resolveShellProfile, SHELL_PROFILE_IDS, SHELL_TOOL_NAMES, } from "./bash/profile.ts";
|
|
14
|
+
export type { ShellProfile, ShellProfileId, ShellKillOptions, ShellProfileResolutionDeps, ShellProfileResolutionResult, ShellSpawnSpec, ShellToolName, } from "./bash/profile.ts";
|
|
15
|
+
export { buildSessionExecToolset, EXEC_COMMAND_ID, EXEC_POLL_ID, type SessionExecSettings, } from "./tool-bridge/session-exec-tool.ts";
|
|
10
16
|
export { ToolRegistry } from "./tool-bridge/naming.ts";
|
|
11
17
|
export type { ToolRoute } from "./tool-bridge/naming.ts";
|
|
12
18
|
export { normalizeToolResult } from "./tool-bridge/normalize-result.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{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";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ToolSet } from "ai";
|
|
2
|
+
import type { ToolBridgeContext } from "./build-tools.ts";
|
|
3
|
+
import type { ToolRegistry } from "./naming.ts";
|
|
4
|
+
export declare const AGENT_INSTALL_TOOL_AGENT_NAME = "roll";
|
|
5
|
+
export declare const AGENT_INSTALL_TOOL_NAME = "agent_install";
|
|
6
|
+
export declare const AGENT_INSTALL_TOOL_ID = "roll__agent_install";
|
|
7
|
+
export interface AgentInstallToolCatalogEntry {
|
|
8
|
+
readonly shortName: string;
|
|
9
|
+
readonly description: string;
|
|
10
|
+
}
|
|
11
|
+
export type AgentInstallToolOutcome = {
|
|
12
|
+
readonly ok: true;
|
|
13
|
+
readonly agentName: string;
|
|
14
|
+
readonly version?: string;
|
|
15
|
+
readonly missingEnv: readonly string[];
|
|
16
|
+
readonly retryCommand?: string;
|
|
17
|
+
readonly refreshApplied: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
readonly ok: false;
|
|
20
|
+
readonly message: string;
|
|
21
|
+
readonly retryCommand?: string;
|
|
22
|
+
};
|
|
23
|
+
export interface AgentInstallToolDeps {
|
|
24
|
+
readonly catalog: readonly AgentInstallToolCatalogEntry[];
|
|
25
|
+
readonly install: (shortName: string, report: (line: string) => void) => Promise<AgentInstallToolOutcome>;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildAgentInstallToolset(deps: AgentInstallToolDeps, registry: ToolRegistry, ctx: ToolBridgeContext): ToolSet;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{tool as n}from"ai";import{z as t}from"zod";export const AGENT_INSTALL_TOOL_AGENT_NAME="roll";export const AGENT_INSTALL_TOOL_NAME="agent_install";export const AGENT_INSTALL_TOOL_ID="roll__agent_install";function e(n,t){const e=t.length>0?`\n\n安装日志:\n${t.join("\n")}`:"";if(!n.ok){const t=n.retryCommand?`\n可在终端重试:${n.retryCommand}`:"";return{output:`安装失败:${n.message}${t}${e}`,isError:!0}}const o=n.version?` v${n.version}`:"",r=n.refreshApplied?"新工具将从下一轮对话开始可用。":"当前会话不会自动接入本次安装的版本,请让用户重新运行 roll chat 后使用。",a=n.missingEnv.length>0?`\n缺少必填环境变量:${n.missingEnv.join(", ")}。请让用户在终端运行 \`roll config setup agent ${n.agentName}\` 完成配置。`:"",i=n.retryCommand?`\n浏览器运行时已跳过安装,请让用户在终端补跑:${n.retryCommand}`:"";return{output:`已安装并注册 Agent "${n.agentName}"${o}。${r}${a}${i}${e}`,isError:!1}}export function buildAgentInstallToolset(o,r,a){const i=o.catalog.map(n=>n.shortName),[s,...l]=i;if(void 0===s)return{};const c=r.register("roll","agent_install"),m=t.object({agent:t.enum([s,...l]).describe(`要安装的官方 Agent 短名(可选:${i.join("、")})`)});return{[c]:n({description:`安装并注册一个官方子 Agent(执行 npm install,需要用户确认,可能耗时较长)。可安装:${o.catalog.map(n=>`${n.shortName}(${n.description})`).join(";")}`,inputSchema:m,execute:async n=>{const t=a.policy?.check({agentName:"roll",toolName:"agent_install",input:n,annotations:{destructiveHint:!0}});if("deny"===t?.action)return{output:"策略拒绝执行"+(t.reason?`: ${t.reason}`:""),isError:!0};const r=await a.requestApproval({agentName:"roll",toolName:"agent_install",input:n,reason:`将执行 npm install 并注册子 Agent "${n.agent}"`});if(!r.approved)return{output:"已取消执行"+(r.reason?`: ${r.reason}`:""),isError:!0};const i=[];return e(await o.install(n.agent,n=>i.push(n)),i)}})}}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ToolSet } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { SessionEvent } from "../types/events.ts";
|
|
4
|
+
import { type CommandClassifier } from "../types/command-classification.ts";
|
|
5
|
+
import { runBashCommand } from "../bash/exec.ts";
|
|
6
|
+
import type { ShellProfile } from "../bash/profile.ts";
|
|
7
|
+
import { type ToolBridgeContext } from "./build-tools.ts";
|
|
8
|
+
import { ToolRegistry } from "./naming.ts";
|
|
9
|
+
export declare const BASH_TOOL_AGENT_NAME = "roll";
|
|
10
|
+
export declare const BASH_TOOL_NAME = "bash";
|
|
11
|
+
export declare const BASH_TOOL_ID = "roll__bash";
|
|
12
|
+
export declare const POWERSHELL_TOOL_NAME = "powershell";
|
|
13
|
+
export declare const POWERSHELL_TOOL_ID = "roll__powershell";
|
|
14
|
+
export interface SessionBashSettings {
|
|
15
|
+
readonly workdir: string;
|
|
16
|
+
readonly defaultTimeoutMs: number;
|
|
17
|
+
readonly maxTimeoutMs: number;
|
|
18
|
+
readonly turnTimeoutMs: number;
|
|
19
|
+
readonly maxCaptureBytes: number;
|
|
20
|
+
readonly maxModelOutputChars: number;
|
|
21
|
+
readonly profile: ShellProfile;
|
|
22
|
+
}
|
|
23
|
+
export interface BashToolContext extends ToolBridgeContext {
|
|
24
|
+
readonly emitEvent?: (event: SessionEvent) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface BashToolDeps {
|
|
27
|
+
readonly classifier?: CommandClassifier;
|
|
28
|
+
readonly exec?: typeof runBashCommand;
|
|
29
|
+
}
|
|
30
|
+
declare const bashToolInputSchema: z.ZodObject<{
|
|
31
|
+
command: z.ZodString;
|
|
32
|
+
workdir: z.ZodOptional<z.ZodString>;
|
|
33
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
command: string;
|
|
36
|
+
workdir?: string | undefined;
|
|
37
|
+
timeout_ms?: number | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
command: string;
|
|
40
|
+
workdir?: string | undefined;
|
|
41
|
+
timeout_ms?: number | undefined;
|
|
42
|
+
}>;
|
|
43
|
+
export type BashToolInput = z.infer<typeof bashToolInputSchema>;
|
|
44
|
+
export declare function buildBashToolset(settings: SessionBashSettings, registry: ToolRegistry, ctx: BashToolContext, deps?: BashToolDeps): ToolSet;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolve as o}from"node:path";import{tool as t}from"ai";import{z as r}from"zod";import{CLASSIFICATION_ANNOTATIONS as e}from"../types/command-classification.js";import{runBashCommand as i}from"../bash/exec.js";import{formatBashResult as s}from"../bash/format-result.js";import{isWithinWorkdirRoot as a}from"../bash/workdir.js";import{gateToolCall as n}from"./build-tools.js";import{ToolRegistry as l}from"./naming.js";export const BASH_TOOL_AGENT_NAME="roll";export const BASH_TOOL_NAME="bash";export const BASH_TOOL_ID="roll__bash";export const POWERSHELL_TOOL_NAME="powershell";export const POWERSHELL_TOOL_ID="roll__powershell";const m=256,c=4096,u=r.object({command:r.string().min(1).describe("要执行的 shell 命令(单字符串,由当前 shell 后端执行)"),workdir:r.string().min(1).optional().describe("工作目录绝对路径,默认为 roll chat 当前目录。不要在 command 里用 cd,改用本字段"),timeout_ms:r.number().int().min(1).max(6e5).optional().describe("超时毫秒数。默认 10000,上限受 maxTimeoutMs 与 turnTimeoutMs 约束;长脚本请显式调大")});function p(o,t,r){const e=o.emitEvent;if(!e)return;let i=0;return(o,s)=>{if(i>=m)return;i+=1;const a=s.length>c?s.slice(0,c):s;e({type:"tool-output-delta",toolCallId:t,agentName:"roll",toolName:r,stream:o,delta:a})}}async function d(o,t,r,e){if(o.policy)return n(o,"roll",t,r,e);const i=await o.requestApproval({agentName:"roll",toolName:t,input:r,reason:"shell 命令需确认"});return i.approved?void 0:{output:"已取消执行"+(i.reason?`: ${i.reason}`:""),isError:!0}}export function buildBashToolset(r,n,l,m={}){const c=r.profile.toolName,f=n.register("roll",c),h=m.classifier??r.profile,_=m.exec??i;return{[f]:t({description:"在当前 shell 后端中执行一条命令并返回输出。命令继承 roll 进程的全部环境变量。总是用 workdir 参数设置工作目录,不要用 cd。",inputSchema:u,execute:async(t,i)=>{const n=o(r.workdir,t.workdir??"."),m=Math.min(t.timeout_ms??r.defaultTimeoutMs,r.maxTimeoutMs,r.turnTimeoutMs),u=a(r.workdir,n)?h.classify(t.command,n):"unknown",f=e[u],x={command:t.command,workdir:n,timeout_ms:m},b=await d(l,c,x,f);if(b)return b;const w=p(l,i.toolCallId,c),O=await _({command:t.command,workdir:n,timeoutMs:m,maxCaptureBytes:r.maxCaptureBytes,profile:r.profile,env:process.env,...i.abortSignal?{abortSignal:i.abortSignal}:{},...w?{onDelta:w}:{}});return s({result:O,maxModelOutputChars:r.maxModelOutputChars})}})}}
|
|
@@ -4,6 +4,7 @@ import type { AgentTool } from "@roll-agent/core/types/agent";
|
|
|
4
4
|
import type { ApprovalDecision } from "../approval/approval-gate.ts";
|
|
5
5
|
import type { ToolAnnotations, ToolPolicy } from "../types/policy.ts";
|
|
6
6
|
import { ToolRegistry } from "./naming.ts";
|
|
7
|
+
import { type NormalizedToolResult } from "./normalize-result.ts";
|
|
7
8
|
export interface SourceTool {
|
|
8
9
|
readonly tool: AgentTool;
|
|
9
10
|
readonly annotations: ToolAnnotations | undefined;
|
|
@@ -27,4 +28,5 @@ export interface BuiltToolset {
|
|
|
27
28
|
readonly tools: ToolSet;
|
|
28
29
|
readonly registry: ToolRegistry;
|
|
29
30
|
}
|
|
31
|
+
export declare function gateToolCall(ctx: ToolBridgeContext, agentName: string, toolName: string, input: Record<string, unknown>, annotations: ToolAnnotations | undefined): Promise<NormalizedToolResult | undefined>;
|
|
30
32
|
export declare function buildAgentToolset(sources: readonly AgentToolSource[], ctx: ToolBridgeContext, registry?: ToolRegistry): BuiltToolset;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsonSchema as o,tool as t}from"ai";import{preflightToolCall as n}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as r}from"./naming.js";import{normalizeToolResult as e}from"./normalize-result.js";function a(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function i(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}async function
|
|
1
|
+
import{jsonSchema as o,tool as t}from"ai";import{preflightToolCall as n}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as r}from"./naming.js";import{normalizeToolResult as e}from"./normalize-result.js";function a(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function i(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}export async function gateToolCall(o,t,n,r,e){if(!o.policy)return;const a=o.policy.check({agentName:t,toolName:n,input:r,...e?{annotations:e}:{}});if("deny"===a.action)return{output:"策略拒绝执行"+(a.reason?`: ${a.reason}`:""),isError:!0};if("confirm"===a.action){const e=await o.requestApproval({agentName:t,toolName:n,input:r,reason:a.reason});if(!e.approved)return{output:"已取消执行"+(e.reason?`: ${e.reason}`:""),isError:!0}}}export function buildAgentToolset(s,c,l=new r){const u={};for(const r of s){const{client:s,agentName:m}=r;for(const{tool:p,annotations:f}of r.tools){u[l.register(m,p.name)]=t({description:p.description??`${p.name} (via ${m})`,inputSchema:o(p.inputSchema),execute:async(o,t)=>{const r=a(o),l=n(p,r);if(!l.ok)return{output:i(l.issues),isError:!0};const u=await gateToolCall(c,m,p.name,r,f);if(u)return u;const g=t.abortSignal?{signal:t.abortSignal}:void 0,y=await s.callTool({name:p.name,arguments:r},void 0,g);return e(y)}})}}return{tools:u,registry:l}}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ToolSet } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type CommandClassifier } from "../types/command-classification.ts";
|
|
4
|
+
import { type SessionManager } from "../bash/session/session-manager.ts";
|
|
5
|
+
import type { BashToolContext } from "./bash-tool.ts";
|
|
6
|
+
import { ToolRegistry } from "./naming.ts";
|
|
7
|
+
export declare const EXEC_AGENT_NAME = "roll";
|
|
8
|
+
export declare const EXEC_COMMAND_NAME = "exec_command";
|
|
9
|
+
export declare const EXEC_POLL_NAME = "exec_poll";
|
|
10
|
+
export declare const EXEC_COMMAND_ID = "roll__exec_command";
|
|
11
|
+
export declare const EXEC_POLL_ID = "roll__exec_poll";
|
|
12
|
+
export interface SessionExecSettings {
|
|
13
|
+
readonly workdir: string;
|
|
14
|
+
readonly defaultYieldMs: number;
|
|
15
|
+
readonly maxOutputTokens: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SessionExecDeps {
|
|
18
|
+
readonly classifier?: CommandClassifier;
|
|
19
|
+
}
|
|
20
|
+
declare const execCommandInputSchema: z.ZodObject<{
|
|
21
|
+
command: z.ZodString;
|
|
22
|
+
workdir: z.ZodOptional<z.ZodString>;
|
|
23
|
+
yield_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
max_output_tokens: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
command: string;
|
|
27
|
+
workdir?: string | undefined;
|
|
28
|
+
yield_time_ms?: number | undefined;
|
|
29
|
+
max_output_tokens?: number | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
command: string;
|
|
32
|
+
workdir?: string | undefined;
|
|
33
|
+
yield_time_ms?: number | undefined;
|
|
34
|
+
max_output_tokens?: number | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
declare const execPollInputSchema: z.ZodObject<{
|
|
37
|
+
session_id: z.ZodNumber;
|
|
38
|
+
chars: z.ZodDefault<z.ZodString>;
|
|
39
|
+
yield_time_ms: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
session_id: number;
|
|
42
|
+
chars: string;
|
|
43
|
+
yield_time_ms?: number | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
session_id: number;
|
|
46
|
+
yield_time_ms?: number | undefined;
|
|
47
|
+
chars?: string | undefined;
|
|
48
|
+
}>;
|
|
49
|
+
export type ExecCommandInput = z.infer<typeof execCommandInputSchema>;
|
|
50
|
+
export type ExecPollInput = z.infer<typeof execPollInputSchema>;
|
|
51
|
+
export declare function buildSessionExecToolset(settings: SessionExecSettings, manager: SessionManager, registry: ToolRegistry, ctx: BashToolContext, deps?: SessionExecDeps): ToolSet;
|
|
52
|
+
export {};
|
|
@@ -0,0 +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)}})}}
|
|
@@ -18,4 +18,4 @@ declare const skillToolInputSchema: z.ZodObject<{
|
|
|
18
18
|
}>;
|
|
19
19
|
export type SkillToolInput = z.infer<typeof skillToolInputSchema>;
|
|
20
20
|
export declare function executeSkillTool(library: SkillLibrary, input: SkillToolInput): NormalizedToolResult;
|
|
21
|
-
export declare function buildSkillToolset(library: SkillLibrary, registry: ToolRegistry): ToolSet;
|
|
21
|
+
export declare function buildSkillToolset(library: SkillLibrary | (() => SkillLibrary), registry: ToolRegistry): ToolSet;
|
|
@@ -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
|
|
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)})}}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolAnnotations } from "./policy.ts";
|
|
2
|
+
export declare const COMMAND_CLASSIFICATIONS: readonly ["known-safe", "dangerous", "unknown"];
|
|
3
|
+
export type CommandClassification = (typeof COMMAND_CLASSIFICATIONS)[number];
|
|
4
|
+
export interface CommandClassifier {
|
|
5
|
+
classify(command: string, workdir: string): CommandClassification;
|
|
6
|
+
}
|
|
7
|
+
export declare const unknownCommandClassifier: CommandClassifier;
|
|
8
|
+
export declare const CLASSIFICATION_ANNOTATIONS: Record<CommandClassification, ToolAnnotations>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const COMMAND_CLASSIFICATIONS=["known-safe","dangerous","unknown"];export const unknownCommandClassifier={classify:()=>"unknown"};export const CLASSIFICATION_ANNOTATIONS={"known-safe":{readOnlyHint:!0},dangerous:{destructiveHint:!0},unknown:{destructiveHint:!0}};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export type SessionEvent = {
|
|
|
34
34
|
readonly toolName: string;
|
|
35
35
|
readonly output: unknown;
|
|
36
36
|
readonly isError: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
readonly type: "tool-output-delta";
|
|
39
|
+
readonly toolCallId: string;
|
|
40
|
+
readonly agentName: string;
|
|
41
|
+
readonly toolName: string;
|
|
42
|
+
readonly stream: "stdout" | "stderr";
|
|
43
|
+
readonly delta: string;
|
|
37
44
|
} | {
|
|
38
45
|
readonly type: "confirmation-required";
|
|
39
46
|
readonly approvalId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.
|
|
36
|
+
"@roll-agent/core": "0.16.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",
|