aws-runtime-bridge 1.9.142 → 1.9.144
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/package/acode/dist/await-complete-tool.js +3 -3
- package/package/acode/dist/background-command-manager.d.ts +22 -4
- package/package/acode/dist/background-command-manager.js +5 -5
- package/package/acode/dist/background-command-tools.js +10 -10
- package/package/acode/dist/background-output-ringbuffer.d.ts +5 -3
- package/package/acode/dist/background-output-ringbuffer.js +2 -2
- package/package/acode/dist/built-in-file-tools.js +2 -2
- package/package/acode/dist/builtins/shellCommandPreparation.js +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const g=`Block and wait for one or more sub-agents AND/OR background commands to complete.
|
|
1
|
+
const _=6e5,g=`Block and wait for one or more sub-agents AND/OR background commands to complete.
|
|
2
2
|
|
|
3
3
|
This is the unified replacement for the old await_sub_agents: it can wait for BOTH
|
|
4
4
|
sub-agents (spawned via sub_agent) and background commands (started via Bash with
|
|
@@ -15,7 +15,7 @@ to fetch full output.
|
|
|
15
15
|
Guidelines:
|
|
16
16
|
- Pass sub-agent IDs in 'sub_agent_ids' and/or background command task_ids in 'bg_cmd_ids'
|
|
17
17
|
- At least one of the two arrays must be non-empty
|
|
18
|
-
- Use timeout_ms to
|
|
19
|
-
- After this returns, call poll_message to receive any pending messages`;function h(o){return{kind:"builtin",toolName:"await_complete",exposedName:"await_complete",description:g,inputSchema:{type:"object",properties:{sub_agent_ids:{type:"array",items:{type:"string"},description:"Array of sub-agent IDs to wait for (from sub_agent). Empty if none."},bg_cmd_ids:{type:"array",items:{type:"string"},description:"Array of background command task_ids to wait for (from Bash wait_seconds or list_background_commands). Empty if none."},timeout_ms:{type:"number",description:"Maximum time to wait in milliseconds, applied to the whole JOIN. 0 = no timeout (wait forever). Default:
|
|
18
|
+
- Use timeout_ms to bound the wait (default 600000 = 10 minutes; 0 = wait forever)
|
|
19
|
+
- After this returns, call poll_message to receive any pending messages`;function h(o){return{kind:"builtin",toolName:"await_complete",exposedName:"await_complete",description:g,inputSchema:{type:"object",properties:{sub_agent_ids:{type:"array",items:{type:"string"},description:"Array of sub-agent IDs to wait for (from sub_agent). Empty if none."},bg_cmd_ids:{type:"array",items:{type:"string"},description:"Array of background command task_ids to wait for (from Bash wait_seconds or list_background_commands). Empty if none."},timeout_ms:{type:"number",description:"Maximum time to wait in milliseconds, applied to the whole JOIN. 0 = no timeout (wait forever). Default: 600000 (10 minutes)."}},additionalProperties:!1},async execute(i){const l=typeof i.timeout_ms=="number"?i.timeout_ms:6e5,m=i.sub_agent_ids,c=i.bg_cmd_ids,n=Array.isArray(m)?m.filter(t=>typeof t=="string"&&t.trim().length>0):[],u=Array.isArray(c)?c.filter(t=>typeof t=="string"&&t.trim().length>0):[];if(n.length===0&&u.length===0)return{title:"await_complete validation error",output:"Invalid input: provide at least one of 'sub_agent_ids' or 'bg_cmd_ids'.",metadata:{error:!0}};if(n.length>0&&!o.awaitSubAgents)return{title:"await_complete error",output:"sub_agent_ids provided but sub-agent manager is not enabled for this agent.",metadata:{error:!0}};try{let t=[];n.length>0&&o.awaitSubAgents&&(t=await o.awaitSubAgents(n,l)??[]);let a=[];u.length>0&&(a=await o.awaitBgCommands(u,l));const s=[],p=[],d=[];if(t.length>0){s.push(`=== ${t.length} sub-agent(s) ===`);for(const e of t)s.push(`[${e.status}] ${e.subId}`),e.error?(s.push("<task_error>"),s.push(e.error),s.push("</task_error>")):(s.push("<task_result>"),s.push(e.output||"(empty output)"),s.push("</task_result>")),s.push(""),p.push(e)}if(a.length>0){s.push(`=== ${a.length} background command(s) ===`);for(const e of a){if(!e){s.push("[not_found] (unknown task_id)"),s.push(""),d.push(null);continue}const r=[];e.timedOut&&r.push("timed_out"),e.aborted&&r.push("aborted"),s.push(`[${e.status}] ${e.taskId} exit=${e.exitCode??"?"}`+(r.length?` (${r.join(",")})`:"")+(e.outputFilePath?` output=${e.outputFilePath}`:"")),s.push(""),d.push(e)}}return{title:`awaited ${t.length+a.length} item(s)`,output:s.join(`
|
|
20
20
|
`),metadata:{total:t.length+a.length,sub_agents:p,bg_cmds:d}}}catch(t){return{title:"await_complete error",output:`Failed to await: ${t instanceof Error?t.message:String(t)}`,metadata:{error:!0}}}}}}export{h as createAwaitCompleteTool};
|
|
21
21
|
|
|
@@ -115,24 +115,39 @@ export interface TaskOutputSnapshot {
|
|
|
115
115
|
}
|
|
116
116
|
export interface ReadOutputArgs {
|
|
117
117
|
task_id: string;
|
|
118
|
-
mode?: "
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
mode?: "lines" | "grep" | "tail";
|
|
119
|
+
|
|
120
|
+
line_size?: number;
|
|
121
|
+
|
|
122
|
+
start_line?: number;
|
|
121
123
|
pattern?: string;
|
|
124
|
+
context_before?: number;
|
|
125
|
+
context_after?: number;
|
|
122
126
|
include_metadata?: boolean;
|
|
123
127
|
}
|
|
128
|
+
|
|
129
|
+
export interface ReadOutputLine {
|
|
130
|
+
line: number;
|
|
131
|
+
text: string;
|
|
132
|
+
}
|
|
124
133
|
export interface ReadOutputResult {
|
|
125
134
|
taskId: string;
|
|
126
135
|
status: BackgroundTaskStatus;
|
|
127
136
|
mode: string;
|
|
128
|
-
lines:
|
|
137
|
+
lines: ReadOutputLine[];
|
|
129
138
|
lineCount: number;
|
|
130
139
|
metadata?: {
|
|
140
|
+
|
|
131
141
|
totalLines: number;
|
|
132
142
|
totalBytes: number;
|
|
133
143
|
returnedBytes: number;
|
|
144
|
+
|
|
134
145
|
truncated: boolean;
|
|
146
|
+
|
|
147
|
+
outputTruncated: boolean;
|
|
135
148
|
hasMore: boolean;
|
|
149
|
+
|
|
150
|
+
nextStartLine: number | null;
|
|
136
151
|
outputFile: string;
|
|
137
152
|
};
|
|
138
153
|
}
|
|
@@ -170,8 +185,11 @@ export declare class BackgroundCommandManager {
|
|
|
170
185
|
getTaskOutputSnapshot(agentId: string, taskId: string, tailLines?: number): TaskOutputSnapshot | null;
|
|
171
186
|
|
|
172
187
|
subscribeTaskOutput(agentId: string, taskId: string, subscriber: TaskOutputSubscriber): (() => void) | null;
|
|
188
|
+
|
|
173
189
|
readOutput(agentId: string, args: ReadOutputArgs): Promise<ReadOutputResult>;
|
|
174
190
|
|
|
191
|
+
private _emptyResult;
|
|
192
|
+
|
|
175
193
|
private readFromFile;
|
|
176
194
|
|
|
177
195
|
awaitCompletion(agentId: string, taskIds: string[], timeoutMs?: number): Promise<Array<AwaitBgCommandResult | null>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`),this.finalizeTask(a,-1,null,"spawn_error")}),this.persistTask(a),{taskId:n,completion:
|
|
3
|
-
`),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),
|
|
1
|
+
import{spawn as E}from"node:child_process";import{promises as A}from"node:fs";import{createInterface as I}from"node:readline";import{randomBytes as S}from"node:crypto";import{prepareShellCommand as O,shellExecutable as R}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as M,decodeShellOutputChunk as w}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as D}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as F}from"./background-command-store.js";const z=1e3,G=10*60*1e3,B=50*1024*1024,C=16,b=256,N=256*1024,W=100,X=5e3,$=256,v=20,_=5*60*1e3,L=1024*1024;class q{constructor(){this.tasksByAgent=new Map,this.store=new F}globalCount(){let t=0;for(const i of this.tasksByAgent.values())t+=i.size;return t}startCommand(t){const i=t.agentId,e=this.getOrCreateBucket(i);if(e.size>=C)throw new x("too_many_tasks",`Agent ${i} has ${e.size} background tasks (limit ${C}). Kill or wait for completion first.`);if(this.globalCount()>=b)throw new x("global_limit_reached",`Global background task limit ${b} reached.`);const n=this.generateTaskId(e),o=t.timeoutMs&&t.timeoutMs>0?Math.min(t.timeoutMs,G):0,s=typeof t.inactivityTimeoutMs=="number"&&Number.isFinite(t.inactivityTimeoutMs)&&t.inactivityTimeoutMs>0?t.inactivityTimeoutMs:0,r=this.store.outputPathFor(i,n),u=t.commandText.length>80?`${t.commandText.slice(0,77)}...`:t.commandText,a={taskId:n,agentId:i,command:t.commandText,commandPreview:u,cwd:t.cwd,status:"running",startedAt:Date.now(),finishedAt:null,exitCode:null,signal:null,killedReason:null,timeoutMs:o,inactivityTimeoutMs:s,child:null,ringBuffer:new D,outputPath:r,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,outputSeq:0,outputSubscribers:new Set,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,inactivityTimer:null,inactivityTimedOut:!1,finalized:!1,stdoutDecoder:M(),stderrDecoder:M(),stdoutText:"",stderrText:"",commandLineCount:0},c=new Promise(d=>{a.completionResolve=d});return e.set(n,a),this.spawnAndAttach(a,t).catch(d=>{a.ringBuffer.push(`[ACode] spawn failed: ${d?.message??String(d)}
|
|
2
|
+
`),this.finalizeTask(a,-1,null,"spawn_error")}),this.persistTask(a),{taskId:n,completion:c}}async spawnAndAttach(t,i){const e=R(),n=await O(i.commandText,i.workspaceRoot,e),o=process.platform==="win32",s=E(e.command,[...e.argsPrefix,n.commandText],{cwd:i.cwd,env:process.env,detached:!o,stdio:["ignore","pipe","pipe"],windowsVerbatimArguments:o,windowsHide:!0});t.child=s,t.commandFilePath=n.commandFilePath,t.commandLineCount=n.commandLineCount,t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),s.stdout?.on("data",r=>{const u=w(t.stdoutDecoder,r);this.onOutput(t,"stdout",u)}),s.stderr?.on("data",r=>{const u=w(t.stderrDecoder,r);this.onOutput(t,"stderr",u)}),s.on("error",r=>{this.onOutput(t,"stderr",`[ACode] child error: ${r.message}
|
|
3
|
+
`),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),s.on("close",(r,u)=>{const a=w(t.stdoutDecoder,void 0,!0),c=w(t.stderrDecoder,void 0,!0);if(a&&this.onOutput(t,"stdout",a),c&&this.onOutput(t,"stderr",c),t.ringBuffer.flush(),!t.finalized){const d=t.killedReason??null;this.finalizeTask(t,r,u,d??void 0)}}),t.timeoutMs>0&&(t.timeoutTimer=setTimeout(()=>{t.finalized||(t.killedReason="timeout",this.killProcessGroup(s))},t.timeoutMs),t.timeoutTimer.unref?.())}disableInactivityTimeout(t,i){const e=this.getTask(t,i);return e?(e.inactivityTimeoutMs=0,e.inactivityTimer&&(clearTimeout(e.inactivityTimer),e.inactivityTimer=null),!0):!1}onOutput(t,i,e){if(!e)return;t.outputSeq++;const n=t.outputSeq;if(t.outputSubscribers.size>0)for(const r of t.outputSubscribers)try{r.onLine({stream:i,text:e,seq:n})}catch{}const o=i==="stdout"?"stdoutText":"stderrText";if(Buffer.byteLength(t[o],"utf-8")<L){const r=L-Buffer.byteLength(t[o],"utf-8");t[o]+=r>0?e.slice(0,r):""}if(t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),t.outputBytes>=B){if(!t.outputTruncated){t.outputTruncated=!0;const r=`
|
|
4
4
|
[ACode] output truncated at 50MB
|
|
5
|
-
`;t.ringBuffer.push(r),this.appendToFile(t,r)}t.ringBuffer.push(e);return}t.ringBuffer.push(e),t.onOutput?.({stream:i,text:e});const
|
|
6
|
-
`)}),t.outputWriteChain)}finalizeTask(t,i,e,n){if(t.finalized)return;t.finalized=!0,t.timeoutTimer&&(clearTimeout(t.timeoutTimer),t.timeoutTimer=null),t.killEscalationTimer&&(clearTimeout(t.killEscalationTimer),t.killEscalationTimer=null),t.inactivityTimer&&(clearTimeout(t.inactivityTimer),t.inactivityTimer=null),t.child=null,t.finishedAt=Date.now();let s;n?(s="killed",t.killedReason=n):i===0?s="completed":s="failed",t.status=s,t.exitCode=i,t.signal=e;const o=n==="inactivity"||t.inactivityTimedOut,r={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||o,inactivityTimedOut:o,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(r);const u={taskId:t.taskId,status:s,exitCode:i,signal:e,timedOut:n==="timeout"||o,aborted:!1,durationMs:t.finishedAt-t.startedAt},a=t.completionWaiters;t.completionWaiters=[];for(const l of a)l(u);if(this.persistTask(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:s,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const l=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const c of l)try{c.onDone({status:s,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},v),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(T.unlink(n.outputPath).catch(()=>{}),e.delete(i),e.size===0&&this.tasksByAgent.delete(t))}listTasks(t,i){const e=this.tasksByAgent.get(t);if(!e)return[];const n=[];for(const s of e.values())i&&i!=="all"&&s.status!==i||n.push(this.toSummary(s));return n.sort((s,o)=>o.startedAt-s.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const s=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:s,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let s=!0;return()=>{s&&(s=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),s=i.mode??"tail",o=Math.min(Math.max(1,i.lines??50),S);if(!n)return{taskId:e,status:"killed",mode:s,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,hasMore:!1,outputFile:""}};let r=[],u=!1;if(s==="head")r=n.ringBuffer.head(o);else if(s==="tail")r=n.ringBuffer.tail(o);else if(s==="offset"){const d=Math.max(0,i.offset??0),f=n.ringBuffer.offset(d,o);if(f.length>0||n.ringBuffer.hasLine(d))r=f;else{const h=await this.readFromFile(n,"offset",d,o,void 0);r=h.lines,u=h.hasMore}}else if(s==="grep"){const d=String(i.pattern??"");if(d.length>z)return{taskId:e,status:n.status,mode:s,lines:[],lineCount:0,metadata:{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:0,truncated:!1,hasMore:!1,outputFile:n.outputPath}};let f;try{f=new RegExp(d,"i")}catch{return{taskId:e,status:n.status,mode:s,lines:[],lineCount:0,metadata:{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:0,truncated:!1,hasMore:!1,outputFile:n.outputPath}}}const h=await this.readFromFile(n,"grep",0,o,f);r=h.lines,u=h.hasMore}let a=0;const l=[];let c=!1;for(const d of r){if(l.length>=S){c=!0,u=!0;break}const f=Buffer.byteLength(d,"utf-8")+1;if(a+f>D){c=!0,u=!0;break}l.push(d),a+=f}const m=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:s,lines:l,lineCount:l.length,metadata:m?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:a,truncated:c,hasMore:u,outputFile:n.outputPath}:void 0}}async readFromFile(t,i,e,n,s){const o=[];let r=!1,u=0,a=0;try{const l=await T.open(t.outputPath,"r"),c=_({input:l.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const m of c){if(i==="grep"&&o.length>=F){r=!0;break}if(i==="offset"){if(u<e){u++;continue}if(a>=n){r=!0;break}o.push(m),a++,u++}else{if(s.test(m)){if(a>=n){r=!0;break}o.push(m),a++}u++}}c.close(),await l.close()}catch{return{lines:[],hasMore:!1}}return{lines:o,hasMore:r}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(r=>{let u=null;const a=l=>{u&&clearTimeout(u),r(l)};n.completionWaiters.push(a),e&&e>0&&(u=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(l=>l!==a),r({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),u.unref?.())});const o=(await this.store.readSession(t))?.tasks.find(r=>r.taskId===i);return o?o.status!=="running"?{taskId:i,status:o.status,exitCode:o.exitCode,signal:o.signal,timedOut:o.killedReason==="timeout",aborted:!1,durationMs:(o.finishedAt??Date.now())-o.startedAt,...o.outputPath?{outputFilePath:o.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(o.finishedAt??Date.now())-o.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const s=e.get(n);if(!s)continue;const o=s.completionWaiters;if(o.length===0)continue;s.completionWaiters=[];const r={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-s.startedAt};for(const u of o)u(r)}}async killTask(t,i){const e=this.getTask(t,i);return e?e.status!=="running"?{taskId:i,status:e.status,exitCode:e.exitCode??void 0}:e.child?(e.killedReason="user_kill",this.killProcessGroup(e.child)?(e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.killEscalationTimer=setTimeout(()=>{e.child&&!e.finalized&&this.killProcessGroup(e.child,!0)},R),e.killEscalationTimer.unref?.(),{taskId:i,status:"killed",signal:"SIGTERM"}):{taskId:i,status:e.status,error:"kill_failed",reason:"permission_denied"}):{taskId:i,status:e.status,error:"kill_failed",reason:"no_child"}:{taskId:i,status:"killed",error:"task_not_found"}}killProcessGroup(t,i=!1){const e=t.pid;if(!e)return!1;try{if(process.platform==="win32"){const n="/F",{spawnSync:s}=require("node:child_process");return s("taskkill",["/PID",String(e),"/T",n],{windowsHide:!0}),!0}try{process.kill(-e,i?"SIGKILL":"SIGTERM")}catch{process.kill(e,i?"SIGKILL":"SIGTERM")}return!0}catch{return!1}}async disposeAgent(t){const i=this.tasksByAgent.get(t);if(i){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="session_disposed",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}await this.store.deleteAgent(t)}async disposeAll(){for(const[t,i]of this.tasksByAgent){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="process_exit",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}}async reconcileOrphans(t){const i=await this.store.readSession(t);if(!i||i.tasks.length===0)return;let e=!1;for(const n of i.tasks)n.status==="running"&&(n.status="killed",n.killedReason="runtime_crashed",n.finishedAt=Date.now(),n.notified=!0,e=!0);e&&await this.store.persistSession(t,i.tasks)}async readSessionState(t){return this.store.readSession(t)}async markNotified(t,i){await this.store.markNotified(t,i)}getOrCreateBucket(t){let i=this.tasksByAgent.get(t);return i||(i=new Map,this.tasksByAgent.set(t,i)),i}getTask(t,i){const e=this.tasksByAgent.get(t);if(e&&/^[A-Za-z0-9]{8}$/.test(i))return e.get(i)}generateTaskId(t){for(let i=0;i<3;i++){const e=g(4).toString("hex").slice(0,8);if(!t.has(e))return e}return g(6).toString("hex").slice(0,8)}async persistTask(t){const i=await this.store.readSession(t.agentId)??{agentId:t.agentId,updatedAt:0,tasks:[]},e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,cwd:t.cwd,status:t.status,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputPath:t.outputPath,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,notified:t.notified,killedReason:t.killedReason},n=i.tasks.findIndex(o=>o.taskId===t.taskId);n>=0?i.tasks[n]=e:i.tasks.push(e);const s=Date.now();i.tasks=i.tasks.filter(o=>o.status==="running"||!o.finishedAt||s-o.finishedAt<v),await this.store.persistSession(t.agentId,i.tasks)}}class b extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}const U=new G;export{G as BackgroundCommandManager,b as BackgroundTaskError,U as backgroundCommandManager};
|
|
5
|
+
`;t.ringBuffer.push(r),this.appendToFile(t,r)}t.ringBuffer.push(e);return}t.ringBuffer.push(e),t.onOutput?.({stream:i,text:e});const s=(e.match(/\n/g)??[]).length;t.outputLineCount+=s,t.outputBytes+=Buffer.byteLength(e,"utf-8"),this.appendToFile(t,e)}scheduleInactivity(t){t.finalized||t.inactivityTimeoutMs<=0||(t.inactivityTimer&&clearTimeout(t.inactivityTimer),t.inactivityTimer=setTimeout(()=>{t.finalized||!t.child||(t.inactivityTimedOut=!0,t.killedReason="inactivity",this.killProcessGroup(t.child))},t.inactivityTimeoutMs),t.inactivityTimer.unref?.())}appendToFile(t,i){return t.outputBytes>B?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>A.appendFile(t.outputPath,i,"utf-8")).catch(e=>{t.ringBuffer.push(`[ACode] output write failed: ${e?.message??e}
|
|
6
|
+
`)}),t.outputWriteChain)}finalizeTask(t,i,e,n){if(t.finalized)return;t.finalized=!0,t.timeoutTimer&&(clearTimeout(t.timeoutTimer),t.timeoutTimer=null),t.killEscalationTimer&&(clearTimeout(t.killEscalationTimer),t.killEscalationTimer=null),t.inactivityTimer&&(clearTimeout(t.inactivityTimer),t.inactivityTimer=null),t.child=null,t.finishedAt=Date.now();let o;n?(o="killed",t.killedReason=n):i===0?o="completed":o="failed",t.status=o,t.exitCode=i,t.signal=e;const s=n==="inactivity"||t.inactivityTimedOut,r={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||s,inactivityTimedOut:s,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(r);const u={taskId:t.taskId,status:o,exitCode:i,signal:e,timedOut:n==="timeout"||s,aborted:!1,durationMs:t.finishedAt-t.startedAt},a=t.completionWaiters;t.completionWaiters=[];for(const c of a)c(u);if(this.persistTask(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:o,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const c=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const d of c)try{d.onDone({status:o,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},_),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(A.unlink(n.outputPath).catch(()=>{}),e.delete(i),e.size===0&&this.tasksByAgent.delete(t))}listTasks(t,i){const e=this.tasksByAgent.get(t);if(!e)return[];const n=[];for(const o of e.values())i&&i!=="all"&&o.status!==i||n.push(this.toSummary(o));return n.sort((o,s)=>s.startedAt-o.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const o=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:o,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let o=!0;return()=>{o&&(o=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),o=i.mode==="lines"||i.mode==="grep"?i.mode:"tail",s=Math.min(Math.max(1,i.line_size??100),W),r=Number.isFinite(i.start_line)&&(i.start_line??0)>0?Math.floor(i.start_line??0):0;if(!n)return{taskId:e,status:"killed",mode:o,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,outputTruncated:!1,hasMore:!1,nextStartLine:null,outputFile:""}};let u=[],a=!1,c=null;if(o==="tail"){const m=n.ringBuffer.tail(s),f=Math.max(0,n.outputLineCount-m.length);u=m.map((l,g)=>({line:f+g,text:l})),c=null}else if(o==="lines"){const m=n.ringBuffer.firstLineIndex(),f=m+n.ringBuffer.getLineCount();if(r>=m&&r<f)u=n.ringBuffer.offset(r,s).map((g,h)=>({line:r+h,text:g})),a=r+u.length<f,c=a?r+u.length:null;else if(r<f){const l=await this.readFromFile(n,"lines",r,s,0,0,void 0);u=l.rows,a=l.hasMore,c=l.nextStartLine}}else{const m=String(i.pattern??"");if(!m||m.length>$)return this._emptyResult(e,n,o);let f;try{f=new RegExp(m,"i")}catch{return this._emptyResult(e,n,o)}const l=Math.min(Math.max(0,i.context_before??0),v),g=Math.min(Math.max(0,i.context_after??0),v),h=await this.readFromFile(n,"grep",r,s,l,g,f);u=h.rows,a=h.hasMore,c=h.nextStartLine}const d=[];let p=0,T=!1;for(const m of u){const f=Buffer.byteLength(m.text,"utf-8")+1;if(d.length>=s||p+f>N){T=!0,a=!0;break}d.push(m),p+=f}const y=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:o,lines:d,lineCount:d.length,metadata:y?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:p,truncated:T,outputTruncated:n.outputTruncated,hasMore:a,nextStartLine:a?c:null,outputFile:n.outputPath}:void 0}}_emptyResult(t,i,e){return{taskId:t,status:i.status,mode:e,lines:[],lineCount:0,metadata:{totalLines:i.outputLineCount,totalBytes:i.outputBytes,returnedBytes:0,truncated:!1,outputTruncated:i.outputTruncated,hasMore:!1,nextStartLine:null,outputFile:i.outputPath}}}async readFromFile(t,i,e,n,o,s,r){const u=[],a=new Set;let c=0,d=!1;const p=[];let T=-1/0;try{const y=await A.open(t.outputPath,"r"),m=I({input:y.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const f of m){const l=c;if(c++,i==="grep"&&l-e>=X&&l>=e){d=!0;break}if(l>=e){if(i==="lines"){if(u.length>=n){d=!0;break}a.has(l)||(a.add(l),u.push({line:l,text:f}))}else if(r.test(f)){for(const h of p)h.line>=e&&!a.has(h.line)&&(a.add(h.line),u.push(h));if(a.has(l)||(a.add(l),u.push({line:l,text:f})),T=l,u.length>=n){d=!0;break}}else if(o>0&&(p.push({line:l,text:f}),p.length>o&&p.shift()),T>=e&&l-T<=s&&!a.has(l)&&(a.add(l),u.push({line:l,text:f}),u.length>=n)){d=!0;break}}}m.close(),await y.close()}catch{return{rows:[],hasMore:!1,nextStartLine:null}}return d?{rows:u,hasMore:d,nextStartLine:c}:{rows:u,hasMore:d,nextStartLine:null}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(r=>{let u=null;const a=c=>{u&&clearTimeout(u),r(c)};n.completionWaiters.push(a),e&&e>0&&(u=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(c=>c!==a),r({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),u.unref?.())});const s=(await this.store.readSession(t))?.tasks.find(r=>r.taskId===i);return s?s.status!=="running"?{taskId:i,status:s.status,exitCode:s.exitCode,signal:s.signal,timedOut:s.killedReason==="timeout",aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt,...s.outputPath?{outputFilePath:s.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const o=e.get(n);if(!o)continue;const s=o.completionWaiters;if(s.length===0)continue;o.completionWaiters=[];const r={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-o.startedAt};for(const u of s)u(r)}}async killTask(t,i){const e=this.getTask(t,i);return e?e.status!=="running"?{taskId:i,status:e.status,exitCode:e.exitCode??void 0}:e.child?(e.killedReason="user_kill",this.killProcessGroup(e.child)?(e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.killEscalationTimer=setTimeout(()=>{e.child&&!e.finalized&&this.killProcessGroup(e.child,!0)},z),e.killEscalationTimer.unref?.(),{taskId:i,status:"killed",signal:"SIGTERM"}):{taskId:i,status:e.status,error:"kill_failed",reason:"permission_denied"}):{taskId:i,status:e.status,error:"kill_failed",reason:"no_child"}:{taskId:i,status:"killed",error:"task_not_found"}}killProcessGroup(t,i=!1){const e=t.pid;if(!e)return!1;try{if(process.platform==="win32"){const n="/F",{spawnSync:o}=require("node:child_process");return o("taskkill",["/PID",String(e),"/T",n],{windowsHide:!0}),!0}try{process.kill(-e,i?"SIGKILL":"SIGTERM")}catch{process.kill(e,i?"SIGKILL":"SIGTERM")}return!0}catch{return!1}}async disposeAgent(t){const i=this.tasksByAgent.get(t);if(i){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="session_disposed",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}await this.store.deleteAgent(t)}async disposeAll(){for(const[t,i]of this.tasksByAgent){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="process_exit",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}}async reconcileOrphans(t){const i=await this.store.readSession(t);if(!i||i.tasks.length===0)return;let e=!1;for(const n of i.tasks)n.status==="running"&&(n.status="killed",n.killedReason="runtime_crashed",n.finishedAt=Date.now(),n.notified=!0,e=!0);e&&await this.store.persistSession(t,i.tasks)}async readSessionState(t){return this.store.readSession(t)}async markNotified(t,i){await this.store.markNotified(t,i)}getOrCreateBucket(t){let i=this.tasksByAgent.get(t);return i||(i=new Map,this.tasksByAgent.set(t,i)),i}getTask(t,i){const e=this.tasksByAgent.get(t);if(e&&/^[A-Za-z0-9]{8}$/.test(i))return e.get(i)}generateTaskId(t){for(let i=0;i<3;i++){const e=S(4).toString("hex").slice(0,8);if(!t.has(e))return e}return S(6).toString("hex").slice(0,8)}async persistTask(t){const i=await this.store.readSession(t.agentId)??{agentId:t.agentId,updatedAt:0,tasks:[]},e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,cwd:t.cwd,status:t.status,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputPath:t.outputPath,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,notified:t.notified,killedReason:t.killedReason},n=i.tasks.findIndex(s=>s.taskId===t.taskId);n>=0?i.tasks[n]=e:i.tasks.push(e);const o=Date.now();i.tasks=i.tasks.filter(s=>s.status==="running"||!s.finishedAt||o-s.finishedAt<_),await this.store.persistSession(t.agentId,i.tasks)}}class x extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}const J=new q;export{q as BackgroundCommandManager,x as BackgroundTaskError,J as backgroundCommandManager};
|
|
7
7
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
const
|
|
1
|
+
const r=`List all background commands for the current agent, including running and recently completed ones.
|
|
2
2
|
|
|
3
3
|
Returns task_id, status, command (preview), startedAt, finishedAt, exitCode, outputLineCount, outputBytes.
|
|
4
|
-
Does NOT include output content (use read_background_output to fetch output).`,
|
|
4
|
+
Does NOT include output content (use read_background_output to fetch output).`,s=`Terminate a running background command by task_id.
|
|
5
5
|
|
|
6
6
|
Sends SIGTERM to the process group, then SIGKILL after 1s if still alive.
|
|
7
7
|
No-op (idempotent) if the task already finished.
|
|
8
|
-
Cross-agent task_id returns task_not_found (does not leak existence).`,
|
|
8
|
+
Cross-agent task_id returns task_not_found (does not leak existence).`,d=`Read the output (stdout+stderr merged, interleaved in arrival order) of a background command.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- grep: filter lines by regex (JS syntax, case-insensitive)
|
|
10
|
+
Modes:
|
|
11
|
+
- lines: read a range by absolute line numbers (start_line + line_size). Use to fetch a window, or to walk forward by passing the returned nextStartLine back as start_line.
|
|
12
|
+
- grep: search matching lines with optional context_before/context_after. start_line is the scan start (resume paging by passing nextStartLine).
|
|
13
|
+
- tail (default): last N lines (live view; no paging anchor).
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Every returned line is { line, text }: line is the 0-based absolute line number in the command output. Use the line number to locate the same content in the output / files, and lines(start_line=..., line_size=...) to expand around a match.
|
|
16
|
+
Output capped at 256KB / line_size lines per call; truncated=true means more is available (grep: continue from nextStartLine).`;function u(a,o){return{kind:"builtin",toolName:"list_background_commands",exposedName:"list_background_commands",description:r,inputSchema:{type:"object",properties:{status_filter:{type:"string",enum:["running","completed","failed","killed","all"],description:"Filter by status. Defaults to 'all'.",default:"all"}},additionalProperties:!1},async execute(e){const n=typeof e.status_filter=="string"?e.status_filter:"all",i=o.manager.listTasks(a,n);return{title:`${i.length} background commands`,output:JSON.stringify(i.map(t=>({taskId:t.taskId,status:t.status,command:t.commandPreview,cwd:t.cwd,startedAt:new Date(t.startedAt).toISOString(),finishedAt:t.finishedAt?new Date(t.finishedAt).toISOString():null,exitCode:t.exitCode,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes})),null,2),metadata:{tasks:i,count:i.length}}}}}function l(a,o){return{kind:"builtin",toolName:"kill_background_command",exposedName:"kill_background_command",description:s,inputSchema:{type:"object",properties:{task_id:{type:"string",description:"The task_id returned by Bash(wait_seconds=...) or list_background_commands."}},required:["task_id"],additionalProperties:!1},async execute(e){const n=typeof e.task_id=="string"?e.task_id:"",i=await o.manager.killTask(a,n);return{title:`kill ${n}: ${i.status}`,output:JSON.stringify(i,null,2),metadata:i}}}}function c(a,o){return{kind:"builtin",toolName:"read_background_output",exposedName:"read_background_output",description:d,inputSchema:{type:"object",properties:{task_id:{type:"string",description:"The task_id to read output from."},mode:{type:"string",enum:["lines","grep","tail"],description:"lines=read range by absolute line numbers, grep=search matching lines with context, tail=last N lines (default).",default:"tail"},line_size:{type:"integer",description:"lines/tail: number of lines to return; grep: max total lines returned (including context). Default 100, max 100.",default:100,minimum:1,maximum:100},start_line:{type:"integer",description:"lines: 0-based absolute line to start from; grep: scan start line (pass nextStartLine to page).",minimum:0},pattern:{type:"string",description:"grep: regex pattern (JS syntax, case-insensitive)."},context_before:{type:"integer",description:"grep: lines of context before each match (0-20, default 0).",minimum:0,maximum:20,default:0},context_after:{type:"integer",description:"grep: lines of context after each match (0-20, default 0).",minimum:0,maximum:20,default:0},include_metadata:{type:"boolean",description:"Include totalLines/totalBytes/truncated/outputTruncated/hasMore/nextStartLine/outputFile in response.",default:!0}},required:["task_id"],additionalProperties:!1},async execute(e){const n=await o.manager.readOutput(a,{task_id:typeof e.task_id=="string"?e.task_id:"",mode:e.mode==="lines"||e.mode==="grep"||e.mode==="tail"?e.mode:"tail",line_size:typeof e.line_size=="number"?e.line_size:void 0,start_line:typeof e.start_line=="number"?e.start_line:void 0,pattern:typeof e.pattern=="string"?e.pattern:void 0,context_before:typeof e.context_before=="number"?e.context_before:void 0,context_after:typeof e.context_after=="number"?e.context_after:void 0,include_metadata:e.include_metadata!==!1});return{title:`read ${n.taskId} (${n.mode}): ${n.lineCount} lines`,output:n.lines.map(({line:i,text:t})=>`${i}: ${t}`).join(`
|
|
17
|
+
`),metadata:n}}}}export{l as createKillBackgroundCommandTool,u as createListBackgroundCommandsTool,c as createReadBackgroundOutputTool};
|
|
18
18
|
|
|
@@ -4,6 +4,8 @@ export declare class BackgroundOutputRingBuffer {
|
|
|
4
4
|
private bytes;
|
|
5
5
|
private partialLine;
|
|
6
6
|
|
|
7
|
+
private droppedLines;
|
|
8
|
+
|
|
7
9
|
push(text: string): void;
|
|
8
10
|
|
|
9
11
|
flush(): void;
|
|
@@ -13,11 +15,11 @@ export declare class BackgroundOutputRingBuffer {
|
|
|
13
15
|
|
|
14
16
|
tail(n: number): string[];
|
|
15
17
|
|
|
16
|
-
offset(
|
|
18
|
+
offset(globalStart: number, limit: number): string[];
|
|
19
|
+
|
|
20
|
+
firstLineIndex(): number;
|
|
17
21
|
|
|
18
22
|
getLineCount(): number;
|
|
19
23
|
|
|
20
24
|
getByteCount(): number;
|
|
21
|
-
|
|
22
|
-
hasLine(lineIndex: number): boolean;
|
|
23
25
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const n=1e3,h=262144;class r{constructor(){this.lines=[],this.bytes=0,this.partialLine=""}push(t){if(!t)return;this.partialLine+=t;const
|
|
2
|
-
`);this.partialLine=
|
|
1
|
+
const n=1e3,h=262144;class r{constructor(){this.lines=[],this.bytes=0,this.partialLine="",this.droppedLines=0}push(t){if(!t)return;this.partialLine+=t;const e=this.partialLine.split(`
|
|
2
|
+
`);this.partialLine=e.pop()??"";for(const i of e)this.lines.push(i),this.bytes+=Buffer.byteLength(i,"utf-8");this.enforceLimits()}flush(){this.partialLine&&(this.lines.push(this.partialLine),this.bytes+=Buffer.byteLength(this.partialLine,"utf-8"),this.partialLine="",this.enforceLimits())}enforceLimits(){for(;this.lines.length>1e3;){const t=this.lines.shift();t!==void 0&&(this.bytes-=Buffer.byteLength(t,"utf-8"),this.droppedLines++)}for(;this.bytes>262144&&this.lines.length>1;){const t=this.lines.shift();t!==void 0&&(this.bytes-=Buffer.byteLength(t,"utf-8"),this.droppedLines++)}this.bytes<0&&(this.bytes=0)}head(t){return this.lines.slice(0,Math.max(0,t))}tail(t){const e=Math.max(0,this.lines.length-t);return this.lines.slice(e)}offset(t,e){const i=t-this.droppedLines;return i<0||i>=this.lines.length?[]:this.lines.slice(i,i+Math.max(0,e))}firstLineIndex(){return this.droppedLines}getLineCount(){return this.lines.length}getByteCount(){return this.bytes+Buffer.byteLength(this.partialLine,"utf-8")}}export{r as BackgroundOutputRingBuffer};
|
|
3
3
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as it}from"node:crypto";import{promises as w,createReadStream as X}from"node:fs";import at from"node:os";import m from"node:path";import Y from"node:readline";import{runShellCommand as rt}from"./builtins/shellCommandRunner.js";import{createOutputEventCollector as st}from"./builtins/commandOutputEvents.js";import{buildCommandOutputSpilloverContent as ct,COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT as lt,countOutputLines as H,formatCommandOutputSpilloverStdout as ut,writeCommandArtifactFile as dt}from"./builtins/commandArtifacts.js";import{symbolsTool as mt}from"./built-in-symbols-tool.js";import{createListBackgroundCommandsTool as ht,createKillBackgroundCommandTool as ft,createReadBackgroundOutputTool as pt}from"./background-command-tools.js";import{detectImageFormatFromBuffer as wt,formatBytes as x,imageMediaType as K,readImageDimensions as gt,tryResizeWithSharp as _t}from"./image-utils.js";const yt=2e3,L=2e3,bt=50*1024,V=4096,kt=.3,Tt=new Set([".png",".jpg",".jpeg",".gif",".bmp",".ico",".webp",".tiff",".tif",".zip",".gz",".tar",".rar",".7z",".bz2",".xz",".lz",".exe",".dll",".so",".dylib",".bin",".o",".a",".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".mp3",".mp4",".avi",".mov",".wav",".flv",".mkv",".ogg",".woff",".woff2",".ttf",".otf",".eot",".class",".jar",".war",".ear",".pyc",".pyo",".node",".sqlite",".db",".mdb",".wasm"]),Pt=new Set(["png","jpg","jpeg","gif","webp"]),Z=20*1024*1024,A=10*1024*1024,D=Math.floor(3.75*1024*1024),U=2e3,Et=15e3,vt=1e3,xt=1024*1024,$t=256*1024,Rt=1e3,St=new Set([".git","node_modules","dist","build",".next",".nuxt",".cache",".turbo",".output","coverage",".nyc_output"]),re={fileReadOutside:"allow",commandExecution:"allow",fileModification:"allow"};async function B(n,o,e){if(!n)return;const t=n.getLevel(o);if(t==="deny")throw new Error(`Operation blocked by tool permission policy (${o} = deny). Explain this restriction to the user if needed.`);if(t==="ask"&&!await n.requestPermission(o,e))throw new Error(`Operation denied by the user (${o}). Do not retry the same operation without asking the user.`)}function $(n){return n.replace(/\\/g,"/")}async function J(n){try{return await w.access(n),!0}catch{return!1}}function Mt(n,o){if(!o)return 0;let e=0,t=n.indexOf(o);for(;t>=0;)e+=1,t=n.indexOf(o,t+o.length);return e}function S(n,o,e){for(const t of o){const i=n[t];if(typeof i=="string")return i}throw new Error(`${e} is required`)}function I(n,o){for(const e of o){const t=n[e];if(typeof t=="string"&&t.trim())return t}}function C(n,o,e){for(const t of o){const i=n[t],s=typeof i=="number"?i:typeof i=="string"?Number(i):NaN;if(Number.isFinite(s)&&s>0)return Math.floor(s)}return e}async function Q(n){const o=m.extname(n).toLowerCase();if(Tt.has(o))return!0;const e=await w.open(n,"r");try{const t=Buffer.alloc(V),{bytesRead:i}=await e.read(t,0,V,0);if(i===0)return!1;const s=t.subarray(0,i);if(s.includes(0))return!0;let a=0;for(let r=0;r<i;r++){const c=s[r];(c<9||c>13&&c<32)&&a++}return a/i>kt}finally{await e.close()}}function O(n,o){const e=m.resolve(o),t=m.relative(n,e);if(t.startsWith("..")||m.isAbsolute(t))throw new Error(`Target path is outside workspace: ${e}`);return e}function W(n){return n==="~"||n.startsWith("~/")?m.join(at.homedir(),n.slice(1)):n}async function It(n,o){const e=m.resolve(String(n||"").trim()),t=await w.realpath(e);if(!(await w.stat(t)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);if(!o?.trim())return{workspaceRoot:t,cwd:t};const s=W(o.trim()),a=m.isAbsolute(s)?m.resolve(s):m.resolve(e,s),r=m.relative(e,a);if(r.startsWith("..")||m.isAbsolute(r))throw new Error(`Command cwd is outside workspace: ${a}`);const c=O(t,await w.realpath(a));if(!(await w.stat(c)).isDirectory())throw new Error(`Command cwd is not a directory: ${c}`);return{workspaceRoot:t,cwd:c}}async function Ot(n,o){const e=await w.realpath(m.dirname(o));O(n,e);try{if((await w.lstat(o)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${o}`)}catch(t){if((typeof t=="object"&&t!==null?Reflect.get(t,"code"):void 0)!=="ENOENT")throw t}}async function F(n,o){const e=m.resolve(String(n||"").trim()),t=await w.realpath(e);if(!(await w.stat(t)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);const s=W(o.trim());if(!s)throw new Error("file_path is required");const a=m.isAbsolute(s)?m.resolve(s):m.resolve(e,s),r=m.relative(e,a);if(r.startsWith("..")||m.isAbsolute(r))throw new Error(`Target path is outside workspace: ${a}`);return{workspaceRoot:t,requestedPath:a,targetPath:O(t,m.join(t,r))}}async function tt(n,o,e){const t=m.resolve(String(n||"").trim()),i=await w.realpath(t);if(!(await w.stat(i)).isDirectory())throw new Error(`Workspace path is not a directory: ${t}`);const a=W(o.trim());if(!a)throw new Error("file_path is required");const r=m.isAbsolute(a)?m.resolve(a):m.resolve(t,a),c=m.relative(t,r),u=await w.realpath(r).catch(()=>r),l=m.relative(i,u);if(c.startsWith("..")||m.isAbsolute(c)||l.startsWith("..")||m.isAbsolute(l)){if(!e)throw new Error(`Target path is outside workspace: ${r}`);return await B(e,"fileReadOutside",{path:r}),{workspaceRoot:i,requestedPath:r,targetPath:u}}return{workspaceRoot:i,requestedPath:r,targetPath:O(i,u)}}async function et(n,o,e){const t=await F(n,o),i=m.dirname(t.targetPath),s=!await J(i);await w.mkdir(i,{recursive:!0}),await Ot(t.workspaceRoot,t.targetPath);const a=m.join(i,`.${m.basename(t.targetPath)}.${process.pid}.${it()}.tmp`);try{await w.writeFile(a,e,"utf-8"),await w.rename(a,t.targetPath)}catch(r){throw await w.rm(a,{force:!0}),r}return{workspaceRoot:t.workspaceRoot,targetPath:t.targetPath,bytes:Buffer.byteLength(e,"utf-8"),createdParentDirectory:s}}async function Lt(n,o){const e=await F(n,o),t=O(e.workspaceRoot,await w.realpath(e.targetPath));if(!(await w.stat(t)).isFile())throw new Error(`Path is not a file: ${t}`);return{workspaceRoot:e.workspaceRoot,targetPath:t,content:await w.readFile(t,"utf-8")}}async function At(n,o,e,t,i){const s=await tt(n,o,i),a=s.targetPath;if(!(await w.stat(a)).isFile())throw new Error(`Path is not a file: ${a}`);if(await Q(a))return{workspaceRoot:s.workspaceRoot,targetPath:a,content:"[Binary file, content skipped]",isBinary:!0,truncated:!1,totalLines:0,shownFromLine:0,shownToLine:0};const c=X(a,{encoding:"utf-8"}),u=Y.createInterface({input:c,crlfDelay:1/0});try{const l=[];let g=0,h=0,_=0,d=!1,E=!1;const f=e-1;for await(const p of u){if(h++,g<f){g++;continue}if(l.length>=t){d=!0,g++;continue}let v=p;v.length>L&&(v=v.slice(0,L)+`... (line truncated to ${L} chars)`);const R=`${g+1}: ${v}`,T=Buffer.byteLength(R,"utf-8")+1;if(_+T>bt){E=!0;break}l.push(R),_+=T,g++}if(h>0&&l.length===0&&!d&&!E&&f>=h)throw new Error(`offset ${e} exceeds total line count ${h} in file: ${a}`);const b=l.length>0?e:0,y=l.length>0?e+l.length-1:0;let k;E?k=`Output capped at 50 KB. Showing lines ${b}-${y}. Use offset=${y+1} to continue.`:d?k=`Showing lines ${b}-${y} of ${h}. Use offset=${y+1} to continue.`:k=`End of file - total ${h} lines`;const P=l.length>0?l.join(`
|
|
2
2
|
`)+`
|
|
3
|
-
`+E:E;return{workspaceRoot:s.workspaceRoot,targetPath:a,content:P,isBinary:!1,truncated:h||k,totalLines:k?-1:p,shownFromLine:y,shownToLine:b}}finally{u.close(),c.destroy()}}function W(n){return/^https?:\/\//i.test(n)}async function It(n){let o;try{o=new URL(n)}catch{throw new Error(`Invalid image URL: ${n}`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`Unsupported image URL protocol: ${o.protocol} (only http/https)`);let e;try{e=await fetch(n,{signal:AbortSignal.timeout(yt)})}catch(u){const l=u instanceof Error?u.message:String(u);throw new Error(`Image URL fetch failed: ${n} (${l})`)}if(!e.ok)throw new Error(`Image URL fetch failed: ${n} (HTTP ${e.status})`);const t=(e.headers.get("content-type")||"").split(";")[0]?.trim().toLowerCase()||"";if(t&&!t.startsWith("image/"))throw new Error(`URL did not return an image (content-type: ${t}): ${n}`);const i=Number(e.headers.get("content-length")||"");if(Number.isFinite(i)&&i>L)throw new Error(`Image too large to fetch: ${n} (${x(i)} > ${x(L)})`);const s=e.body;if(!s){const u=await e.arrayBuffer();if(u.byteLength>L)throw new Error(`Image too large to fetch: ${n} (${x(u.byteLength)} > ${x(L)})`);return Buffer.from(u)}const a=s.getReader(),r=[];let c=0;for(;;){const{done:u,value:l}=await a.read();if(u)break;if(l){if(c+=l.byteLength,c>L)throw a.cancel().catch(()=>{}),new Error(`Image too large to fetch: ${n} (${x(c)} > ${x(L)})`);r.push(Buffer.from(l))}}return Buffer.concat(r,c)}async function Mt(n,o,e){let t,i;if(W(o))t=await It(o),i=o;else{const b=(await J(n,o,e)).targetPath,E=await m.stat(b);if(!E.isFile())throw new Error(`Path is not a file: ${b}`);if(E.size>K)throw new Error(`Image file too large to read: ${$(b)} (${x(E.size)} > ${x(K)}). Downscale it first.`);t=await m.readFile(b),i=$(b)}if(t.length===0)throw new Error(`Image file is empty: ${i}`);const s=dt(t);if(!s)throw new Error(`Unsupported or corrupt image (unrecognized format): ${i}`);const a=ht(t);let r=s,c=t,u=a?.width,l=a?.height,w=!1;const p=await pt(t,s,{maxWidth:C,maxHeight:C,maxBytes:U});if(p)c=p.buffer,r=p.mediaType,u=p.width,l=p.height,w=p.changed;else if(t.length>U)throw new Error(`Image too large to inline: ${i} (${x(t.length)} > ${x(U)}). Downscale it first, e.g. via Bash: magick "${o}" -resize ${C}x${C} "${o}". Installing sharp enables automatic downscaling.`);const _=u&&l?`${u}x${l}`:"unknown dimensions",h=w&&a&&a.width!==u?` (resized from ${a.width}x${a.height})`:w?" (resized)":"",k=`[Image: ${i}, ${_}${h}, ${x(c.length)}]`,f=W(o)?d.basename(new URL(o).pathname)||"image":d.basename(i);return{ok:!0,filePath:i,content:k,isBinary:!1,image:{mediaType:Y(r),width:u??null,height:l??null,originalWidth:a?.width??null,originalHeight:a?.height??null,resized:w,size:c.length},attachments:[{type:"image",name:f,mediaType:Y(r),base64Data:c.toString("base64")}]}}function tt(n){let o="",e=0;for(;e<n.length;){const t=n[e];if(t==="*")o+="[^/]*";else if(t==="?")o+="[^/]";else if(t==="{"){const i=n.indexOf("}",e);if(i>=0){const s=n.slice(e+1,i);o+=`(${s.split(",").join("|")})`,e=i}else o+="\\{"}else if(t==="["){const i=n.indexOf("]",e);i>=0?(o+=n.slice(e,i+1),e=i):o+="\\["}else"+^$.()|\\".includes(t)?o+="\\"+t:o+=t;e++}return new RegExp(`^${o}$`)}async function Ot(n,o,e,t){const i=[];async function s(a){if(i.length>=t)return;const r=await m.readdir(a,{withFileTypes:!0});for(const c of r){if(i.length>=t)return;if(c.name.startsWith(".")&&c.isDirectory())continue;const u=d.join(a,c.name);if(c.isDirectory()){if(vt.has(c.name))continue;await s(u)}else if(c.isFile()){if(o&&!o.test(c.name)||e&&e.test(c.name))continue;i.push(u)}}}return await s(n),i}async function Lt(n,o,e){if((await m.stat(n)).size>kt)return{matches:[],count:0};if(await Z(n))return{matches:[],count:0};const i=d.relative(o,n).replace(/\\/g,"/"),s=[];let a=0;const r=G(n,{encoding:"utf-8"}),c=X.createInterface({input:r,crlfDelay:1/0});try{let u=0;for await(const l of c)if(u++,e.regex.test(l)){if(a++,e.collectContent){let w=l;if(w.length>O&&(w=w.slice(0,O)+`... (line truncated to ${O} chars)`),s.push({file:i,line:u,content:w}),s.length>=e.maxResults)break}if(e.stopOnFirstMatch)break}}finally{c.close(),r.destroy()}return{matches:s,count:a}}const et=10*6e4,At=et,Ft=6e4,Bt=10*6e4;function Nt(n,o,e){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",description:"Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to write."},content:{type:"string",description:"Complete UTF-8 file content."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","content"],additionalProperties:!1},async execute(t){const i=R(t,["file_path","filePath"],"file_path"),s=R(t,["content"],"content"),a=I(t,["reason"]),r=await A(n,i);await F(e,"fileModification",{operation:"write",path:r.targetPath});const c=await V(r.targetPath),u=c?await m.readFile(r.targetPath,"utf-8").catch(()=>null):null,l=await Q(n,i,s);if(o){const w=d.relative(l.workspaceRoot,l.targetPath).replace(/\\/g,"/");await o({filePath:w,operation:c?"UPDATE":"CREATE",beforeContent:u,afterContent:s,reason:a})}return{ok:!0,filePath:$(l.targetPath),bytes:l.bytes,createdParentDirectory:l.createdParentDirectory}}}}function Ct(n,o,e){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",description:"Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to edit."},old_string:{type:"string",description:"Existing text to replace. Must occur exactly once."},new_string:{type:"string",description:"Replacement text."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","old_string","new_string"],additionalProperties:!1},async execute(t){const i=R(t,["file_path","filePath"],"file_path"),s=R(t,["old_string","oldString"],"old_string"),a=R(t,["new_string","newString"],"new_string"),r=I(t,["reason"]);if(!s)throw new Error("old_string must not be empty");const c=await A(n,i);await F(e,"fileModification",{operation:"edit",path:c.targetPath});const u=await Rt(n,i),l=xt(u.content,s);if(l===0)throw new Error("old_string was not found in file");if(l>1)throw new Error(`old_string occurs ${l} times; provide a unique match`);const w=u.content.replace(s,a),p=await Q(n,i,w);if(o){const _=d.relative(p.workspaceRoot,p.targetPath).replace(/\\/g,"/");await o({filePath:_,operation:"UPDATE",beforeContent:u.content,afterContent:w,reason:r})}return{ok:!0,filePath:$(p.targetPath),bytes:p.bytes}}}}function Ut(n,o,e){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",description:"Delete a file inside the current workspace. Returns an error if the file does not exist.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to delete."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path"],additionalProperties:!1},async execute(t){const i=R(t,["file_path","filePath"],"file_path"),s=I(t,["reason"]),a=await A(n,i);await F(e,"fileModification",{operation:"delete",path:a.targetPath});const r=M(a.workspaceRoot,await m.realpath(a.targetPath).catch(()=>a.targetPath)),c=await m.stat(r).catch(()=>null);if(!c)throw new Error(`File does not exist: ${r}`);if(!c.isFile())throw new Error(`Path is not a file: ${r}`);const u=await m.readFile(r,"utf-8").catch(()=>null);if(await m.rm(r,{force:!0}),o){const l=d.relative(a.workspaceRoot,r).replace(/\\/g,"/");await o({filePath:l,operation:"DELETE",beforeContent:u,afterContent:null,reason:s})}return{ok:!0,filePath:$(r),deleted:!0}}}}function Dt(n,o){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",description:"Read a UTF-8 text file inside the current workspace. Returns content with line numbers in the format '<line>: <content>'. Supports offset (1-indexed line number to start from) and limit (max lines to return, default 2000). Output is capped at 2000 lines or 50 KB, whichever is reached first. Individual lines longer than 2000 characters are truncated. Avoid tiny repeated slices (e.g. 30-line chunks); read a larger window if more context is needed. Images (.png/.jpg/.jpeg/.gif/.webp) and http(s) image URLs are also supported: the image is inlined into the conversation so you can see it (requires a model with image input). offset/limit are ignored for images; oversized images are automatically downscaled when sharp is available.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read. Also accepts an http(s) URL pointing to an image."},offset:{type:"number",description:"Line number to start reading from (1-indexed). Defaults to 1."},limit:{type:"number",description:"Maximum number of lines to return. Defaults to 2000."}},required:["file_path"],additionalProperties:!1},async execute(e){const t=R(e,["file_path","filePath"],"file_path"),i=d.extname(t).toLowerCase().slice(1);if(W(t)||_t.has(i))return await Mt(n,t,o);const s=B(e,["offset"],1),a=B(e,["limit"],ft),r=await St(n,t,s,a,o);return{ok:!0,filePath:$(r.targetPath),content:r.content,isBinary:r.isBinary,truncated:r.truncated,totalLines:r.totalLines,shownFromLine:r.shownFromLine,shownToLine:r.shownToLine}}}}function Wt(n,o,e,t){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded timeout and returns stdout, stderr, exit code, and (if interrupted) partial output. By default (wait_seconds omitted or -1) the command runs in the FOREGROUND and streams output in real time; if the 5-minute adapter watchdog fires while it is still running, the command is interrupted and the partial output produced so far is returned (large output is spilled to a file via outputFilePath to avoid context overflow), with watchdogInterrupted=true. For long-running commands use wait_seconds=0 or n>0 to run in the BACKGROUND, then manage them via list_background_commands / read_background_output / kill_background_command / await_complete.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute."},cwd:{type:"string",description:"Optional workspace-relative working directory. Defaults to the workspace root."},timeout_ms:{type:"number",description:"Optional timeout in milliseconds. Defaults to 600000 and is capped at 600000."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000."},wait_seconds:{type:"number",description:"How to wait for the command to finish. Omitted values (or -1, the default): run in the FOREGROUND until completion; if the 5-minute adapter watchdog fires first, the command is interrupted and only the partial output already produced is returned (watchdogInterrupted=true; large output spills to outputFilePath). 0: spawn and immediately execute in the background, return task_id. n>0: wait up to n seconds; if still running, move to the background and return task_id. Background commands remain subject to timeout_ms (total upper bound); inactivity_timeout_ms is ignored once backgrounded.",minimum:-1},description:{type:"string",description:"Optional short reason for running the command."}},required:["command"],additionalProperties:!1},async execute(i,s){const a=R(i,["command"],"command").trim();if(!a)throw new Error("command must not be empty");const r=B(i,["timeout_ms","timeoutMs"],At),c=Math.min(r,et),u=B(i,["inactivity_timeout_ms","inactivityTimeoutMs"],Ft),l=Math.min(u,Bt),w=I(i,["cwd"]),p=await Tt(n,w);await F(t,"commandExecution",{command:a,cwd:$(p.cwd)});const _=i.wait_seconds,k=typeof _=="number"&&Number.isFinite(_)?_:-1;if(k!==-1&&e)return await qt({deps:e,command:a,agentId:o,workspaceRoot:p.workspaceRoot,cwd:p.cwd,timeoutMs:c,inactivityTimeoutMs:l,waitSeconds:k,onOutput:s?.onOutput,settleSignal:s?.settleSignal});const f=await at({commandText:a,agentId:o,workspaceRoot:p.workspaceRoot,cwd:p.cwd,timeoutMs:c,inactivityTimeoutMs:l,onOutput:s?.onOutput,watchdogSignal:s?.settleSignal}),y={ok:f.exitCode===0&&!f.timedOut,tool:"Bash",command:a,cwd:$(p.cwd),exitCode:f.exitCode,signal:f.signal,timedOut:f.timedOut,inactivityTimedOut:f.inactivityTimedOut,watchdogInterrupted:f.watchdogInterrupted,timeoutMs:c,inactivityTimeoutMs:l,durationMs:f.durationMs,outputSpilled:f.outputSpilled,outputFilePath:f.outputFilePath,outputLineCount:f.outputLineCount,commandFilePath:f.commandFilePath,commandLineCount:f.commandLineCount,stdout:f.stdout,stderr:f.stderr};return f.watchdogInterrupted?{...y,message:"Command was interrupted by the adapter watchdog after ~5 minutes of synchronous (foreground) execution. Only the partial output produced so far is returned above"+(f.outputFilePath?` (full output spilled to "${f.outputFilePath}"; read it if you need more)`:"")+". For long-running commands, re-run with wait_seconds=0 (or n>0) to execute in the background, then manage via await_complete / list_background_commands / read_background_output / kill_background_command."}:y}}}async function qt(n){const{deps:o,command:e,agentId:t,workspaceRoot:i,cwd:s,timeoutMs:a,inactivityTimeoutMs:r,waitSeconds:c,onOutput:u,settleSignal:l}=n,w=u?rt({cwd:s,commandLineCount:0,onOutput:u}):void 0;let p=!!w;const _=()=>{p&&(p=!1,w?.close())};try{const{taskId:h,completion:k}=o.manager.startCommand({commandText:e,agentId:t,workspaceRoot:i,cwd:s,timeoutMs:a,inactivityTimeoutMs:r,onOutput:p?P=>{p&&w?.push(P.stream,P.text)}:void 0,onCompleted:o.emitCompleted});if(c===0)return o.manager.disableInactivityTimeout(t,h),_(),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:h,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, list_background_commands to view all, or kill_background_command(task_id="${h}") to stop.`};const f=c===-1?0:Math.max(0,Math.floor(c*1e3));let y,b;c===-1?b=Promise.resolve("__never__"):b=new Promise(P=>{y=setTimeout(()=>P("__timeout__"),f),y.unref?.()});const E=l?l.then(()=>"__settle__"):Promise.resolve("__never__");try{const P=await Promise.race([k,b,E]);if(P==="__settle__")return o.manager.disableInactivityTimeout(t,h),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:h,status:"backgrounded",reason:"watchdog_timeout",waitedMs:f,message:`Command still running after watchdog timeout; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, or kill_background_command(task_id="${h}") to stop.`};if(P==="__timeout__")return o.manager.disableInactivityTimeout(t,h),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:h,status:"backgrounded",reason:"timeout",waitedMs:f,message:`Command still running after ${c}s; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, or read_background_output(task_id="${h}", mode="tail") / kill_background_command(task_id="${h}") to inspect.`};const g=P;return{ok:g.exitCode===0&&!g.timedOut,tool:"Bash",command:e,backgrounded:!1,taskId:h,exitCode:g.exitCode,signal:g.signal,timedOut:g.timedOut,inactivityTimedOut:g.inactivityTimedOut,timeoutMs:g.timeoutMs,inactivityTimeoutMs:g.inactivityTimeoutMs,durationMs:g.durationMs,outputSpilled:g.outputSpilled,outputFilePath:g.outputFilePath,outputLineCount:g.outputLineCount,commandFilePath:g.commandFilePath,commandLineCount:g.commandLineCount,stdout:g.stdout,stderr:g.stderr}}finally{y&&clearTimeout(y),_()}}catch(h){if(h?.code==="too_many_tasks"||h?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:e,backgrounded:!1,status:"not_executed",error:h.code,message:`Command was NOT executed: ${h.message}. Wait for some background tasks to finish/cleanup (per-agent limit 16, global 256), or reduce concurrent background commands, then retry.`};throw h}}function jt(n){return{kind:"builtin",toolName:"grep",exposedName:"grep",description:"Search file contents using regular expressions within the workspace. Returns matched lines with file paths and line numbers. Supports include/exclude glob patterns, case-insensitive matching, and three output modes (content, files_with_matches, count). Searches recursively through directories, skipping binary files, hidden directories, and common build/dependency directories (node_modules, .git, dist, etc.).",inputSchema:{type:"object",properties:{pattern:{type:"string",description:"Regular expression pattern to search for in file contents."},path:{type:"string",description:"Workspace-relative file or directory path to search in. Defaults to the workspace root."},include:{type:"string",description:"Glob pattern to filter files by name (e.g. '*.ts', '*.{js,ts}'). Only matching files are searched."},exclude:{type:"string",description:"Glob pattern to exclude files by name (e.g. '*.test.ts'). Matching files are skipped."},case_insensitive:{type:"boolean",description:"Case-insensitive matching. Default false."},output_mode:{type:"string",enum:["content","files_with_matches","count"],description:"Output format: 'content' returns matched lines with line numbers (default), 'files_with_matches' returns only file paths with matches, 'count' returns match counts per file."},max_results:{type:"number",description:"Maximum number of matched lines to return (content mode). Default 1000."}},required:["pattern"],additionalProperties:!1},async execute(o){const e=R(o,["pattern"],"pattern"),t=I(o,["path"])||".",i=I(o,["include"]),s=I(o,["exclude"]),a=o.case_insensitive===!0,r=(()=>{const v=o.output_mode;return v==="files_with_matches"||v==="count"?v:"content"})(),c=B(o,["max_results"],Pt),u=a?"i":"";let l;try{l=new RegExp(e,u)}catch(v){throw new Error(`Invalid regex pattern: ${v instanceof Error?v.message:String(v)}`)}const w=i?tt(i):null,p=s?tt(s):null,_=await A(n,t),h=M(_.workspaceRoot,await m.realpath(_.targetPath)),k=await m.stat(h);let f;if(k.isFile())f=[h];else if(k.isDirectory())f=await Ot(h,w,p,bt);else throw new Error(`Path is neither a file nor a directory: ${h}`);const y=[],b=[],E=[];let P=0,g=0,T=!1;for(const v of f){if(r==="content"&&y.length>=c){T=!0;break}const ot={regex:l,maxResults:r==="content"?c-y.length:Number.MAX_SAFE_INTEGER,collectContent:r==="content",stopOnFirstMatch:r==="files_with_matches"},N=await Lt(v,_.workspaceRoot,ot);if(N.count>0){const q=d.relative(_.workspaceRoot,v).replace(/\\/g,"/");if(E.push(q),P+=N.count,r==="content"){for(const j of N.matches){const z=Buffer.byteLength(j.content,"utf-8")+64;if(g+z>Et){T=!0;break}g+=z,y.push(j)}if(T)break;if(y.length>=c){T=!0;break}}else r==="count"&&b.push({file:q,count:N.count})}}const S={ok:!0,tool:"grep",pattern:e,searchPath:$(_.targetPath),outputMode:r,filesSearched:f.length,filesMatched:E.length,totalMatches:P,truncated:T};return r==="content"?S.matches=y:r==="files_with_matches"?S.matches=E:S.matches=b,S}}}function ee(n,o,e,t,i){const s=o||"unknown",a=[Dt(n,i),Nt(n,e,i),Ct(n,e,i),Ut(n,e,i),jt(n),Wt(n,s,t,i),st(n)];return t&&a.push(ct(s,t),lt(s,t),ut(s,t)),a}export{te as DEFAULT_TOOL_PERMISSION_POLICY,ee as createBuiltInFileTools,M as ensureInsideWorkspace,A as resolveWorkspaceTarget};
|
|
3
|
+
`+k:k;return{workspaceRoot:s.workspaceRoot,targetPath:a,content:P,isBinary:!1,truncated:d||E,totalLines:E?-1:h,shownFromLine:b,shownToLine:y}}finally{u.close(),c.destroy()}}function q(n){return/^https?:\/\//i.test(n)}async function Ft(n){let o;try{o=new URL(n)}catch{throw new Error(`Invalid image URL: ${n}`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`Unsupported image URL protocol: ${o.protocol} (only http/https)`);let e;try{e=await fetch(n,{signal:AbortSignal.timeout(Et)})}catch(u){const l=u instanceof Error?u.message:String(u);throw new Error(`Image URL fetch failed: ${n} (${l})`)}if(!e.ok)throw new Error(`Image URL fetch failed: ${n} (HTTP ${e.status})`);const t=(e.headers.get("content-type")||"").split(";")[0]?.trim().toLowerCase()||"";if(t&&!t.startsWith("image/"))throw new Error(`URL did not return an image (content-type: ${t}): ${n}`);const i=Number(e.headers.get("content-length")||"");if(Number.isFinite(i)&&i>A)throw new Error(`Image too large to fetch: ${n} (${x(i)} > ${x(A)})`);const s=e.body;if(!s){const u=await e.arrayBuffer();if(u.byteLength>A)throw new Error(`Image too large to fetch: ${n} (${x(u.byteLength)} > ${x(A)})`);return Buffer.from(u)}const a=s.getReader(),r=[];let c=0;for(;;){const{done:u,value:l}=await a.read();if(u)break;if(l){if(c+=l.byteLength,c>A)throw a.cancel().catch(()=>{}),new Error(`Image too large to fetch: ${n} (${x(c)} > ${x(A)})`);r.push(Buffer.from(l))}}return Buffer.concat(r,c)}async function Bt(n,o,e){let t,i;if(q(o))t=await Ft(o),i=o;else{const y=(await tt(n,o,e)).targetPath,k=await w.stat(y);if(!k.isFile())throw new Error(`Path is not a file: ${y}`);if(k.size>Z)throw new Error(`Image file too large to read: ${$(y)} (${x(k.size)} > ${x(Z)}). Downscale it first.`);t=await w.readFile(y),i=$(y)}if(t.length===0)throw new Error(`Image file is empty: ${i}`);const s=wt(t);if(!s)throw new Error(`Unsupported or corrupt image (unrecognized format): ${i}`);const a=gt(t);let r=s,c=t,u=a?.width,l=a?.height,g=!1;const h=await _t(t,s,{maxWidth:U,maxHeight:U,maxBytes:D});if(h)c=h.buffer,r=h.mediaType,u=h.width,l=h.height,g=h.changed;else if(t.length>D)throw new Error(`Image too large to inline: ${i} (${x(t.length)} > ${x(D)}). Downscale it first, e.g. via Bash: magick "${o}" -resize ${U}x${U} "${o}". Installing sharp enables automatic downscaling.`);const _=u&&l?`${u}x${l}`:"unknown dimensions",d=g&&a&&a.width!==u?` (resized from ${a.width}x${a.height})`:g?" (resized)":"",E=`[Image: ${i}, ${_}${d}, ${x(c.length)}]`,f=q(o)?m.basename(new URL(o).pathname)||"image":m.basename(i);return{ok:!0,filePath:i,content:E,isBinary:!1,image:{mediaType:K(r),width:u??null,height:l??null,originalWidth:a?.width??null,originalHeight:a?.height??null,resized:g,size:c.length},attachments:[{type:"image",name:f,mediaType:K(r),base64Data:c.toString("base64")}]}}function ot(n){let o="",e=0;for(;e<n.length;){const t=n[e];if(t==="*")o+="[^/]*";else if(t==="?")o+="[^/]";else if(t==="{"){const i=n.indexOf("}",e);if(i>=0){const s=n.slice(e+1,i);o+=`(${s.split(",").join("|")})`,e=i}else o+="\\{"}else if(t==="["){const i=n.indexOf("]",e);i>=0?(o+=n.slice(e,i+1),e=i):o+="\\["}else"+^$.()|\\".includes(t)?o+="\\"+t:o+=t;e++}return new RegExp(`^${o}$`)}async function Ct(n,o,e,t){const i=[];async function s(a){if(i.length>=t)return;const r=await w.readdir(a,{withFileTypes:!0});for(const c of r){if(i.length>=t)return;if(c.name.startsWith(".")&&c.isDirectory())continue;const u=m.join(a,c.name);if(c.isDirectory()){if(St.has(c.name))continue;await s(u)}else if(c.isFile()){if(o&&!o.test(c.name)||e&&e.test(c.name))continue;i.push(u)}}}return await s(n),i}async function Nt(n,o,e){if((await w.stat(n)).size>xt)return{matches:[],count:0};if(await Q(n))return{matches:[],count:0};const i=m.relative(o,n).replace(/\\/g,"/"),s=[];let a=0;const r=X(n,{encoding:"utf-8"}),c=Y.createInterface({input:r,crlfDelay:1/0});try{let u=0;for await(const l of c)if(u++,e.regex.test(l)){if(a++,e.collectContent){let g=l;if(g.length>L&&(g=g.slice(0,L)+`... (line truncated to ${L} chars)`),s.push({file:i,line:u,content:g}),s.length>=e.maxResults)break}if(e.stopOnFirstMatch)break}}finally{c.close(),r.destroy()}return{matches:s,count:a}}const nt=10*6e4,Ut=nt,Dt=6e4,Wt=10*6e4;function qt(n,o,e){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",description:"Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to write."},content:{type:"string",description:"Complete UTF-8 file content."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","content"],additionalProperties:!1},async execute(t){const i=S(t,["file_path","filePath"],"file_path"),s=S(t,["content"],"content"),a=I(t,["reason"]),r=await F(n,i);await B(e,"fileModification",{operation:"write",path:r.targetPath});const c=await J(r.targetPath),u=c?await w.readFile(r.targetPath,"utf-8").catch(()=>null):null,l=await et(n,i,s);if(o){const g=m.relative(l.workspaceRoot,l.targetPath).replace(/\\/g,"/");await o({filePath:g,operation:c?"UPDATE":"CREATE",beforeContent:u,afterContent:s,reason:a})}return{ok:!0,filePath:$(l.targetPath),bytes:l.bytes,createdParentDirectory:l.createdParentDirectory}}}}function jt(n,o,e){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",description:"Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to edit."},old_string:{type:"string",description:"Existing text to replace. Must occur exactly once."},new_string:{type:"string",description:"Replacement text."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","old_string","new_string"],additionalProperties:!1},async execute(t){const i=S(t,["file_path","filePath"],"file_path"),s=S(t,["old_string","oldString"],"old_string"),a=S(t,["new_string","newString"],"new_string"),r=I(t,["reason"]);if(!s)throw new Error("old_string must not be empty");const c=await F(n,i);await B(e,"fileModification",{operation:"edit",path:c.targetPath});const u=await Lt(n,i),l=Mt(u.content,s);if(l===0)throw new Error("old_string was not found in file");if(l>1)throw new Error(`old_string occurs ${l} times; provide a unique match`);const g=u.content.replace(s,a),h=await et(n,i,g);if(o){const _=m.relative(h.workspaceRoot,h.targetPath).replace(/\\/g,"/");await o({filePath:_,operation:"UPDATE",beforeContent:u.content,afterContent:g,reason:r})}return{ok:!0,filePath:$(h.targetPath),bytes:h.bytes}}}}function zt(n,o,e){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",description:"Delete a file inside the current workspace. Returns an error if the file does not exist.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to delete."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path"],additionalProperties:!1},async execute(t){const i=S(t,["file_path","filePath"],"file_path"),s=I(t,["reason"]),a=await F(n,i);await B(e,"fileModification",{operation:"delete",path:a.targetPath});const r=O(a.workspaceRoot,await w.realpath(a.targetPath).catch(()=>a.targetPath)),c=await w.stat(r).catch(()=>null);if(!c)throw new Error(`File does not exist: ${r}`);if(!c.isFile())throw new Error(`Path is not a file: ${r}`);const u=await w.readFile(r,"utf-8").catch(()=>null);if(await w.rm(r,{force:!0}),o){const l=m.relative(a.workspaceRoot,r).replace(/\\/g,"/");await o({filePath:l,operation:"DELETE",beforeContent:u,afterContent:null,reason:s})}return{ok:!0,filePath:$(r),deleted:!0}}}}function Gt(n,o){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",description:"Read a UTF-8 text file inside the current workspace. Returns content with line numbers in the format '<line>: <content>'. Supports offset (1-indexed line number to start from) and limit (max lines to return, default 2000). Output is capped at 2000 lines or 50 KB, whichever is reached first. Individual lines longer than 2000 characters are truncated. Avoid tiny repeated slices (e.g. 30-line chunks); read a larger window if more context is needed. Images (.png/.jpg/.jpeg/.gif/.webp) and http(s) image URLs are also supported: the image is inlined into the conversation so you can see it (requires a model with image input). offset/limit are ignored for images; oversized images are automatically downscaled when sharp is available.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read. Also accepts an http(s) URL pointing to an image."},offset:{type:"number",description:"Line number to start reading from (1-indexed). Defaults to 1."},limit:{type:"number",description:"Maximum number of lines to return. Defaults to 2000."}},required:["file_path"],additionalProperties:!1},async execute(e){const t=S(e,["file_path","filePath"],"file_path"),i=m.extname(t).toLowerCase().slice(1);if(q(t)||Pt.has(i))return await Bt(n,t,o);const s=C(e,["offset"],1),a=C(e,["limit"],yt),r=await At(n,t,s,a,o);return{ok:!0,filePath:$(r.targetPath),content:r.content,isBinary:r.isBinary,truncated:r.truncated,totalLines:r.totalLines,shownFromLine:r.shownFromLine,shownToLine:r.shownToLine}}}}function Xt(n,o,e,t){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded foreground wait and returns stdout, stderr, exit code, and (if converted to background) a task_id. By default (wait_seconds omitted or -1) the command runs in the FOREGROUND and streams output in real time; if the 5-minute adapter watchdog or the command total timeout (timeout_ms) fires while it is still running, the command is moved to the BACKGROUND and the tool returns a task_id plus an explanation (backgrounded=true, reason=watchdog_timeout|tool_timeout). The background command has no total timeout and can be managed via await_complete / list_background_commands / read_background_output / kill_background_command. For immediate backgrounding use wait_seconds=0, or wait_seconds=n>0 to wait n seconds then background.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute."},cwd:{type:"string",description:"Optional workspace-relative working directory. Defaults to the workspace root."},timeout_ms:{type:"number",description:"Optional timeout in milliseconds. Defaults to 600000 and is capped at 600000."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000."},wait_seconds:{type:"number",description:"How to wait for the command to finish. Omitted values (or -1, the default): run in the FOREGROUND until completion; if the 5-minute adapter watchdog or timeout_ms fires first, the command is moved to the background and a task_id is returned (backgrounded=true, reason=watchdog_timeout|tool_timeout). 0: spawn and immediately execute in the background, return task_id. n>0: wait up to n seconds; if still running, move to the background and return task_id. Background commands have NO total timeout (run until finished or killed); inactivity_timeout_ms is ignored once backgrounded.",minimum:-1},description:{type:"string",description:"Optional short reason for running the command."}},required:["command"],additionalProperties:!1},async execute(i,s){const a=S(i,["command"],"command").trim();if(!a)throw new Error("command must not be empty");const r=C(i,["timeout_ms","timeoutMs"],Ut),c=Math.min(r,nt),u=C(i,["inactivity_timeout_ms","inactivityTimeoutMs"],Dt),l=Math.min(u,Wt),g=I(i,["cwd"]),h=await It(n,g);await B(t,"commandExecution",{command:a,cwd:$(h.cwd)});const _=i.wait_seconds,E=typeof _=="number"&&Number.isFinite(_)?_:-1;if(e)return await Yt({deps:e,command:a,agentId:o,workspaceRoot:h.workspaceRoot,cwd:h.cwd,timeoutMs:c,inactivityTimeoutMs:l,waitSeconds:E,onOutput:s?.onOutput,settleSignal:s?.settleSignal});const f=await rt({commandText:a,agentId:o,workspaceRoot:h.workspaceRoot,cwd:h.cwd,timeoutMs:c,inactivityTimeoutMs:l,onOutput:s?.onOutput,watchdogSignal:s?.settleSignal}),b={ok:f.exitCode===0&&!f.timedOut,tool:"Bash",command:a,cwd:$(h.cwd),exitCode:f.exitCode,signal:f.signal,timedOut:f.timedOut,inactivityTimedOut:f.inactivityTimedOut,watchdogInterrupted:f.watchdogInterrupted,timeoutMs:c,inactivityTimeoutMs:l,durationMs:f.durationMs,outputSpilled:f.outputSpilled,outputFilePath:f.outputFilePath,outputLineCount:f.outputLineCount,commandFilePath:f.commandFilePath,commandLineCount:f.commandLineCount,stdout:f.stdout,stderr:f.stderr};return f.watchdogInterrupted?{...b,message:"Command was interrupted by the adapter watchdog after ~5 minutes of synchronous (foreground) execution. Only the partial output produced so far is returned above"+(f.outputFilePath?` (full output spilled to "${f.outputFilePath}"; read it if you need more)`:"")+". For long-running commands, re-run with wait_seconds=0 (or n>0) to execute in the background, then manage via await_complete / list_background_commands / read_background_output / kill_background_command."}:b}}}async function Yt(n){const{deps:o,command:e,agentId:t,workspaceRoot:i,cwd:s,timeoutMs:a,inactivityTimeoutMs:r,waitSeconds:c,onOutput:u,settleSignal:l}=n,g=u?st({cwd:s,commandLineCount:0,onOutput:u}):void 0;let h=!!g;const _=()=>{h&&(h=!1,g?.close())};try{const{taskId:d,completion:E}=o.manager.startCommand({commandText:e,agentId:t,workspaceRoot:i,cwd:s,timeoutMs:0,inactivityTimeoutMs:r,onOutput:h?P=>{h&&g?.push(P.stream,P.text)}:void 0,onCompleted:o.emitCompleted});if(c===0)return o.manager.disableInactivityTimeout(t,d),_(),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:d,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use await_complete(bg_cmd_ids=["${d}"]) to block until it finishes, read_background_output(task_id="${d}", mode="tail") to read output, list_background_commands to view all, or kill_background_command(task_id="${d}") to stop.`};const f=c===-1?0:Math.max(0,Math.floor(c*1e3)),b=[E];let y;c!==-1&&b.push(new Promise(P=>{y=setTimeout(()=>P("__timeout__"),f),y.unref?.()}));let k;c===-1&&a>0&&b.push(new Promise(P=>{k=setTimeout(()=>P("__total_timeout__"),a),k.unref?.()})),l&&b.push(l.then(()=>"__settle__"));try{const P=await Promise.race(b);if(P==="__settle__")return o.manager.disableInactivityTimeout(t,d),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:d,status:"backgrounded",reason:"watchdog_timeout",waitedMs:f,message:`Command still running after watchdog timeout; moved to background. Use await_complete(bg_cmd_ids=["${d}"]) to block until it finishes, read_background_output(task_id="${d}", mode="tail") to read output, or kill_background_command(task_id="${d}") to stop.`};if(P==="__timeout__")return o.manager.disableInactivityTimeout(t,d),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:d,status:"backgrounded",reason:"timeout",waitedMs:f,message:`Command still running after ${c}s; moved to background. Use await_complete(bg_cmd_ids=["${d}"]) to block until it finishes, or read_background_output(task_id="${d}", mode="tail") / kill_background_command(task_id="${d}") to inspect.`};if(P==="__total_timeout__")return o.manager.disableInactivityTimeout(t,d),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:d,status:"backgrounded",reason:"tool_timeout",waitedMs:Math.floor(a/1e3),message:`Command still running after ${Math.floor(a/1e3)}s (total timeout); moved to background. It continues running in the background without a total timeout. Use await_complete(bg_cmd_ids=["${d}"]) to block until it finishes, or read_background_output(task_id="${d}", mode="tail") / kill_background_command(task_id="${d}") to inspect.`};const p=P;let v=p.stdout,R=p.stderr,T=p.outputSpilled,N=p.outputFilePath;if(!T&&H(`${p.stdout}${p.stderr}`)>lt){const M=await dt(t,"cmd","out",ct({commandText:e,commandFilePath:p.commandFilePath,commandLineCount:p.commandLineCount,cwd:s,startedAt:new Date(Date.now()-p.durationMs),endedAt:new Date,exitCode:p.exitCode,signal:p.signal??null,timedOut:p.timedOut,inactivityTimedOut:p.inactivityTimedOut??!1,stdout:v,stderr:R}));v=ut($(M),H(`${p.stdout}${p.stderr}`)),T=!0,N=$(M)}return{ok:p.exitCode===0&&!p.timedOut,tool:"Bash",command:e,backgrounded:!1,taskId:d,exitCode:p.exitCode,signal:p.signal,timedOut:p.timedOut,inactivityTimedOut:p.inactivityTimedOut,timeoutMs:p.timeoutMs||a,inactivityTimeoutMs:p.inactivityTimeoutMs,durationMs:p.durationMs,outputSpilled:T,outputFilePath:N,outputLineCount:p.outputLineCount,commandFilePath:p.commandFilePath,commandLineCount:p.commandLineCount,stdout:v,stderr:R}}finally{y&&clearTimeout(y),k&&clearTimeout(k),_()}}catch(d){if(d?.code==="too_many_tasks"||d?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:e,backgrounded:!1,status:"not_executed",error:d.code,message:`Command was NOT executed: ${d.message}. Wait for some background tasks to finish/cleanup (per-agent limit 16, global 256), or reduce concurrent background commands, then retry.`};throw d}}function Ht(n){return{kind:"builtin",toolName:"grep",exposedName:"grep",description:"Search file contents using regular expressions within the workspace. Returns matched lines with file paths and line numbers. Supports include/exclude glob patterns, case-insensitive matching, and three output modes (content, files_with_matches, count). Searches recursively through directories, skipping binary files, hidden directories, and common build/dependency directories (node_modules, .git, dist, etc.).",inputSchema:{type:"object",properties:{pattern:{type:"string",description:"Regular expression pattern to search for in file contents."},path:{type:"string",description:"Workspace-relative file or directory path to search in. Defaults to the workspace root."},include:{type:"string",description:"Glob pattern to filter files by name (e.g. '*.ts', '*.{js,ts}'). Only matching files are searched."},exclude:{type:"string",description:"Glob pattern to exclude files by name (e.g. '*.test.ts'). Matching files are skipped."},case_insensitive:{type:"boolean",description:"Case-insensitive matching. Default false."},output_mode:{type:"string",enum:["content","files_with_matches","count"],description:"Output format: 'content' returns matched lines with line numbers (default), 'files_with_matches' returns only file paths with matches, 'count' returns match counts per file."},max_results:{type:"number",description:"Maximum number of matched lines to return (content mode). Default 1000."}},required:["pattern"],additionalProperties:!1},async execute(o){const e=S(o,["pattern"],"pattern"),t=I(o,["path"])||".",i=I(o,["include"]),s=I(o,["exclude"]),a=o.case_insensitive===!0,r=(()=>{const T=o.output_mode;return T==="files_with_matches"||T==="count"?T:"content"})(),c=C(o,["max_results"],Rt),u=a?"i":"";let l;try{l=new RegExp(e,u)}catch(T){throw new Error(`Invalid regex pattern: ${T instanceof Error?T.message:String(T)}`)}const g=i?ot(i):null,h=s?ot(s):null,_=await F(n,t),d=O(_.workspaceRoot,await w.realpath(_.targetPath)),E=await w.stat(d);let f;if(E.isFile())f=[d];else if(E.isDirectory())f=await Ct(d,g,h,vt);else throw new Error(`Path is neither a file nor a directory: ${d}`);const b=[],y=[],k=[];let P=0,p=0,v=!1;for(const T of f){if(r==="content"&&b.length>=c){v=!0;break}const N={regex:l,maxResults:r==="content"?c-b.length:Number.MAX_SAFE_INTEGER,collectContent:r==="content",stopOnFirstMatch:r==="files_with_matches"},M=await Nt(T,_.workspaceRoot,N);if(M.count>0){const j=m.relative(_.workspaceRoot,T).replace(/\\/g,"/");if(k.push(j),P+=M.count,r==="content"){for(const z of M.matches){const G=Buffer.byteLength(z.content,"utf-8")+64;if(p+G>$t){v=!0;break}p+=G,b.push(z)}if(v)break;if(b.length>=c){v=!0;break}}else r==="count"&&y.push({file:j,count:M.count})}}const R={ok:!0,tool:"grep",pattern:e,searchPath:$(_.targetPath),outputMode:r,filesSearched:f.length,filesMatched:k.length,totalMatches:P,truncated:v};return r==="content"?R.matches=b:r==="files_with_matches"?R.matches=k:R.matches=y,R}}}function se(n,o,e,t,i){const s=o||"unknown",a=[Gt(n,i),qt(n,e,i),jt(n,e,i),zt(n,e,i),Ht(n),Xt(n,s,t,i),mt(n)];return t&&a.push(ht(s,t),ft(s,t),pt(s,t)),a}export{re as DEFAULT_TOOL_PERMISSION_POLICY,se as createBuiltInFileTools,O as ensureInsideWorkspace,F as resolveWorkspaceTarget};
|
|
4
4
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as o}from"node:fs";import{countOutputLines as c,LONG_COMMAND_LINE_LIMIT as s,toDisplayPath as a,writeCommandArtifactFile as m}from"./commandArtifacts.js";function p(n){return`'${n.replace(/'/g,"'\\''")}'`}function u(n){return`"${n.replace(/"/g,'""')}"`}function l(n=process.env,t=o){return["/bin/bash","/usr/bin/bash",String(n.SHELL||"").trim(),"/bin/sh"].find(r=>r&&t(r))||"/bin/sh"}function x(){if(process.platform==="win32")return{command:process.env.ComSpec||"cmd.exe",argsPrefix:["/d","/c"],scriptExtension:"cmd",scriptInvocation:t=>`call ${u(t)}`};const n=l();return{command:n,argsPrefix:["-lc"],scriptExtension:"sh",scriptInvocation:t=>`${n} ${p(t)}`}}function f(n,t){const i=n.endsWith(`
|
|
2
2
|
`)?n:`${n}
|
|
3
3
|
`;return t==="cmd"?`@echo off\r
|
|
4
|
-
${i}`:i}async function C(n,t,i){const r=c(n);if(r<=s)return{commandText:n,originalCommandText:n,commandLineCount:r};const
|
|
4
|
+
${i}`:i}function d(n){return process.platform!=="win32"?n:n.replace(/[<>:"/\\|?*\u0000-\u001f]/g,"_").replace(/\s+/g,"_")||"workspace"}async function C(n,t,i){const r=c(n);if(r<=s)return{commandText:n,originalCommandText:n,commandLineCount:r};const e=await m(d(t),"cmd",i.scriptExtension,f(n,i.scriptExtension));return{commandText:i.scriptInvocation(e),originalCommandText:n,commandLineCount:r,commandFilePath:a(e)}}export{C as prepareShellCommand,l as resolveUnixCommandShell,x as shellExecutable};
|
|
5
5
|
|