@roll-agent/runtime 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/bash/classifier/compound.d.ts +3 -0
  2. package/dist/bash/classifier/compound.js +1 -0
  3. package/dist/bash/classifier/dangerous.d.ts +1 -0
  4. package/dist/bash/classifier/dangerous.js +1 -0
  5. package/dist/bash/classifier/flag-audit.d.ts +3 -0
  6. package/dist/bash/classifier/flag-audit.js +1 -0
  7. package/dist/bash/classifier/git-audit.d.ts +2 -0
  8. package/dist/bash/classifier/git-audit.js +1 -0
  9. package/dist/bash/classifier/index.d.ts +3 -0
  10. package/dist/bash/classifier/index.js +1 -0
  11. package/dist/bash/classifier/lookup-key.d.ts +1 -0
  12. package/dist/bash/classifier/lookup-key.js +1 -0
  13. package/dist/bash/classifier/path-audit.d.ts +3 -0
  14. package/dist/bash/classifier/path-audit.js +1 -0
  15. package/dist/bash/classifier/safe-list.d.ts +1 -0
  16. package/dist/bash/classifier/safe-list.js +1 -0
  17. package/dist/bash/classifier/tokenize.d.ts +10 -0
  18. package/dist/bash/classifier/tokenize.js +1 -0
  19. package/dist/bash/classifier/types.d.ts +2 -0
  20. package/dist/bash/classifier/types.js +1 -0
  21. package/dist/bash/clean-env.d.ts +8 -0
  22. package/dist/bash/clean-env.js +1 -0
  23. package/dist/bash/exec.d.ts +22 -0
  24. package/dist/bash/exec.js +1 -0
  25. package/dist/bash/format-result.d.ts +31 -0
  26. package/dist/bash/format-result.js +1 -0
  27. package/dist/bash/kill.d.ts +3 -0
  28. package/dist/bash/kill.js +1 -0
  29. package/dist/bash/output-buffer.d.ts +23 -0
  30. package/dist/bash/output-buffer.js +1 -0
  31. package/dist/bash/profile.d.ts +43 -0
  32. package/dist/bash/profile.js +1 -0
  33. package/dist/bash/session/head-tail-buffer.d.ts +17 -0
  34. package/dist/bash/session/head-tail-buffer.js +1 -0
  35. package/dist/bash/session/session-exec.d.ts +2 -0
  36. package/dist/bash/session/session-exec.js +1 -0
  37. package/dist/bash/session/session-manager.d.ts +33 -0
  38. package/dist/bash/session/session-manager.js +1 -0
  39. package/dist/bash/session/types.d.ts +39 -0
  40. package/dist/bash/session/types.js +1 -0
  41. package/dist/bash/session/yield-loop.d.ts +2 -0
  42. package/dist/bash/session/yield-loop.js +1 -0
  43. package/dist/bash/shell.d.ts +6 -0
  44. package/dist/bash/shell.js +1 -0
  45. package/dist/bash/truncate.d.ts +6 -0
  46. package/dist/bash/truncate.js +1 -0
  47. package/dist/bash/workdir.d.ts +1 -0
  48. package/dist/bash/workdir.js +1 -0
  49. package/dist/engine/agent-session.d.ts +46 -3
  50. package/dist/engine/agent-session.js +1 -1
  51. package/dist/engine/conversation-engine.d.ts +33 -2
  52. package/dist/engine/conversation-engine.js +1 -1
  53. package/dist/engine/system-prompt.d.ts +18 -0
  54. package/dist/engine/system-prompt.js +1 -1
  55. package/dist/index.d.ts +8 -0
  56. package/dist/index.js +1 -1
  57. package/dist/server/protocol.d.ts +8 -0
  58. package/dist/server/protocol.js +1 -1
  59. package/dist/server/runtime-server.js +1 -1
  60. package/dist/tool-bridge/agent-install-tool.d.ts +27 -0
  61. package/dist/tool-bridge/agent-install-tool.js +1 -0
  62. package/dist/tool-bridge/bash-tool.d.ts +45 -0
  63. package/dist/tool-bridge/bash-tool.js +1 -0
  64. package/dist/tool-bridge/build-tools.d.ts +2 -0
  65. package/dist/tool-bridge/build-tools.js +1 -1
  66. package/dist/tool-bridge/session-exec-tool.d.ts +52 -0
  67. package/dist/tool-bridge/session-exec-tool.js +1 -0
  68. package/dist/tool-bridge/skill-tool.d.ts +1 -1
  69. package/dist/tool-bridge/skill-tool.js +1 -1
  70. package/dist/types/cancellation.d.ts +12 -0
  71. package/dist/types/cancellation.js +1 -0
  72. package/dist/types/command-classification.d.ts +8 -0
  73. package/dist/types/command-classification.js +1 -0
  74. package/dist/types/events.d.ts +12 -0
  75. package/package.json +2 -2
@@ -0,0 +1,3 @@
1
+ import type { CommandClassification } from "../../types/command-classification.ts";
2
+ export declare function containsDangerousMetachar(script: string): boolean;
3
+ export declare function classifyScript(script: string, platform: NodeJS.Platform): CommandClassification;
@@ -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,3 @@
1
+ import type { AuditVerdict, FlagAuditor } from "./types.ts";
2
+ export declare const FLAG_AUDITORS: Readonly<Record<string, FlagAuditor>>;
3
+ export declare function auditFlags(key: string, argv: readonly string[]): AuditVerdict;
@@ -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,2 @@
1
+ import type { AuditVerdict } from "./types.ts";
2
+ export declare function auditGit(argv: readonly string[]): AuditVerdict;
@@ -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,3 @@
1
+ import type { CommandClassifier } from "../../types/command-classification.ts";
2
+ export declare function unwrapShellWrapper(command: string, platform: NodeJS.Platform): string;
3
+ export declare const ruleBasedClassifier: CommandClassifier;
@@ -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,3 @@
1
+ import type { AuditVerdict } from "./types.ts";
2
+ export declare function isEscapingPathArg(arg: string): boolean;
3
+ export declare function auditPathArgs(key: string, argv: readonly string[]): AuditVerdict;
@@ -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,2 @@
1
+ export type AuditVerdict = "safe" | "reject";
2
+ export type FlagAuditor = (argv: readonly string[]) => AuditVerdict;
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,8 @@
1
+ export declare const CLEAN_EXEC_ENV: {
2
+ readonly NO_COLOR: "1";
3
+ readonly TERM: "dumb";
4
+ readonly PAGER: "cat";
5
+ readonly GIT_PAGER: "cat";
6
+ readonly LANG: "C.UTF-8";
7
+ };
8
+ export declare function withCleanEnv(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -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{BASH_TERMINATION_CAUSES as n,EXEC_TIMEOUT_EXIT_CODE as s,normalizeExitCode as l}from"./format-result.js";import{isTimeoutAbortReason as a}from"../types/cancellation.js";const m=2e3,u=2500,c=1e3,d=130,f={spawn:e,killTreeDeadlineMs:u,rootKillSettleTimeoutMs:c};function p(e){return e instanceof Error?e.message:String(e)}function T(e,t){const o=new i(0).collect();return{exitCode:1,timedOut:!1,timeoutMs:t,wallTimeMs:0,stdout:o,stderr:o,spawnError:e}}function w(e,t){const o=new i(0).collect(),r=a(t);return{exitCode:r?s:d,timedOut:r,timeoutMs:e,wallTimeMs:0,stdout:o,stderr:o,terminationCause:r?n.timeout:n.abort}}export function runBashCommand(e,u=f){const{command:c,workdir:v,timeoutMs:M,maxCaptureBytes:b,profile:h,env:C,abortSignal:E,onDelta:g}=e;return new Promise(e=>{if(E?.aborted)return void e(w(M,E.reason));if(!t(v))return void e(T(`工作目录不存在: ${v}`,M));let f;try{const e=h.buildSpawn(c,v,C??process.env);f=u.spawn(e.file,e.args,e.options)}catch(t){return void e(T(p(t),M))}const x=r.now(),y=new i(b),$=new i(b);let k,S,D,L,O,K,j=!1,P=!1,A=!1,B=!1,I=!1,_=!1,F=null,G=null,N=void 0!==f.pid,R=!1,q=!1;const z=e=>{K=K?`${K};${e}`:e},H=()=>{if(B||!I||!_)return;if(A&&!0===h.waitForTreeKillAfterRootExit&&!R&&!q)return;const e=null!==G?o.signals[G]:void 0,t=j?s:P?d:l({timedOut:!1,code:F,signalNumber:e});W({exitCode:t,timedOut:j,timeoutMs:M,wallTimeMs:r.now()-x,stdout:y.collect(),stderr:$.collect(),...k?{terminationCause:k}:{},...K?{terminationError:K}:{}})},J=()=>{if(!B&&!I){for(const e of Y)e.stream.destroy();f.unref(),W({exitCode:j?s:d,timedOut:j,timeoutMs:M,wallTimeMs:r.now()-x,stdout:y.collect(),stderr:$.collect(),...k?{terminationCause:k}:{},terminationError:`${K??"进程树清理未完成"};根进程在强制终止请求后仍未确认退出`})}},Q=e=>{if(!B){if(L&&(clearTimeout(L),L=void 0),!R){R=!0;const t=void 0===e?"进程树清理未完成":p(e);z(`${t};已回退仅向根进程发送强制终止请求,无法确认后代进程是否已清理`)}if(I)H();else if(void 0===O){O=setTimeout(J,u.rootKillSettleTimeoutMs);try{f.kill("SIGKILL")||z("根进程强制终止请求未被接受")}catch(e){z(`根进程强制终止请求失败: ${p(e)}`)}}}},U=()=>{if(!A){A=!0,D=new AbortController,L=setTimeout(()=>Q(new Error("进程树清理超过独立期限")),u.killTreeDeadlineMs);try{h.killTree(f.pid,"terminate",{signal:D.signal}).then(()=>{q=!0,I&&L&&(clearTimeout(L),L=void 0),H()}).catch(Q)}catch(e){Q(e)}}},V=()=>{if(void 0!==k)return;const e=a(E?.reason);k=e?n.timeout:n.abort,j=e,P=!e,clearTimeout(Z),U()},W=t=>{B||(B=!0,clearTimeout(Z),S&&clearTimeout(S),L&&clearTimeout(L),O&&clearTimeout(O),D?.abort(),D=void 0,E?.removeEventListener("abort",V),e(t))},X=(e,t,o)=>{if(null===e)return;const r=new TextDecoder("utf-8");e.on("data",e=>{if(o.append(e),g){const o=r.decode(e,{stream:!0});o.length>0&&g(t,o)}});const i=new Promise(t=>{e.once("close",()=>t())});return{stream:e,closed:i}},Y=[X(f.stdout,"stdout",y),X(f.stderr,"stderr",$)].filter(e=>void 0!==e);f.once("spawn",()=>{N=!0}),f.on("error",e=>{if(B)return;if(!A||!N)return void W(T(p(e),M));const t=`根进程终止请求失败: ${p(e)}`;R?z(t):Q(new Error(t,{cause:e}))}),f.on("exit",(e,t)=>{B||I||(I=!0,F=e,G=t,clearTimeout(Z),E?.removeEventListener("abort",V),q&&L&&(clearTimeout(L),L=void 0),O&&(clearTimeout(O),O=void 0),S=setTimeout(()=>{for(const e of Y)e.stream.destroy()},u.ioDrainTimeoutMs??m),Promise.all(Y.map(e=>e.closed)).then(()=>{_=!0,H()}).catch(()=>{}))});const Z=setTimeout(()=>{void 0===k&&(k=n.timeout,j=!0,U())},M);E&&(E.aborted?V():E.addEventListener("abort",V))})}
@@ -0,0 +1,31 @@
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 declare const BASH_TERMINATION_CAUSES: {
6
+ readonly timeout: "timeout";
7
+ readonly abort: "abort";
8
+ };
9
+ export type BashTerminationCause = (typeof BASH_TERMINATION_CAUSES)[keyof typeof BASH_TERMINATION_CAUSES];
10
+ export interface BashExecResult {
11
+ readonly exitCode: number;
12
+ readonly timedOut: boolean;
13
+ readonly timeoutMs: number;
14
+ readonly wallTimeMs: number;
15
+ readonly stdout: CapturedStream;
16
+ readonly stderr: CapturedStream;
17
+ readonly terminationCause?: BashTerminationCause;
18
+ readonly spawnError?: string;
19
+ readonly terminationError?: string;
20
+ }
21
+ export interface NormalizeExitCodeParams {
22
+ readonly timedOut: boolean;
23
+ readonly code: number | null;
24
+ readonly signalNumber: number | undefined;
25
+ }
26
+ export declare function normalizeExitCode(params: NormalizeExitCodeParams): number;
27
+ export interface FormatBashResultInput {
28
+ readonly result: BashExecResult;
29
+ readonly maxModelOutputChars: number;
30
+ }
31
+ 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;export const BASH_TERMINATION_CAUSES={timeout:"timeout",abort:"abort"};const o=1;export function normalizeExitCode(t){return t.timedOut?124:null!==t.code?t.code:void 0!==t.signalNumber?128+t.signalNumber:1}function e(t,r){if(r.truncated)return`Warning: ${t} 输出已截断(原始 ${String(r.totalBytes)} 字节 / ${String(r.totalLines)} 行)`}function n(t,r){const o=r.length>0?r:void 0;return void 0!==o?`[${t}]\n${o}`:void 0}export function formatBashResult(o){const{result:i,maxModelOutputChars:s}=o;if(void 0!==i.spawnError)return{output:`命令无法启动: ${i.spawnError}`,isError:!0};const u=t(s,Array.from(i.stderr.text).length),d=r(i.stdout.text,u.stdout),a=r(i.stderr.text,u.stderr),m=[];i.terminationCause===BASH_TERMINATION_CAUSES.abort&&m.push("命令已中断(收到取消请求),不能视为正常完成"),i.timedOut&&m.push(i.terminationError?`命令超时(超过 ${String(i.timeoutMs)}ms),终止状态未确认`:`命令超时(超过 ${String(i.timeoutMs)}ms),已终止`),i.terminationError&&m.push(`终止失败: ${i.terminationError}`),m.push(`Exit code: ${String(i.exitCode)}`),m.push(`Wall time: ${(i.wallTimeMs/1e3).toFixed(1)} s`);const c=[i.stdout.truncated||d.truncated?e("stdout",i.stdout):void 0,i.stderr.truncated||a.truncated?e("stderr",i.stderr):void 0].filter(t=>void 0!==t);m.push(...c);const E=[n("stdout",d.text),n("stderr",a.text)].filter(t=>void 0!==t);return{output:[m.join("\n"),...E].join("\n\n"),isError:0!==i.exitCode||void 0!==i.terminationError}}
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_KILL_GRACE_MS = 150;
2
+ export declare function killProcessGroup(pid: number | undefined, signal: NodeJS.Signals): void;
3
+ export declare function escalateKillGroup(pid: number | undefined, graceMs?: number): NodeJS.Timeout;
@@ -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,2 @@
1
+ import type { ManagedSession, SpawnSessionInput } from "./types.ts";
2
+ export declare function spawnSession(input: SpawnSessionInput): ManagedSession;
@@ -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,33 @@
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
+ readonly interruptGraceMs?: number;
14
+ }
15
+ export interface SpawnRequest {
16
+ readonly command: string;
17
+ readonly workdir: string;
18
+ readonly onDelta?: (stream: BashStreamName, delta: string) => void;
19
+ }
20
+ export declare class SessionManager {
21
+ private readonly sessions;
22
+ private readonly options;
23
+ constructor(options: SessionManagerOptions);
24
+ spawn(request: SpawnRequest): ManagedSession;
25
+ get(id: number): ManagedSession | undefined;
26
+ delete(id: number): void;
27
+ size(): number;
28
+ interruptAll(): void;
29
+ terminateAll(): void;
30
+ private evictExited;
31
+ private interruptThenTerminate;
32
+ private allocateId;
33
+ }
@@ -0,0 +1 @@
1
+ import{spawnSession as s}from"./session-exec.js";const e=150;export class SessionCapError extends Error{constructor(s){super(`会话数已达上限 ${String(s)},且无空闲会话可回收`),this.name="SessionCapError"}}function t(){return 1e3+Math.floor(99e3*Math.random())}export class SessionManager{sessions=new Map;options;constructor(s){this.options=s}spawn(e){if(this.evictExited(),this.sessions.size>=this.options.maxSessions)throw new SessionCapError(this.options.maxSessions);const t=this.allocateId(),i=s({id:t,command:e.command,workdir:e.workdir,profile:this.options.profile,env:this.options.env,bufferCapacity:this.options.bufferCapacity,...e.onDelta?{onDelta:e.onDelta}:{}});return this.sessions.set(t,i),i}get(s){return this.sessions.get(s)}delete(s){this.sessions.delete(s)}size(){return this.sessions.size}interruptAll(){const s=[...this.sessions.values()];this.sessions.clear();for(const e of s)this.interruptThenTerminate(e).catch(()=>{})}terminateAll(){for(const s of this.sessions.values())s.profile.killTree(s.child.pid,"terminate").catch(()=>{});this.sessions.clear()}evictExited(){for(const[s,e]of this.sessions)void 0!==e.exitCode&&this.sessions.delete(s)}async interruptThenTerminate(s){if(s.profile.killTree(s.child.pid,"interrupt").catch(()=>{}),void 0!==s.exitCode)return;await i(s,this.options.interruptGraceMs??e)||void 0!==s.exitCode||await s.profile.killTree(s.child.pid,"terminate").catch(()=>{})}allocateId(){const s=this.options.generateId??t;for(let e=0;e<1e3;e+=1){const e=s();if(!this.sessions.has(e))return e}throw new Error("无法分配唯一会话 id")}}function i(s,e){return new Promise(t=>{const i=setTimeout(()=>t(!1),e);i.unref(),s.waitExit().then(()=>{clearTimeout(i),t(!0)})})}
@@ -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,2 @@
1
+ import type { ManagedSession, SessionPollResult } from "./types.ts";
2
+ export declare function pollUntilDeadline(session: ManagedSession, deadlineMs: number, maxChars: number): Promise<SessionPollResult>;
@@ -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,6 @@
1
+ export interface TruncationResult {
2
+ readonly text: string;
3
+ readonly truncated: boolean;
4
+ readonly removed: number;
5
+ }
6
+ export declare function truncateMiddle(text: string, maxChars: number): TruncationResult;
@@ -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;
@@ -24,9 +28,36 @@ export interface AgentSessionOptions {
24
28
  readonly compaction?: SessionCompactionSettings;
25
29
  readonly turnTimeoutMs?: number;
26
30
  readonly providerOptions?: SharedV4ProviderOptions;
31
+ /** `setProviderOptions()` 生效后触发;ConversationEngine 用它同步子 Agent Sampling。 */
32
+ readonly onProviderOptionsChange?: (providerOptions: SharedV4ProviderOptions | undefined) => void;
27
33
  readonly debugEvents?: boolean;
28
34
  readonly systemPrompt?: string;
29
35
  readonly skillLibrary?: SkillLibrary;
36
+ readonly bash?: SessionBashSettings;
37
+ readonly bashClassifier?: CommandClassifier;
38
+ readonly bashSession?: AgentSessionBashSession;
39
+ readonly agentInstall?: AgentSessionAgentInstall;
40
+ }
41
+ export interface SessionAgentRefresh {
42
+ readonly source: AgentToolSource;
43
+ readonly skillLibrary?: SkillLibrary;
44
+ readonly systemPrompt: string;
45
+ }
46
+ export interface AgentInstallSessionResult {
47
+ readonly outcome: AgentInstallToolOutcome;
48
+ readonly refresh?: SessionAgentRefresh;
49
+ }
50
+ export interface AgentSessionAgentInstall {
51
+ readonly catalog: readonly AgentInstallToolCatalogEntry[];
52
+ readonly install: (shortName: string, report: (line: string) => void) => Promise<AgentInstallSessionResult>;
53
+ }
54
+ export interface AgentSessionBashSession {
55
+ readonly workdir: string;
56
+ readonly profile: ShellProfile;
57
+ readonly maxSessions: number;
58
+ readonly defaultYieldMs: number;
59
+ readonly maxOutputTokens: number;
60
+ readonly bufferCapacity: number;
30
61
  }
31
62
  export type SessionSkillSummary = SkillSummary;
32
63
  export declare class AgentSession {
@@ -40,18 +71,26 @@ export declare class AgentSession {
40
71
  private readonly compaction;
41
72
  private readonly turnTimeoutMs;
42
73
  private providerOptions;
74
+ private readonly onProviderOptionsChange;
43
75
  private readonly debugEvents;
44
- private readonly systemPrompt;
45
- private readonly skillSummaries;
76
+ private readonly policy;
77
+ private systemPrompt;
78
+ private skillSummaries;
79
+ private skillLibrary;
80
+ private skillToolBuilt;
81
+ private readonly toolSourceAgentNames;
46
82
  private readonly gate;
47
- private readonly tools;
83
+ private tools;
48
84
  private readonly registry;
85
+ private readonly sessionManager;
49
86
  private emit;
50
87
  private activeTurn;
51
88
  private sessionUsage;
52
89
  private lastInputTokens;
53
90
  private needsCompaction;
54
91
  constructor(options: AgentSessionOptions);
92
+ private buildSkillTools;
93
+ applyAgentRefresh(refresh: SessionAgentRefresh): void;
55
94
  send(input: string): AsyncIterable<SessionEvent>;
56
95
  compact(reason?: ContextCompactionReason): AsyncIterable<SessionEvent>;
57
96
  private runTurn;
@@ -68,6 +107,10 @@ export declare class AgentSession {
68
107
  private runCompaction;
69
108
  private recoverFromContextError;
70
109
  private isTurnAborted;
110
+ private cancellationMessage;
111
+ private emitCancellation;
112
+ private persistCancelledTurn;
113
+ cancel(): boolean;
71
114
  abort(): void;
72
115
  private debug;
73
116
  private scheduleDebug;
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{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 n}from"../tool-bridge/skill-tool.js";import{buildBashToolset as a}from"../tool-bridge/bash-tool.js";import{buildSessionExecToolset as r}from"../tool-bridge/session-exec-tool.js";import{SessionManager as l}from"../bash/session/session-manager.js";import{withCleanEnv as c}from"../bash/clean-env.js";import{RUNTIME_CANCELLATION_ABORT_REASON as u,SESSION_CANCELLATION_REASONS as h,TURN_TIMEOUT_ABORT_REASON as p,USER_CANCELLATION_ABORT_REASON as m,isTurnTimeoutAbortReason as d}from"../types/cancellation.js";import{ToolRegistry as g}from"../tool-bridge/naming.js";import{buildChatSystemPrompt as b}from"./system-prompt.js";import{readIsError as v}from"../tool-bridge/normalize-result.js";import{ApprovalGate as T}from"../approval/approval-gate.js";import{compactMessages as k}from"./compactor.js";import{AsyncEventQueue as y}from"./event-queue.js";function f(e){return e instanceof Error?e.message:String(e)}function C(e){return"object"==typeof e&&null!==e&&"output"in e}function x(e){return C(e)?f(e.output):f(e)}function w(e){const t=x(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function S(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(f(e))}function A(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.inputTokenDetails?.cacheWriteTokens,o=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{cacheWriteTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function M(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function R(e,t){const s=M(e.cachedInputTokens,t.cachedInputTokens),o=M(e.cacheWriteTokens,t.cacheWriteTokens),i=M(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{cacheWriteTokens:o}:{},...void 0!==i?{reasoningTokens:i}:{}}}function N(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function I(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function E(e,t){return void 0!==e?e:d(t)?h.timeout:h.runtime}function P(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;onProviderOptionsChange;debugEvents;policy;systemPrompt;skillSummaries;skillLibrary;skillToolBuilt=!1;toolSourceAgentNames;gate=new T;tools;registry;sessionManager;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?P(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.onProviderOptionsChange=e.onProviderOptionsChange,this.debugEvents=e.debugEvents??!1,this.policy=e.policy,this.systemPrompt=e.systemPrompt??b(),this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary?.list()??[],this.toolSourceAgentNames=new Set(e.sources.map(e=>e.agentName));const t=new g,s=e.skillLibrary?this.buildSkillTools(t):{},n={...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e),emitEvent:e=>this.emit?.(e)},u=e.bashClassifier?{classifier:e.bashClassifier}:{},h=e.bash?a(e.bash,t,n,u):{};e.bashSession&&(this.sessionManager=new l({maxSessions:e.bashSession.maxSessions,profile:e.bashSession.profile,env:c(process.env),bufferCapacity:e.bashSession.bufferCapacity}));const p=e.bashSession&&this.sessionManager?r({workdir:e.bashSession.workdir,defaultYieldMs:e.bashSession.defaultYieldMs,maxOutputTokens:e.bashSession.maxOutputTokens},this.sessionManager,t,n,u):{},m=e.agentInstall,d=m?i({catalog:m.catalog,install:async(e,t)=>{const s=await m.install(e,t);return s.outcome.ok&&s.refresh?(this.applyAgentRefresh(s.refresh),{...s.outcome,refreshApplied:!0}):s.outcome}},t,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)}):{},v=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)},t);this.tools={...s,...h,...p,...d,...v.tools},this.registry=v.registry}buildSkillTools(e){return this.skillToolBuilt=!0,n(()=>{if(!this.skillLibrary)throw new Error("skill library 不可用");return this.skillLibrary},e)}applyAgentRefresh(e){if(!this.toolSourceAgentNames.has(e.source.agentName)){const t=o([e.source],{...this.policy?{policy:this.policy}:{},requestApproval:e=>this.requestApproval(e)},this.registry);this.tools={...this.tools,...t.tools},this.toolSourceAgentNames.add(e.source.agentName)}e.skillLibrary&&(this.skillLibrary=e.skillLibrary,this.skillSummaries=e.skillLibrary.list(),this.skillToolBuilt||(this.tools={...this.tools,...this.buildSkillTools(this.registry)})),this.systemPrompt=e.systemPrompt}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new y;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1,cancellationEventEmitted:!1,cancellationPersisted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:f(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new y;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1,cancellationEventEmitted:!1,cancellationPersisted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:f(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,n){const a=Date.now();let r,l;try{if(this.debug(o,"turn","start",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",a,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:f(e)})}}if(i.aborted||i.abortController.signal.aborted)return r=this.messages.length,this.messages.push({role:"user",content:n}),void this.persistCancelledTurn(o,i,r,[],a);r=this.messages.length,this.messages.push({role:"user",content:n}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}}),void 0!==this.turnTimeoutMs&&(l=setTimeout(()=>{i.abortController.signal.aborted||(i.aborted=!0,i.cancellationReason=h.timeout,this.gate.abortAll("本轮运行超时"),i.abortController.abort(p))},this.turnTimeoutMs));let c=[];const u=s({model:this.model,system:this.systemPrompt,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},onAbort:({steps:e})=>{c=e.flatMap(e=>e.response.messages)}});this.debug(o,"model","streamText returned",a);let m,g,b,T,k,y,C="",M="",W=!1,j=0,O=!1;const D=this.scheduleDebug(o,"model","waiting for first stream event",a,{messages:this.messages.length});try{for await(const e of u.fullStream){switch(O||(O=!0,this.clearDebugTimer(D),this.debug(o,"model","first stream event",a,{part:e.type})),e.type){case"text-delta":if(!W){const t=M+e.text;if(I(t,n)){M=t;break}if(M.length>0){const t=M+e.text;M="",C+=t,o.push({type:"text-delta",delta:t});break}}C+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{W=!0,M="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=v(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(y=w(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=x(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(y=w(e.error));break}case"finish-step":{const t=A(e.usage);g=N(g,t.inputTokens),j+=1,T=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(b=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!W&&M.length>0&&(C+=M,o.push({type:"text-delta",delta:M}),M=""),m=A(e.totalUsage);break;case"error":if(this.isTurnAborted(i)||d(e.error)){i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e.error);break}S(e.error)&&(this.needsCompaction=!0),k=f(e.error),o.push({type:"error",stage:"execute",message:k});break;case"abort":i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e.reason)}if(void 0!==y)break}}finally{this.clearDebugTimer(D)}if(this.debug(o,"model","fullStream finished",a,{textChars:C.length}),void 0!==k)return this.messages.splice(r),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return i.cancellationReason=E(i.cancellationReason,i.abortController.signal.reason),void this.persistCancelledTurn(o,i,r,c,a);if(void 0!==y){const e=y,t=0===C.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-r}),this.onPersist?.(this.messages.slice(r)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),m&&(this.sessionUsage=R(this.sessionUsage,m));const s=g??m?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...m?{totalUsage:m}:{},sessionUsage:{...this.sessionUsage},...void 0!==g?{contextInputTokens:g}:{},...void 0!==b?{outputTokensPerSecond:b}:{}})}let U;this.debug(o,"model","awaiting response messages",a);const q=this.scheduleDebug(o,"model","still awaiting response messages",a);try{U=(await u.response).messages}catch(e){return this.clearDebugTimer(q),this.isTurnAborted(i)||d(e)?(i.aborted=!0,i.cancellationReason=E(i.cancellationReason,e),void this.persistCancelledTurn(o,i,r,c,a)):(S(e)&&(this.needsCompaction=!0),this.messages.splice(r),o.push({type:"error",stage:"execute",message:f(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i)))}finally{this.clearDebugTimer(q)}this.debug(o,"model","response messages ready",a,{responseMessages:U.length});const L=P(U);this.messages.push(...L),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-r}),this.onPersist?.(this.messages.slice(r)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),m&&(this.sessionUsage=R(this.sessionUsage,m));const F=g??m?.inputTokens;void 0!==F&&(this.lastInputTokens=F);const z=j>=this.maxSteps&&"tool-calls"===T;o.push({type:"message-finish",text:C,...m?{totalUsage:m}:{},sessionUsage:{...this.sessionUsage},...void 0!==g?{contextInputTokens:g}:{},...void 0!==b?{outputTokensPerSecond:b}:{},...z?{stoppedAtStepLimit:!0}:{}})}catch(e){if(this.isTurnAborted(i)||d(e))return i.aborted=!0,i.cancellationReason=E(i.cancellationReason,i.abortController.signal.reason??e),void(void 0!==r?this.persistCancelledTurn(o,i,r,[],a):this.emitCancellation(o,i));void 0!==r&&this.messages.splice(r),S(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",a,{message:f(e)}),o.push({type:"error",stage:"execute",message:f(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{void 0!==l&&clearTimeout(l),this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}getSkillSummaries(){return this.skillSummaries}setProviderOptions(e){this.providerOptions=e,this.onProviderOptionsChange?.(e)}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:f(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),i=this.compaction,n=i?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));if(!i)return void e.push({type:"context-compacted",reason:t,strategy:n,removed:0,kept:this.messages.length});const a=this.lastInputTokens;let r=i.strategy;const l=s?.abortController.signal;let c;try{c=await k({messages:this.messages,strategy:r,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}catch(t){if(this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));if("summarize"!==r)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:f(t)}),r="truncate",c=await k({messages:this.messages,strategy:r,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}if(this.isTurnAborted(s))return void(void 0!==s&&this.emitCancellation(e,s));const u=c.removed>0||c.truncatedTools>0;u&&(this.onReplace?.(c.messages),this.messages.splice(0,this.messages.length,...c.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:r,removed:c.removed,kept:c.kept,...c.truncatedTools>0?{truncatedTools:c.truncatedTools}:{},...void 0!==a?{beforeInputTokens:a}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:r,removed:c.removed,kept:c.kept,truncatedTools:c.truncatedTools,progressed:u})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:f(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}cancellationMessage(e){switch(e.cancellationReason??h.runtime){case h.user:return"已取消本轮;正在运行的模型或工具已收到中断请求,已发生的外部副作用不会自动回滚。";case h.timeout:return`本轮因运行超时${void 0!==this.turnTimeoutMs?`(${String(this.turnTimeoutMs)}ms)`:""}而中断;未返回成功 tool result 或 Exit code: 0 的操作不能视为正常完成。`;case h.runtime:return"本轮被运行时中断;未返回成功 tool result 或 Exit code: 0 的操作状态未知。"}}emitCancellation(e,t){t.cancellationEventEmitted||(t.cancellationReason??=h.runtime,t.cancellationEventEmitted=!0,e.push({type:"turn-cancelled",reason:t.cancellationReason,message:this.cancellationMessage(t)}))}persistCancelledTurn(e,t,s,o,i){t.cancellationPersisted||(this.messages.splice(s+1),this.messages.push(...P(o)),this.messages.push({role:"assistant",content:this.cancellationMessage(t)}),t.cancellationPersisted=!0,this.debug(e,"persist","persisting cancelled turn",i,{appendedMessages:this.messages.length-s}),this.onPersist?.(this.messages.slice(s))),this.emitCancellation(e,t)}cancel(){return!!this.activeTurn&&(this.activeTurn.aborted=!0,this.activeTurn.cancellationReason=h.user,this.gate.abortAll("用户取消本轮"),this.sessionManager?.interruptAll(),this.activeTurn.abortController.abort(m),!0)}abort(){this.activeTurn&&(this.activeTurn.aborted=!0,this.activeTurn.cancellationReason??=h.runtime,this.gate.abortAll(),this.activeTurn.abortController.abort(u)),this.sessionManager?.terminateAll()}debug(e,t,s,o,i){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==i?{data:i}:{}})}scheduleDebug(e,t,s,o,i){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,i)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}