aws-runtime-bridge 1.9.126 → 1.9.130

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.
@@ -1,6 +1,6 @@
1
- import L from"node:crypto";import A from"node:fs";import u from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{isUpgradeAllowed as R}from"../services/access-control.js";import{createLogger as j}from"../utils/logger.js";const p=j("pty"),W=80,F=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,B=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function Y(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function X(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(s=>H(s,r))||"/bin/sh",args:[]}}function H(e,t){const r=e.trim();if(!r)return!1;const n=u.basename(r).toLowerCase();return B.has(n)?!1:!u.isAbsolute(r)||t(r)}function J(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return X(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),s=(t==="win32"?u.win32.basename(n):u.basename(n)).toLowerCase();if(!i.has(s))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[s]??n:n,args:[]}}function V(e,t){const r=u.relative(u.resolve(t),u.resolve(e));return r===""||!r.startsWith("..")&&!u.isAbsolute(r)}function K(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(u.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>u.resolve(t))}function Z(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!u.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=u.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const s=K(t);if(s.length>0&&!s.some(a=>V(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function G(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function I(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function P(e,t){for(const r of e.sockets)I(r,t)}function k(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},Y())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function Q(e){if(c.size>=ne())throw new Error("maximum PTY sessions reached");const t=Z(e.workspacePath),r=J(e.shell),n=T(e.cols,W),i=T(e.rows,F),s=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,l=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),o={id:s,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:l,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return l.onData(f=>{o.lastActiveAt=new Date().toISOString(),G(o,f),P(o,{type:"output",data:f})}),l.onExit(({exitCode:f,signal:m})=>{o.status="exited",o.exitCode=f,o.signal=m,o.lastActiveAt=new Date().toISOString(),P(o,{type:"exit",exitCode:f,signal:m}),o.sockets.size===0&&!o.persistent?k(o):o.persistent&&(o.idleTimer&&clearTimeout(o.idleTimer),o.idleTimer=setTimeout(()=>{S(o.id,"exited-timeout")},re()))}),c.set(s,o),o.persistent||k(o),ee(o,e.preCommand),p.info(`created pty session id=${s}, cwd=${t}, shell=${r.shell}`),g(o)}function ee(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const s=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${s.message}`)}}function te(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function re(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function ne(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function oe(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function ie(e){const t=c.get(e);return t?g(t):void 0}function ce(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+te();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ae(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function le(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),le(e),P(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function Pe(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=Q(oe(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ce(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function pe(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),I(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&I(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,s=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),s==="input"){e.ptyProcess.write(String(i.data||""));return}if(s==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(s==="close"){S(e.id,"client-close");return}s==="ping"&&I(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&k(e)})}function de(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{if(!R(r,n))return;const s=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${s}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const l=de(r,a),o=decodeURIComponent(d[1]||""),f=l?l.length>12?l.substring(0,12)+"...":l:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${o}, token=${f}, pathname=${a.pathname}`),!x(l)&&!ae(o,l)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${o}, token=${f}, runtimeTokenValid=${x(l)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
1
+ import O from"node:crypto";import A from"node:fs";import c from"node:path";import{Router as N}from"express";import*as U from"node-pty";import{v4 as D}from"uuid";import M,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{isUpgradeAllowed as R}from"../services/access-control.js";import{createLogger as j}from"../utils/logger.js";const p=j("pty"),W=80,F=24,_=2e5,E=30*60*1e3,b=5*60*1e3,$=60*1e3,v=8,B=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),H=new Set(["sh","dash","ash"]),u=new Map,w=new Map;function X(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function Y(e=process.platform,t=process.env,r=A.existsSync){if(e==="win32")return{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]};const n=String(t.SHELL||"").trim();return{shell:[...H.has(c.basename(n).toLowerCase())?[]:[n],"/bin/bash","/usr/bin/bash",n,"/bin/sh"].find(a=>J(a,r))||"/bin/sh",args:[]}}function J(e,t){const r=e.trim();if(!r)return!1;const n=c.basename(r).toLowerCase();return B.has(n)?!1:!c.isAbsolute(r)||t(r)}function K(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return Y(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),o=(t==="win32"?c.win32.basename(n):c.basename(n)).toLowerCase();if(!i.has(o))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[o]??n:n,args:[]}}function V(e,t){const r=c.relative(c.resolve(t),c.resolve(e));return r===""||!r.startsWith("..")&&!c.isAbsolute(r)}function Z(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(c.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>c.resolve(t))}function q(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!c.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=c.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const o=Z(t);if(o.length>0&&!o.some(a=>V(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function G(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function Q(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function I(e,t){e.readyState===M.OPEN&&e.send(JSON.stringify(t))}function k(e,t){for(const r of e.sockets)I(r,t)}function P(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},X())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function ee(e){if(u.size>=se())throw new Error("maximum PTY sessions reached");const t=q(e.workspacePath),r=K(e.shell),n=T(e.cols,W),i=T(e.rows,F),o=`pty_${D()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${u.size+1}`,l=U.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:G(process.env)}),s={id:o,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:l,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return l.onData(f=>{s.lastActiveAt=new Date().toISOString(),Q(s,f),k(s,{type:"output",data:f})}),l.onExit(({exitCode:f,signal:m})=>{s.status="exited",s.exitCode=f,s.signal=m,s.lastActiveAt=new Date().toISOString(),k(s,{type:"exit",exitCode:f,signal:m}),s.sockets.size===0&&!s.persistent?P(s):s.persistent&&(s.idleTimer&&clearTimeout(s.idleTimer),s.idleTimer=setTimeout(()=>{S(s.id,"exited-timeout")},ne()))}),u.set(o,s),s.persistent||P(s),te(s,e.preCommand),p.info(`created pty session id=${o}, cwd=${t}, shell=${r.shell}`),g(s)}function te(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const o=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${o.message}`)}}function re(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?$:r}function ne(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return b;const r=Number(t);return!Number.isFinite(r)||r<1e3?b:r}function se(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return v;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?v:r}function oe(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function ie(){return Array.from(u.values()).map(g)}function ae(e){const t=u.get(e);return t?g(t):void 0}function ce(e){if(!u.has(e))return;const t=`ptyws_${O.randomBytes(32).toString("base64url")}`,r=Date.now()+re();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ue(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function le(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=u.get(e);if(!r)return!1;C(r),le(e),k(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),u.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function Pe(e="shutdown"){for(const t of Array.from(u.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:ie()})}),y.post("/sessions",h,(e,t)=>{try{const r=ee(oe(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=ae(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ce(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function pe(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function de(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),I(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&I(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=pe(r);if(!n||typeof n!="object")return;const i=n,o=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),o==="input"){e.ptyProcess.write(String(i.data||""));return}if(o==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(o==="close"){S(e.id,"client-close");return}o==="ping"&&I(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&P(e)})}function fe(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function Ae(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{if(!R(r,n))return;const o=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${o}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const l=fe(r,a),s=decodeURIComponent(d[1]||""),f=l?l.length>12?l.substring(0,12)+"...":l:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${s}, token=${f}, pathname=${a.pathname}`),!x(l)&&!ue(s,l)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${s}, token=${f}, runtimeTokenValid=${x(l)}, ptySessions.size=${u.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),n.destroy();return}const m=c.get(o);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${o}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
3
+ `),n.destroy();return}const m=u.get(s);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${s}, ptySessions.size=${u.size}, keys=${Array.from(u.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
4
4
  \r
5
- `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${o}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{pe(m,O)})})}export{ke as attachPtyWebSocketServer,Pe as closeAllPtySessions,S as closePtySession,ce as createPtyConnectToken,Q as createPtySession,ie as getPtySessionSummary,V as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,X as resolveDefaultShell,ne as resolveMaxPtySessions,re as resolvePtyExitedTtlMs,Y as resolvePtyIdleTtlMs,Z as resolvePtyWorkspacePath,J as resolveRequestedShell};
5
+ `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${s}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,L=>{de(m,L)})})}export{Ae as attachPtyWebSocketServer,Pe as closeAllPtySessions,S as closePtySession,ce as createPtyConnectToken,ee as createPtySession,ae as getPtySessionSummary,V as isPathInsideRoot,ie as listPtySessions,y as ptyRouter,u as ptySessions,Y as resolveDefaultShell,se as resolveMaxPtySessions,ne as resolvePtyExitedTtlMs,X as resolvePtyIdleTtlMs,q as resolvePtyWorkspacePath,K as resolveRequestedShell};
6
6
 
@@ -7,6 +7,19 @@ export interface BackgroundCommandResult {
7
7
  exitCode: number | null;
8
8
  signal: string | null;
9
9
  timedOut: boolean;
10
+
11
+ inactivityTimedOut?: boolean;
12
+
13
+ outputFilePath: string;
14
+
15
+ outputLineCount: number;
16
+
17
+ outputSpilled: boolean;
18
+
19
+ commandFilePath?: string;
20
+ commandLineCount: number;
21
+ timeoutMs: number;
22
+ inactivityTimeoutMs: number;
10
23
  stdout: string;
11
24
  stderr: string;
12
25
  durationMs: number;
@@ -30,6 +43,8 @@ export interface StartCommandInput {
30
43
  workspaceRoot: string;
31
44
  cwd: string;
32
45
  timeoutMs?: number;
46
+
47
+ inactivityTimeoutMs?: number;
33
48
  onOutput?: (event: {
34
49
  stream: "stdout" | "stderr";
35
50
  text: string;
@@ -105,7 +120,11 @@ export declare class BackgroundCommandManager {
105
120
  completion: Promise<BackgroundCommandResult>;
106
121
  };
107
122
  private spawnAndAttach;
123
+
124
+ disableInactivityTimeout(agentId: string, taskId: string): boolean;
108
125
  private onOutput;
126
+
127
+ private scheduleInactivity;
109
128
  private appendToFile;
110
129
  private finalizeTask;
111
130
 
@@ -1,8 +1,7 @@
1
- import{spawn as S}from"node:child_process";import{promises as g}from"node:fs";import{createInterface as P}from"node:readline";import{randomBytes as T}from"node:crypto";import{prepareShellCommand as I,shellExecutable as L}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as w,decodeShellOutputChunk as p}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as O}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as R}from"./background-command-store.js";const b=1e3,A=10*60*1e3,B=50*1024*1024,y=16,C=256,x=256*1024,_=500,v=5e3,D=256,k=5*60*1e3;class z{constructor(){this.tasksByAgent=new Map,this.store=new R}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>=y)throw new M("too_many_tasks",`Agent ${i} has ${e.size} background tasks (limit ${y}). Kill or wait for completion first.`);if(this.globalCount()>=C)throw new M("global_limit_reached",`Global background task limit ${C} reached.`);const n=this.generateTaskId(e),o=Math.min(t.timeoutMs??A,A),s=this.store.outputPathFor(i,n),u=t.commandText.length>80?`${t.commandText.slice(0,77)}...`:t.commandText,r={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,child:null,ringBuffer:new O,outputPath:s,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,finalized:!1,stdoutDecoder:w(),stderrDecoder:w()},l=new Promise(a=>{r.completionResolve=a});return e.set(n,r),this.spawnAndAttach(r,t).catch(a=>{r.ringBuffer.push(`[ACode] spawn failed: ${a?.message??String(a)}
2
- `),this.finalizeTask(r,-1,null,"spawn_error")}),this.persistTask(r),{taskId:n,completion:l}}async spawnAndAttach(t,i){const e=L(),n=await I(i.commandText,i.workspaceRoot,e),o=process.platform==="win32",s=S(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,s.stdout?.on("data",u=>{const r=p(t.stdoutDecoder,u);this.onOutput(t,"stdout",r)}),s.stderr?.on("data",u=>{const r=p(t.stderrDecoder,u);this.onOutput(t,"stderr",r)}),s.on("error",u=>{this.onOutput(t,"stderr",`[ACode] child error: ${u.message}
3
- `),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),s.on("close",(u,r)=>{const l=p(t.stdoutDecoder,void 0,!0),a=p(t.stderrDecoder,void 0,!0);if(l&&this.onOutput(t,"stdout",l),a&&this.onOutput(t,"stderr",a),t.ringBuffer.flush(),!t.finalized){const c=t.killedReason??null;this.finalizeTask(t,u,r,c??void 0)}}),t.timeoutTimer=setTimeout(()=>{t.finalized||(t.killedReason="timeout",this.killProcessGroup(s))},t.timeoutMs),t.timeoutTimer.unref?.()}onOutput(t,i,e){if(!e)return;if(t.outputBytes>=B){if(!t.outputTruncated){t.outputTruncated=!0;const o=`
1
+ import{spawn as S}from"node:child_process";import{promises as T}from"node:fs";import{createInterface as E}from"node:readline";import{randomBytes as g}from"node:crypto";import{prepareShellCommand as I,shellExecutable as b}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as y,decodeShellOutputChunk as p}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as O}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as x}from"./background-command-store.js";const R=1e3,w=10*60*1e3,A=50*1024*1024,B=16,C=256,D=256*1024,v=500,F=5e3,z=256,M=5*60*1e3,_=1024*1024;class G{constructor(){this.tasksByAgent=new Map,this.store=new x}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>=B)throw new L("too_many_tasks",`Agent ${i} has ${e.size} background tasks (limit ${B}). Kill or wait for completion first.`);if(this.globalCount()>=C)throw new L("global_limit_reached",`Global background task limit ${C} reached.`);const n=this.generateTaskId(e),s=Math.min(t.timeoutMs??w,w),o=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:s,inactivityTimeoutMs:o,child:null,ringBuffer:new O,outputPath:r,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,inactivityTimer:null,inactivityTimedOut:!1,finalized:!1,stdoutDecoder:y(),stderrDecoder:y(),stdoutText:"",stderrText:"",commandLineCount:0},l=new Promise(c=>{a.completionResolve=c});return e.set(n,a),this.spawnAndAttach(a,t).catch(c=>{a.ringBuffer.push(`[ACode] spawn failed: ${c?.message??String(c)}
2
+ `),this.finalizeTask(a,-1,null,"spawn_error")}),this.persistTask(a),{taskId:n,completion:l}}async spawnAndAttach(t,i){const e=b(),n=await I(i.commandText,i.workspaceRoot,e),s=process.platform==="win32",o=S(e.command,[...e.argsPrefix,n.commandText],{cwd:i.cwd,env:process.env,detached:!s,stdio:["ignore","pipe","pipe"],windowsVerbatimArguments:s,windowsHide:!0});t.child=o,t.commandFilePath=n.commandFilePath,t.commandLineCount=n.commandLineCount,t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),o.stdout?.on("data",r=>{const u=p(t.stdoutDecoder,r);this.onOutput(t,"stdout",u)}),o.stderr?.on("data",r=>{const u=p(t.stderrDecoder,r);this.onOutput(t,"stderr",u)}),o.on("error",r=>{this.onOutput(t,"stderr",`[ACode] child error: ${r.message}
3
+ `),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),o.on("close",(r,u)=>{const a=p(t.stdoutDecoder,void 0,!0),l=p(t.stderrDecoder,void 0,!0);if(a&&this.onOutput(t,"stdout",a),l&&this.onOutput(t,"stderr",l),t.ringBuffer.flush(),!t.finalized){const c=t.killedReason??null;this.finalizeTask(t,r,u,c??void 0)}}),t.timeoutTimer=setTimeout(()=>{t.finalized||(t.killedReason="timeout",this.killProcessGroup(o))},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;const n=i==="stdout"?"stdoutText":"stderrText";if(Buffer.byteLength(t[n],"utf-8")<_){const o=_-Buffer.byteLength(t[n],"utf-8");t[n]+=o>0?e.slice(0,o):""}if(t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),t.outputBytes>=A){if(!t.outputTruncated){t.outputTruncated=!0;const o=`
4
4
  [ACode] output truncated at 50MB
5
- `;t.ringBuffer.push(o),this.appendToFile(t,o)}t.ringBuffer.push(e);return}t.ringBuffer.push(e),t.onOutput?.({stream:i,text:e});const n=(e.match(/\n/g)??[]).length;t.outputLineCount+=n,t.outputBytes+=Buffer.byteLength(e,"utf-8"),this.appendToFile(t,e)}appendToFile(t,i){return t.outputBytes>B?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>g.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.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=t.ringBuffer.tail(1e3),u={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout",stdout:s.join(`
7
- `),stderr:"",durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(u);const r={taskId:t.taskId,status:o,exitCode:i,signal:e,timedOut:n==="timeout",aborted:!1,durationMs:t.finishedAt-t.startedAt},l=t.completionWaiters;t.completionWaiters=[];for(const a of l)a(r);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.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},k),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(g.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}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),o=i.mode??"tail",s=Math.min(Math.max(1,i.lines??50),_);if(!n)return{taskId:e,status:"killed",mode:o,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,hasMore:!1,outputFile:""}};let u=[],r=!1;if(o==="head")u=n.ringBuffer.head(s);else if(o==="tail")u=n.ringBuffer.tail(s);else if(o==="offset"){const d=Math.max(0,i.offset??0),f=n.ringBuffer.offset(d,s);if(f.length>0||n.ringBuffer.hasLine(d))u=f;else{const h=await this.readFromFile(n,"offset",d,s,void 0);u=h.lines,r=h.hasMore}}else if(o==="grep"){const d=String(i.pattern??"");if(d.length>D)return{taskId:e,status:n.status,mode:o,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:o,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,s,f);u=h.lines,r=h.hasMore}let l=0;const a=[];let c=!1;for(const d of u){if(a.length>=_){c=!0,r=!0;break}const f=Buffer.byteLength(d,"utf-8")+1;if(l+f>x){c=!0,r=!0;break}a.push(d),l+=f}const m=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:o,lines:a,lineCount:a.length,metadata:m?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:l,truncated:c,hasMore:r,outputFile:n.outputPath}:void 0}}async readFromFile(t,i,e,n,o){const s=[];let u=!1,r=0,l=0;try{const a=await g.open(t.outputPath,"r"),c=P({input:a.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const m of c){if(i==="grep"&&s.length>=v){u=!0;break}if(i==="offset"){if(r<e){r++;continue}if(l>=n){u=!0;break}s.push(m),l++,r++}else{if(o.test(m)){if(l>=n){u=!0;break}s.push(m),l++}r++}}c.close(),await a.close()}catch{return{lines:[],hasMore:!1}}return{lines:s,hasMore:u}}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(u=>{let r=null;const l=a=>{r&&clearTimeout(r),u(a)};n.completionWaiters.push(l),e&&e>0&&(r=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(a=>a!==l),u({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),r.unref?.())});const s=(await this.store.readSession(t))?.tasks.find(u=>u.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}:{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}}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 u={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-o.startedAt};for(const r of s)r(u)}}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)},b),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=T(4).toString("hex").slice(0,8);if(!t.has(e))return e}return T(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<k),await this.store.persistSession(t.agentId,i.tasks)}}class M extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}const Y=new z;export{z as BackgroundCommandManager,M as BackgroundTaskError,Y as backgroundCommandManager};
5
+ `;t.ringBuffer.push(o),this.appendToFile(t,o)}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>A?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>T.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 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);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.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},M),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}}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),v);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>=v){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=E({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}:{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}}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<M),await this.store.persistSession(t.agentId,i.tasks)}}class L extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}const H=new G;export{G as BackgroundCommandManager,L as BackgroundTaskError,H as backgroundCommandManager};
8
7
 
@@ -1,4 +1,4 @@
1
- import{randomUUID as Y}from"node:crypto";import{promises as u,createReadStream as N}from"node:fs";import p from"node:path";import U from"node:readline";import{runShellCommand as K}from"./builtins/shellCommandRunner.js";import{symbolsTool as H}from"./built-in-symbols-tool.js";import{createListBackgroundCommandsTool as V,createKillBackgroundCommandTool as Z,createReadBackgroundOutputTool as J}from"./background-command-tools.js";const Q=2e3,M=2e3,tt=50*1024,D=4096,et=.3,ot=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"]),nt=1e3,it=1024*1024,rt=256*1024,at=1e3,st=new Set([".git","node_modules","dist","build",".next",".nuxt",".cache",".turbo",".output","coverage",".nyc_output"]);function b(n){return n.replace(/\\/g,"/")}async function W(n){try{return await u.access(n),!0}catch{return!1}}function ct(n,o){if(!o)return 0;let t=0,e=n.indexOf(o);for(;e>=0;)t+=1,e=n.indexOf(o,e+o.length);return t}function P(n,o,t){for(const e of o){const r=n[e];if(typeof r=="string")return r}throw new Error(`${t} is required`)}function E(n,o){for(const t of o){const e=n[t];if(typeof e=="string"&&e.trim())return e}}function C(n,o,t){for(const e of o){const r=n[e],i=typeof r=="number"?r:typeof r=="string"?Number(r):NaN;if(Number.isFinite(i)&&i>0)return Math.floor(i)}return t}async function q(n){const o=p.extname(n).toLowerCase();if(ot.has(o))return!0;const t=await u.open(n,"r");try{const e=Buffer.alloc(D),{bytesRead:r}=await t.read(e,0,D,0);if(r===0)return!1;const i=e.subarray(0,r);if(i.includes(0))return!0;let c=0;for(let a=0;a<r;a++){const l=i[a];(l<9||l>13&&l<32)&&c++}return c/r>et}finally{await t.close()}}function S(n,o){const t=p.resolve(o),e=p.relative(n,t);if(e.startsWith("..")||p.isAbsolute(e))throw new Error(`Target path is outside workspace: ${t}`);return t}async function lt(n,o){const t=p.resolve(String(n||"").trim()),e=await u.realpath(t);if(!(await u.stat(e)).isDirectory())throw new Error(`Workspace path is not a directory: ${t}`);if(!o?.trim())return{workspaceRoot:e,cwd:e};const i=o.trim(),c=p.isAbsolute(i)?p.resolve(i):p.resolve(t,i),a=p.relative(t,c);if(a.startsWith("..")||p.isAbsolute(a))throw new Error(`Command cwd is outside workspace: ${c}`);const l=S(e,await u.realpath(c));if(!(await u.stat(l)).isDirectory())throw new Error(`Command cwd is not a directory: ${l}`);return{workspaceRoot:e,cwd:l}}async function ut(n,o){const t=await u.realpath(p.dirname(o));S(n,t);try{if((await u.lstat(o)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${o}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}async function O(n,o){const t=p.resolve(String(n||"").trim()),e=await u.realpath(t);if(!(await u.stat(e)).isDirectory())throw new Error(`Workspace path is not a directory: ${t}`);const i=o.trim();if(!i)throw new Error("file_path is required");const c=p.isAbsolute(i)?p.resolve(i):p.resolve(t,i),a=p.relative(t,c);if(a.startsWith("..")||p.isAbsolute(a))throw new Error(`Target path is outside workspace: ${c}`);return{workspaceRoot:e,requestedPath:c,targetPath:S(e,p.join(e,a))}}async function j(n,o,t){const e=await O(n,o),r=p.dirname(e.targetPath),i=!await W(r);await u.mkdir(r,{recursive:!0}),await ut(e.workspaceRoot,e.targetPath);const c=p.join(r,`.${p.basename(e.targetPath)}.${process.pid}.${Y()}.tmp`);try{await u.writeFile(c,t,"utf-8"),await u.rename(c,e.targetPath)}catch(a){throw await u.rm(c,{force:!0}),a}return{workspaceRoot:e.workspaceRoot,targetPath:e.targetPath,bytes:Buffer.byteLength(t,"utf-8"),createdParentDirectory:i}}async function dt(n,o){const t=await O(n,o),e=S(t.workspaceRoot,await u.realpath(t.targetPath));if(!(await u.stat(e)).isFile())throw new Error(`Path is not a file: ${e}`);return{workspaceRoot:t.workspaceRoot,targetPath:e,content:await u.readFile(e,"utf-8")}}async function pt(n,o,t,e){const r=await O(n,o),i=S(r.workspaceRoot,await u.realpath(r.targetPath));if(!(await u.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);if(await q(i))return{workspaceRoot:r.workspaceRoot,targetPath:i,content:"[Binary file, content skipped]",isBinary:!0,truncated:!1,totalLines:0,shownFromLine:0,shownToLine:0};const a=N(i,{encoding:"utf-8"}),l=U.createInterface({input:a,crlfDelay:1/0});try{const s=[];let m=0,f=0,g=0,w=!1,d=!1;const _=t-1;for await(const L of l){if(f++,m<_){m++;continue}if(s.length>=e){w=!0,m++;continue}let T=L;T.length>M&&(T=T.slice(0,M)+`... (line truncated to ${M} chars)`);const v=`${m+1}: ${T}`,R=Buffer.byteLength(v,"utf-8")+1;if(g+R>tt){d=!0;break}s.push(v),g+=R,m++}if(f>0&&s.length===0&&!w&&!d&&_>=f)throw new Error(`offset ${t} exceeds total line count ${f} in file: ${i}`);const h=s.length>0?t:0,y=s.length>0?t+s.length-1:0;let x;d?x=`Output capped at 50 KB. Showing lines ${h}-${y}. Use offset=${y+1} to continue.`:w?x=`Showing lines ${h}-${y} of ${f}. Use offset=${y+1} to continue.`:x=`End of file - total ${f} lines`;const B=s.length>0?s.join(`
1
+ import{randomUUID as K}from"node:crypto";import{promises as d,createReadStream as N}from"node:fs";import H from"node:os";import p from"node:path";import U from"node:readline";import{runShellCommand as V}from"./builtins/shellCommandRunner.js";import{createOutputEventCollector as Z}from"./builtins/commandOutputEvents.js";import{symbolsTool as J}from"./built-in-symbols-tool.js";import{createListBackgroundCommandsTool as Q,createKillBackgroundCommandTool as tt,createReadBackgroundOutputTool as et}from"./background-command-tools.js";const ot=2e3,C=2e3,nt=50*1024,D=4096,it=.3,at=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"]),rt=1e3,st=1024*1024,ct=256*1024,lt=1e3,ut=new Set([".git","node_modules","dist","build",".next",".nuxt",".cache",".turbo",".output","coverage",".nyc_output"]);function v(n){return n.replace(/\\/g,"/")}async function W(n){try{return await d.access(n),!0}catch{return!1}}function dt(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 T(n,o,e){for(const t of o){const a=n[t];if(typeof a=="string")return a}throw new Error(`${e} is required`)}function M(n,o){for(const e of o){const t=n[e];if(typeof t=="string"&&t.trim())return t}}function B(n,o,e){for(const t of o){const a=n[t],i=typeof a=="number"?a:typeof a=="string"?Number(a):NaN;if(Number.isFinite(i)&&i>0)return Math.floor(i)}return e}async function j(n){const o=p.extname(n).toLowerCase();if(at.has(o))return!0;const e=await d.open(n,"r");try{const t=Buffer.alloc(D),{bytesRead:a}=await e.read(t,0,D,0);if(a===0)return!1;const i=t.subarray(0,a);if(i.includes(0))return!0;let c=0;for(let r=0;r<a;r++){const s=i[r];(s<9||s>13&&s<32)&&c++}return c/a>it}finally{await e.close()}}function O(n,o){const e=p.resolve(o),t=p.relative(n,e);if(t.startsWith("..")||p.isAbsolute(t))throw new Error(`Target path is outside workspace: ${e}`);return e}function q(n){return n==="~"||n.startsWith("~/")?p.join(H.homedir(),n.slice(1)):n}async function pt(n,o){const e=p.resolve(String(n||"").trim()),t=await d.realpath(e);if(!(await d.stat(t)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);if(!o?.trim())return{workspaceRoot:t,cwd:t};const i=q(o.trim()),c=p.isAbsolute(i)?p.resolve(i):p.resolve(e,i),r=p.relative(e,c);if(r.startsWith("..")||p.isAbsolute(r))throw new Error(`Command cwd is outside workspace: ${c}`);const s=O(t,await d.realpath(c));if(!(await d.stat(s)).isDirectory())throw new Error(`Command cwd is not a directory: ${s}`);return{workspaceRoot:t,cwd:s}}async function ft(n,o){const e=await d.realpath(p.dirname(o));O(n,e);try{if((await d.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 L(n,o){const e=p.resolve(String(n||"").trim()),t=await d.realpath(e);if(!(await d.stat(t)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);const i=q(o.trim());if(!i)throw new Error("file_path is required");const c=p.isAbsolute(i)?p.resolve(i):p.resolve(e,i),r=p.relative(e,c);if(r.startsWith("..")||p.isAbsolute(r))throw new Error(`Target path is outside workspace: ${c}`);return{workspaceRoot:t,requestedPath:c,targetPath:O(t,p.join(t,r))}}async function G(n,o,e){const t=await L(n,o),a=p.dirname(t.targetPath),i=!await W(a);await d.mkdir(a,{recursive:!0}),await ft(t.workspaceRoot,t.targetPath);const c=p.join(a,`.${p.basename(t.targetPath)}.${process.pid}.${K()}.tmp`);try{await d.writeFile(c,e,"utf-8"),await d.rename(c,t.targetPath)}catch(r){throw await d.rm(c,{force:!0}),r}return{workspaceRoot:t.workspaceRoot,targetPath:t.targetPath,bytes:Buffer.byteLength(e,"utf-8"),createdParentDirectory:i}}async function mt(n,o){const e=await L(n,o),t=O(e.workspaceRoot,await d.realpath(e.targetPath));if(!(await d.stat(t)).isFile())throw new Error(`Path is not a file: ${t}`);return{workspaceRoot:e.workspaceRoot,targetPath:t,content:await d.readFile(t,"utf-8")}}async function ht(n,o,e,t){const a=await L(n,o),i=O(a.workspaceRoot,await d.realpath(a.targetPath));if(!(await d.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);if(await j(i))return{workspaceRoot:a.workspaceRoot,targetPath:i,content:"[Binary file, content skipped]",isBinary:!0,truncated:!1,totalLines:0,shownFromLine:0,shownToLine:0};const r=N(i,{encoding:"utf-8"}),s=U.createInterface({input:r,crlfDelay:1/0});try{const l=[];let m=0,f=0,y=0,_=!1,u=!1;const w=e-1;for await(const k of s){if(f++,m<w){m++;continue}if(l.length>=t){_=!0,m++;continue}let h=k;h.length>C&&(h=h.slice(0,C)+`... (line truncated to ${C} chars)`);const R=`${m+1}: ${h}`,S=Buffer.byteLength(R,"utf-8")+1;if(y+S>nt){u=!0;break}l.push(R),y+=S,m++}if(f>0&&l.length===0&&!_&&!u&&w>=f)throw new Error(`offset ${e} exceeds total line count ${f} in file: ${i}`);const b=l.length>0?e:0,g=l.length>0?e+l.length-1:0;let P;u?P=`Output capped at 50 KB. Showing lines ${b}-${g}. Use offset=${g+1} to continue.`:_?P=`Showing lines ${b}-${g} of ${f}. Use offset=${g+1} to continue.`:P=`End of file - total ${f} lines`;const E=l.length>0?l.join(`
2
2
  `)+`
3
- `+x:x;return{workspaceRoot:r.workspaceRoot,targetPath:i,content:B,isBinary:!1,truncated:w||d,totalLines:d?-1:f,shownFromLine:h,shownToLine:y}}finally{l.close(),a.destroy()}}function G(n){let o="",t=0;for(;t<n.length;){const e=n[t];if(e==="*")o+="[^/]*";else if(e==="?")o+="[^/]";else if(e==="{"){const r=n.indexOf("}",t);if(r>=0){const i=n.slice(t+1,r);o+=`(${i.split(",").join("|")})`,t=r}else o+="\\{"}else if(e==="["){const r=n.indexOf("]",t);r>=0?(o+=n.slice(t,r+1),t=r):o+="\\["}else"+^$.()|\\".includes(e)?o+="\\"+e:o+=e;t++}return new RegExp(`^${o}$`)}async function ft(n,o,t,e){const r=[];async function i(c){if(r.length>=e)return;const a=await u.readdir(c,{withFileTypes:!0});for(const l of a){if(r.length>=e)return;if(l.name.startsWith(".")&&l.isDirectory())continue;const s=p.join(c,l.name);if(l.isDirectory()){if(st.has(l.name))continue;await i(s)}else if(l.isFile()){if(o&&!o.test(l.name)||t&&t.test(l.name))continue;r.push(s)}}}return await i(n),r}async function mt(n,o,t){if((await u.stat(n)).size>it)return{matches:[],count:0};if(await q(n))return{matches:[],count:0};const r=p.relative(o,n).replace(/\\/g,"/"),i=[];let c=0;const a=N(n,{encoding:"utf-8"}),l=U.createInterface({input:a,crlfDelay:1/0});try{let s=0;for await(const m of l)if(s++,t.regex.test(m)){if(c++,t.collectContent){let f=m;if(f.length>M&&(f=f.slice(0,M)+`... (line truncated to ${M} chars)`),i.push({file:r,line:s,content:f}),i.length>=t.maxResults)break}if(t.stopOnFirstMatch)break}}finally{l.close(),a.destroy()}return{matches:i,count:c}}const X=10*6e4,ht=X,wt=6e4,gt=10*6e4;function _t(n,o){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 e=P(t,["file_path","filePath"],"file_path"),r=P(t,["content"],"content"),i=E(t,["reason"]),c=await O(n,e),a=await W(c.targetPath),l=a?await u.readFile(c.targetPath,"utf-8").catch(()=>null):null,s=await j(n,e,r);if(o){const m=p.relative(s.workspaceRoot,s.targetPath).replace(/\\/g,"/");await o({filePath:m,operation:a?"UPDATE":"CREATE",beforeContent:l,afterContent:r,reason:i})}return{ok:!0,tool:"write_file",workspacePath:b(s.workspaceRoot),filePath:b(s.targetPath),bytes:s.bytes,createdParentDirectory:s.createdParentDirectory}}}}function yt(n,o){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 e=P(t,["file_path","filePath"],"file_path"),r=P(t,["old_string","oldString"],"old_string"),i=P(t,["new_string","newString"],"new_string"),c=E(t,["reason"]);if(!r)throw new Error("old_string must not be empty");const a=await dt(n,e),l=ct(a.content,r);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 s=a.content.replace(r,i),m=await j(n,e,s);if(o){const f=p.relative(m.workspaceRoot,m.targetPath).replace(/\\/g,"/");await o({filePath:f,operation:"UPDATE",beforeContent:a.content,afterContent:s,reason:c})}return{ok:!0,tool:"edit_file",workspacePath:b(m.workspaceRoot),filePath:b(m.targetPath),replacements:1,bytes:m.bytes}}}}function kt(n,o){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 e=P(t,["file_path","filePath"],"file_path"),r=E(t,["reason"]),i=await O(n,e),c=S(i.workspaceRoot,await u.realpath(i.targetPath).catch(()=>i.targetPath)),a=await u.stat(c).catch(()=>null);if(!a)throw new Error(`File does not exist: ${c}`);if(!a.isFile())throw new Error(`Path is not a file: ${c}`);const l=await u.readFile(c,"utf-8").catch(()=>null);if(await u.rm(c,{force:!0}),o){const s=p.relative(i.workspaceRoot,c).replace(/\\/g,"/");await o({filePath:s,operation:"DELETE",beforeContent:l,afterContent:null,reason:r})}return{ok:!0,tool:"delete_file",workspacePath:b(i.workspaceRoot),filePath:b(c),deleted:!0}}}}function bt(n){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.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read."},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(o){const t=P(o,["file_path","filePath"],"file_path"),e=C(o,["offset"],1),r=C(o,["limit"],Q),i=await pt(n,t,e,r);return{ok:!0,tool:"read_file",workspacePath:b(i.workspaceRoot),filePath:b(i.targetPath),content:i.content,isBinary:i.isBinary,truncated:i.truncated,totalLines:i.totalLines,shownFromLine:i.shownFromLine,shownToLine:i.shownToLine}}}}function Pt(n,o,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, and exit code. Set wait_seconds to 0 or n>0 to move long-running commands to the background and return a task_id immediately (or after n seconds); then use ListBackgroundCommands / ReadBackgroundOutput / KillBackgroundCommand to manage them.",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. Only applies to foreground (wait_seconds=-1); ignored once a command moves to background."},wait_seconds:{type:"number",description:"How long to block waiting for the command to finish. -1 (default): block until completion (current behavior, capped at timeout_ms). 0: spawn and immediately move to background, return task_id. n>0: wait up to n seconds; if still running, move to background and return task_id. Background commands remain subject to timeout_ms (total upper bound); inactivity_timeout_ms is ignored once backgrounded.",default:-1,minimum:-1},description:{type:"string",description:"Optional short reason for running the command."}},required:["command"],additionalProperties:!1},async execute(e,r){const i=P(e,["command"],"command").trim();if(!i)throw new Error("command must not be empty");const c=C(e,["timeout_ms","timeoutMs"],ht),a=Math.min(c,X),l=C(e,["inactivity_timeout_ms","inactivityTimeoutMs"],wt),s=Math.min(l,gt),m=E(e,["cwd"]),f=await lt(n,m),g=e.wait_seconds,w=typeof g=="number"&&Number.isFinite(g)?g:-1;if(w!==-1&&t)return await xt({deps:t,command:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:a,waitSeconds:w,settleSignal:r?.settleSignal});const d=await K({commandText:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:a,inactivityTimeoutMs:s,onOutput:r?.onOutput});return{ok:d.exitCode===0&&!d.timedOut,tool:"Bash",command:i,cwd:b(f.cwd),exitCode:d.exitCode,signal:d.signal,timedOut:d.timedOut,inactivityTimedOut:d.inactivityTimedOut,timeoutMs:a,inactivityTimeoutMs:s,durationMs:d.durationMs,outputSpilled:d.outputSpilled,outputFilePath:d.outputFilePath,outputLineCount:d.outputLineCount,commandFilePath:d.commandFilePath,commandLineCount:d.commandLineCount,stdout:d.stdout,stderr:d.stderr}}}}async function xt(n){const{deps:o,command:t,agentId:e,workspaceRoot:r,cwd:i,timeoutMs:c,waitSeconds:a,settleSignal:l}=n;try{const{taskId:s,completion:m}=o.manager.startCommand({commandText:t,agentId:e,workspaceRoot:r,cwd:i,timeoutMs:c,onCompleted:o.emitCompleted});if(a===0)return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use ListBackgroundCommands to view, ReadBackgroundOutput(task_id="${s}", mode="tail") to read output, KillBackgroundCommand(task_id="${s}") to stop.`};const f=Math.max(0,Math.floor(a*1e3));let g;const w=new Promise(_=>{g=setTimeout(()=>_("__timeout__"),f),g.unref?.()}),d=l?l.then(()=>"__settle__"):Promise.resolve("__never__");try{const _=await Promise.race([m,w,d]);if(_==="__settle__")return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"watchdog_timeout",waitedMs:f,message:`Command still running after watchdog timeout; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${s}", mode="tail") to inspect.`};if(_==="__timeout__")return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"timeout",waitedMs:f,message:`Command still running after ${a}s; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${s}", mode="tail") to inspect.`};const h=_;return{ok:h.exitCode===0&&!h.timedOut,tool:"Bash",command:t,backgrounded:!1,taskId:s,exitCode:h.exitCode,signal:h.signal,timedOut:h.timedOut,durationMs:h.durationMs,stdout:h.stdout,stderr:h.stderr}}finally{g&&clearTimeout(g)}}catch(s){if(s?.code==="too_many_tasks"||s?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:t,backgrounded:!1,error:s.code,message:s.message};throw s}}function vt(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 t=P(o,["pattern"],"pattern"),e=E(o,["path"])||".",r=E(o,["include"]),i=E(o,["exclude"]),c=o.case_insensitive===!0,a=(()=>{const k=o.output_mode;return k==="files_with_matches"||k==="count"?k:"content"})(),l=C(o,["max_results"],at),s=c?"i":"";let m;try{m=new RegExp(t,s)}catch(k){throw new Error(`Invalid regex pattern: ${k instanceof Error?k.message:String(k)}`)}const f=r?G(r):null,g=i?G(i):null,w=await O(n,e),d=S(w.workspaceRoot,await u.realpath(w.targetPath)),_=await u.stat(d);let h;if(_.isFile())h=[d];else if(_.isDirectory())h=await ft(d,f,g,nt);else throw new Error(`Path is neither a file nor a directory: ${d}`);const y=[],x=[],B=[];let L=0,T=0,v=!1;for(const k of h){if(a==="content"&&y.length>=l){v=!0;break}const z={regex:m,maxResults:a==="content"?l-y.length:Number.MAX_SAFE_INTEGER,collectContent:a==="content",stopOnFirstMatch:a==="files_with_matches"},A=await mt(k,w.workspaceRoot,z);if(A.count>0){const I=p.relative(w.workspaceRoot,k).replace(/\\/g,"/");if(B.push(I),L+=A.count,a==="content"){for(const $ of A.matches){const F=Buffer.byteLength($.content,"utf-8")+64;if(T+F>rt){v=!0;break}T+=F,y.push($)}if(v)break;if(y.length>=l){v=!0;break}}else a==="count"&&x.push({file:I,count:A.count})}}const R={ok:!0,tool:"grep",pattern:t,searchPath:b(w.targetPath),outputMode:a,filesSearched:h.length,filesMatched:B.length,totalMatches:L,truncated:v};return a==="content"?R.matches=y:a==="files_with_matches"?R.matches=B:R.matches=x,R}}}function Ct(n,o,t,e){const r=o||"unknown",i=[bt(n),_t(n,t),yt(n,t),kt(n,t),vt(n),Pt(n,r,e),H(n)];return e&&i.push(V(r,e),Z(r,e),J(r,e)),i}export{Ct as createBuiltInFileTools,S as ensureInsideWorkspace,O as resolveWorkspaceTarget};
3
+ `+P:P;return{workspaceRoot:a.workspaceRoot,targetPath:i,content:E,isBinary:!1,truncated:_||u,totalLines:u?-1:f,shownFromLine:b,shownToLine:g}}finally{s.close(),r.destroy()}}function X(n){let o="",e=0;for(;e<n.length;){const t=n[e];if(t==="*")o+="[^/]*";else if(t==="?")o+="[^/]";else if(t==="{"){const a=n.indexOf("}",e);if(a>=0){const i=n.slice(e+1,a);o+=`(${i.split(",").join("|")})`,e=a}else o+="\\{"}else if(t==="["){const a=n.indexOf("]",e);a>=0?(o+=n.slice(e,a+1),e=a):o+="\\["}else"+^$.()|\\".includes(t)?o+="\\"+t:o+=t;e++}return new RegExp(`^${o}$`)}async function wt(n,o,e,t){const a=[];async function i(c){if(a.length>=t)return;const r=await d.readdir(c,{withFileTypes:!0});for(const s of r){if(a.length>=t)return;if(s.name.startsWith(".")&&s.isDirectory())continue;const l=p.join(c,s.name);if(s.isDirectory()){if(ut.has(s.name))continue;await i(l)}else if(s.isFile()){if(o&&!o.test(s.name)||e&&e.test(s.name))continue;a.push(l)}}}return await i(n),a}async function gt(n,o,e){if((await d.stat(n)).size>st)return{matches:[],count:0};if(await j(n))return{matches:[],count:0};const a=p.relative(o,n).replace(/\\/g,"/"),i=[];let c=0;const r=N(n,{encoding:"utf-8"}),s=U.createInterface({input:r,crlfDelay:1/0});try{let l=0;for await(const m of s)if(l++,e.regex.test(m)){if(c++,e.collectContent){let f=m;if(f.length>C&&(f=f.slice(0,C)+`... (line truncated to ${C} chars)`),i.push({file:a,line:l,content:f}),i.length>=e.maxResults)break}if(e.stopOnFirstMatch)break}}finally{s.close(),r.destroy()}return{matches:i,count:c}}const z=10*6e4,_t=z,yt=6e4,kt=10*6e4;function bt(n,o){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(e){const t=T(e,["file_path","filePath"],"file_path"),a=T(e,["content"],"content"),i=M(e,["reason"]),c=await L(n,t),r=await W(c.targetPath),s=r?await d.readFile(c.targetPath,"utf-8").catch(()=>null):null,l=await G(n,t,a);if(o){const m=p.relative(l.workspaceRoot,l.targetPath).replace(/\\/g,"/");await o({filePath:m,operation:r?"UPDATE":"CREATE",beforeContent:s,afterContent:a,reason:i})}return{ok:!0,tool:"write_file",workspacePath:v(l.workspaceRoot),filePath:v(l.targetPath),bytes:l.bytes,createdParentDirectory:l.createdParentDirectory}}}}function Pt(n,o){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(e){const t=T(e,["file_path","filePath"],"file_path"),a=T(e,["old_string","oldString"],"old_string"),i=T(e,["new_string","newString"],"new_string"),c=M(e,["reason"]);if(!a)throw new Error("old_string must not be empty");const r=await mt(n,t),s=dt(r.content,a);if(s===0)throw new Error("old_string was not found in file");if(s>1)throw new Error(`old_string occurs ${s} times; provide a unique match`);const l=r.content.replace(a,i),m=await G(n,t,l);if(o){const f=p.relative(m.workspaceRoot,m.targetPath).replace(/\\/g,"/");await o({filePath:f,operation:"UPDATE",beforeContent:r.content,afterContent:l,reason:c})}return{ok:!0,tool:"edit_file",workspacePath:v(m.workspaceRoot),filePath:v(m.targetPath),replacements:1,bytes:m.bytes}}}}function xt(n,o){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(e){const t=T(e,["file_path","filePath"],"file_path"),a=M(e,["reason"]),i=await L(n,t),c=O(i.workspaceRoot,await d.realpath(i.targetPath).catch(()=>i.targetPath)),r=await d.stat(c).catch(()=>null);if(!r)throw new Error(`File does not exist: ${c}`);if(!r.isFile())throw new Error(`Path is not a file: ${c}`);const s=await d.readFile(c,"utf-8").catch(()=>null);if(await d.rm(c,{force:!0}),o){const l=p.relative(i.workspaceRoot,c).replace(/\\/g,"/");await o({filePath:l,operation:"DELETE",beforeContent:s,afterContent:null,reason:a})}return{ok:!0,tool:"delete_file",workspacePath:v(i.workspaceRoot),filePath:v(c),deleted:!0}}}}function vt(n){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.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read."},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(o){const e=T(o,["file_path","filePath"],"file_path"),t=B(o,["offset"],1),a=B(o,["limit"],ot),i=await ht(n,e,t,a);return{ok:!0,tool:"read_file",workspacePath:v(i.workspaceRoot),filePath:v(i.targetPath),content:i.content,isBinary:i.isBinary,truncated:i.truncated,totalLines:i.totalLines,shownFromLine:i.shownFromLine,shownToLine:i.shownToLine}}}}function Tt(n,o,e){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, and exit code. If the 5-minute adapter watchdog fires while the command is still running (or wait_seconds is set to 0 / n>0), the command moves to the background and the tool returns a task_id; then use ListBackgroundCommands / ReadBackgroundOutput / KillBackgroundCommand to manage it.",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. Applies while waiting synchronously; it is disabled after the command moves to background."},wait_seconds:{type:"number",description:"How long to block waiting for the command to finish. Omitted values (or -1): stay in the foreground until completion (capped at timeout_ms); if the 5-minute watchdog fires first, the command settles to the background and returns task_id. 0: spawn and immediately move to background, return task_id. n>0: wait up to n seconds; if still running, move to 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(t,a){const i=T(t,["command"],"command").trim();if(!i)throw new Error("command must not be empty");const c=B(t,["timeout_ms","timeoutMs"],_t),r=Math.min(c,z),s=B(t,["inactivity_timeout_ms","inactivityTimeoutMs"],yt),l=Math.min(s,kt),m=M(t,["cwd"]),f=await pt(n,m),y=t.wait_seconds,u=typeof y=="number"&&Number.isFinite(y)?y:-1;if(e)return await Rt({deps:e,command:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:r,inactivityTimeoutMs:l,waitSeconds:u,onOutput:a?.onOutput,settleSignal:a?.settleSignal});const w=await V({commandText:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:r,inactivityTimeoutMs:l,onOutput:a?.onOutput});return{ok:w.exitCode===0&&!w.timedOut,tool:"Bash",command:i,cwd:v(f.cwd),exitCode:w.exitCode,signal:w.signal,timedOut:w.timedOut,inactivityTimedOut:w.inactivityTimedOut,timeoutMs:r,inactivityTimeoutMs:l,durationMs:w.durationMs,outputSpilled:w.outputSpilled,outputFilePath:w.outputFilePath,outputLineCount:w.outputLineCount,commandFilePath:w.commandFilePath,commandLineCount:w.commandLineCount,stdout:w.stdout,stderr:w.stderr}}}}async function Rt(n){const{deps:o,command:e,agentId:t,workspaceRoot:a,cwd:i,timeoutMs:c,inactivityTimeoutMs:r,waitSeconds:s,onOutput:l,settleSignal:m}=n,f=l?Z({cwd:i,commandLineCount:0,onOutput:l}):void 0;let y=!!f;const _=()=>{y&&(y=!1,f?.close())};try{const{taskId:u,completion:w}=o.manager.startCommand({commandText:e,agentId:t,workspaceRoot:a,cwd:i,timeoutMs:c,inactivityTimeoutMs:r,onOutput:y?k=>{y&&f?.push(k.stream,k.text)}:void 0,onCompleted:o.emitCompleted});if(s===0)return o.manager.disableInactivityTimeout(t,u),_(),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:u,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use ListBackgroundCommands to view, ReadBackgroundOutput(task_id="${u}", mode="tail") to read output, KillBackgroundCommand(task_id="${u}") to stop.`};const b=s===-1?0:Math.max(0,Math.floor(s*1e3));let g,P;s===-1?P=Promise.resolve("__never__"):P=new Promise(k=>{g=setTimeout(()=>k("__timeout__"),b),g.unref?.()});const E=m?m.then(()=>"__settle__"):Promise.resolve("__never__");try{const k=await Promise.race([w,P,E]);if(k==="__settle__")return o.manager.disableInactivityTimeout(t,u),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:u,status:"backgrounded",reason:"watchdog_timeout",waitedMs:b,message:`Command still running after watchdog timeout; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${u}", mode="tail") to inspect.`};if(k==="__timeout__")return o.manager.disableInactivityTimeout(t,u),{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:u,status:"backgrounded",reason:"timeout",waitedMs:b,message:`Command still running after ${s}s; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${u}", mode="tail") to inspect.`};const h=k;return{ok:h.exitCode===0&&!h.timedOut,tool:"Bash",command:e,backgrounded:!1,taskId:u,exitCode:h.exitCode,signal:h.signal,timedOut:h.timedOut,inactivityTimedOut:h.inactivityTimedOut,timeoutMs:h.timeoutMs,inactivityTimeoutMs:h.inactivityTimeoutMs,durationMs:h.durationMs,outputSpilled:h.outputSpilled,outputFilePath:h.outputFilePath,outputLineCount:h.outputLineCount,commandFilePath:h.commandFilePath,commandLineCount:h.commandLineCount,stdout:h.stdout,stderr:h.stderr}}finally{g&&clearTimeout(g),_()}}catch(u){if(u?.code==="too_many_tasks"||u?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:e,backgrounded:!1,error:u.code,message:u.message};throw u}}function Et(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=T(o,["pattern"],"pattern"),t=M(o,["path"])||".",a=M(o,["include"]),i=M(o,["exclude"]),c=o.case_insensitive===!0,r=(()=>{const x=o.output_mode;return x==="files_with_matches"||x==="count"?x:"content"})(),s=B(o,["max_results"],lt),l=c?"i":"";let m;try{m=new RegExp(e,l)}catch(x){throw new Error(`Invalid regex pattern: ${x instanceof Error?x.message:String(x)}`)}const f=a?X(a):null,y=i?X(i):null,_=await L(n,t),u=O(_.workspaceRoot,await d.realpath(_.targetPath)),w=await d.stat(u);let b;if(w.isFile())b=[u];else if(w.isDirectory())b=await wt(u,f,y,rt);else throw new Error(`Path is neither a file nor a directory: ${u}`);const g=[],P=[],E=[];let k=0,h=0,R=!1;for(const x of b){if(r==="content"&&g.length>=s){R=!0;break}const Y={regex:m,maxResults:r==="content"?s-g.length:Number.MAX_SAFE_INTEGER,collectContent:r==="content",stopOnFirstMatch:r==="files_with_matches"},I=await gt(x,_.workspaceRoot,Y);if(I.count>0){const A=p.relative(_.workspaceRoot,x).replace(/\\/g,"/");if(E.push(A),k+=I.count,r==="content"){for(const F of I.matches){const $=Buffer.byteLength(F.content,"utf-8")+64;if(h+$>ct){R=!0;break}h+=$,g.push(F)}if(R)break;if(g.length>=s){R=!0;break}}else r==="count"&&P.push({file:A,count:I.count})}}const S={ok:!0,tool:"grep",pattern:e,searchPath:v(_.targetPath),outputMode:r,filesSearched:b.length,filesMatched:E.length,totalMatches:k,truncated:R};return r==="content"?S.matches=g:r==="files_with_matches"?S.matches=E:S.matches=P,S}}}function $t(n,o,e,t){const a=o||"unknown",i=[vt(n),bt(n,e),Pt(n,e),xt(n,e),Et(n),Tt(n,a,t),J(n)];return t&&i.push(Q(a,t),tt(a,t),et(a,t)),i}export{$t as createBuiltInFileTools,O as ensureInsideWorkspace,L as resolveWorkspaceTarget};
4
4
 
@@ -10,5 +10,7 @@ export interface PreparedShellCommand {
10
10
  readonly commandLineCount: number;
11
11
  readonly commandFilePath?: string;
12
12
  }
13
+
14
+ export declare function resolveUnixCommandShell(env?: NodeJS.ProcessEnv, exists?: (path: string) => boolean): string;
13
15
  export declare function shellExecutable(): ShellExecutable;
14
16
  export declare function prepareShellCommand(commandText: string, workspaceRoot: string, shell: ShellExecutable): Promise<PreparedShellCommand>;
@@ -1,5 +1,5 @@
1
- import{countOutputLines as e,LONG_COMMAND_LINE_LIMIT as c,toDisplayPath as a,writeCommandArtifactFile as s}from"./commandArtifacts.js";function m(n){return`'${n.replace(/'/g,"'\\''")}'`}function p(n){return`"${n.replace(/"/g,'""')}"`}function d(){return process.platform==="win32"?{command:process.env.ComSpec||"cmd.exe",argsPrefix:["/d","/c"],scriptExtension:"cmd",scriptInvocation:n=>`call ${p(n)}`}:{command:process.env.SHELL||"/bin/sh",argsPrefix:["-lc"],scriptExtension:"sh",scriptInvocation:n=>`sh ${m(n)}`}}function u(n,i){const t=n.endsWith(`
1
+ import{existsSync as e}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=e){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 d(n,t){const i=n.endsWith(`
2
2
  `)?n:`${n}
3
- `;return i==="cmd"?`@echo off\r
4
- ${t}`:t}async function f(n,i,t){const o=e(n);if(o<=c)return{commandText:n,originalCommandText:n,commandLineCount:o};const r=await s(i,"cmd",t.scriptExtension,u(n,t.scriptExtension));return{commandText:t.scriptInvocation(r),originalCommandText:n,commandLineCount:o,commandFilePath:a(r)}}export{f as prepareShellCommand,d as shellExecutable};
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 o=await m(t,"cmd",i.scriptExtension,d(n,i.scriptExtension));return{commandText:i.scriptInvocation(o),originalCommandText:n,commandLineCount:r,commandFilePath:a(o)}}export{C as prepareShellCommand,l as resolveUnixCommandShell,x as shellExecutable};
5
5
 
@@ -8,7 +8,7 @@ import{EventEmitter as Ve}from"node:events";import G from"node:fs";import Xe fro
8
8
  `),B=[...b(this.config.globalPrompt),...b(P),...b(this.config.memory),...N?[{role:"system",content:N}]:[]],F=B.map(g=>g.content).filter(Boolean).join(`
9
9
 
10
10
  `);this.initialConversation=ue(B),this.state={started:!0,terminated:!1,configMcpCount:Object.keys(e.mcpServers??{}).length,skills:t,provider:l,permissions:i,mcpConnections:a,ownsMcpConnections:r,exposedTools:A,commands:R,commandRegistry:f,conversation:ue(B),accumulatedInputTokens:0,accumulatedOutputTokens:0,accumulatedCacheHitTokens:0,accumulatedCacheMissTokens:0},this.exposedToolsInitialized=!0,this.emitAcodeEvent("startup_phase",{phase:"session_start_hook"}),await this.hookBus.dispatch("SessionStart",{workingDirectory:this.config.workingDirectory,model:l.model,commandCount:R.length});const L=me(l);this.emitAcodeEvent("session_started",{workingDirectory:this.config.workingDirectory,model:l.model,commandCount:R.length,estimatedContextTokens:Bt(F,A),contextLimit:L.contextLimit,maxOutput:L.maxOutput,...L.inputLimit!==void 0?{inputLimit:L.inputLimit}:{},compactionThreshold:L.threshold}),this.emitAcodeEvent("mcp_status",{configPath:this.config.configPath,installedCount:this.state.configMcpCount,servers:Object.keys(e.mcpServers??{}),connectedCount:a.length,builtInToolCount:p.length,toolCount:A.length,permissionMode:i.mode,allowedToolRules:i.allow,deniedToolRules:i.deny,tools:A.map(g=>({name:g.exposedName,source:g.kind==="mcp"?`${g.serverName}.${g.toolName}`:`builtin.${g.toolName}`,description:g.description||""}))}),this.emitAcodeEvent("skill_status",{skillsDir:this.config.skillsDir,installedCount:t.length,skills:t.map(g=>({name:g.name,description:g.description,directory:g.directory})),skillGroups:o.map(g=>({dir:g.dir,exists:g.exists,skills:g.skills.map(q=>({name:q.name,description:q.description,directory:q.directory}))}))}),this.emitAcodeEvent("command_status",{action:"commands_loaded",installedCommandCount:R.length,commandDirs:$,commands:R.map(g=>({name:g.name,description:g.description}))}),this.config.initialPrompt&&await this.submit(this.config.initialPrompt),this.startCompactionDebugLog(),this.startSkillWatchers(s)}async loadAllSkills(){const e=this.config.skillsDir??"",t=te.join(this.config.workingDirectory,".acode","skills"),o=te.join(this.config.workingDirectory,".agentswork","skills"),s=new Set,l=[],i=[];for(const c of[e,t,o]){if(!c)continue;const m=te.resolve(c);if(!s.has(m)){s.add(m),l.push(await At(c));try{await G.promises.access(m),i.push(m)}catch{}}}const a=new Map;for(const c of l)for(const m of c.skills)a.set(m.name,m);return{skills:[...a.values()],skillGroups:l,watchedDirs:i}}startSkillWatchers(e){for(const t of e){let o=null;try{o=G.watch(t,{recursive:!0},()=>{this.scheduleSkillRefresh()})}catch{try{o=G.watch(t,()=>{this.scheduleSkillRefresh()})}catch{continue}}o&&(o.on("error",()=>{}),this.skillWatchers.push(o))}}scheduleSkillRefresh(){this.skillRefreshTimer&&clearTimeout(this.skillRefreshTimer),this.skillRefreshTimer=setTimeout(()=>{this.skillRefreshTimer=null,this.refreshSkills()},H.SKILL_REFRESH_DEBOUNCE_MS)}async refreshSkills(){if(!(!this.state.started||this.state.terminated))try{const{skills:e,skillGroups:t}=await this.loadAllSkills();this.skillTool&&this.skillTool.refresh(e),this.state.skills=e,this.emitAcodeEvent("skill_status",{skillsDir:this.config.skillsDir,installedCount:e.length,skills:e.map(o=>({name:o.name,description:o.description,directory:o.directory})),skillGroups:t.map(o=>({dir:o.dir,exists:o.exists,skills:o.skills.map(s=>({name:s.name,description:s.description,directory:s.directory}))}))}),u(`[skill-watch] Skills refreshed: ${e.length} skills (${e.map(o=>o.name).join(", ")||"none"})`,{sessionId:this.config.sessionId})}catch(e){console.error("[skill-watch] Failed to refresh skills:",e instanceof Error?e.message:e)}}stopSkillWatchers(){this.skillRefreshTimer&&(clearTimeout(this.skillRefreshTimer),this.skillRefreshTimer=null);for(const e of this.skillWatchers)try{e.close()}catch{}this.skillWatchers=[]}startCompactionDebugLog(){if(!ae||this.compactionDebugTimer)return;const e=this.state.provider,{contextLimit:t,maxOutput:o,inputLimit:s,threshold:l}=me(e);this.compactionDebugTimer=setInterval(()=>{try{const i=this.state.accumulatedInputTokens+this.state.accumulatedOutputTokens,a=oe(this.state.conversation)+Be(this.state.exposedTools),r=Math.max(i,a),c=Y(this.state.conversation),m=this.state.started&&!this.state.terminated,p=[`[${new Date().toISOString()}]`,`started=${this.state.started}`,`terminated=${this.state.terminated}`,`activeTurn=${this.activeTurn}`,`conversationSize=${this.state.conversation.length}msgs`,`sizeKB=${c}KB`,`estimation=${a} tokens`,`actualInput=${this.state.accumulatedInputTokens}`,`actualOutput=${this.state.accumulatedOutputTokens}`,`actualUsed=${i}`,`contextLimit=${t}`,`inputLimit=${s??"n/a"}`,`maxOutput=${o}`,`triggerThreshold=${l}`,`remainingToTrigger=${Math.max(0,l-i)}`,`isRunning=${m}`,`shouldCompact=${m?r>=l:!1}`,"---"].join(" | ");G.promises.appendFile(Oe,p+`
11
- `,"utf-8").catch(()=>{})}catch{}},5e3)}stopCompactionDebugLog(){this.compactionDebugTimer&&(clearInterval(this.compactionDebugTimer),this.compactionDebugTimer=null)}async submit(e){const t=this.contextVersion;u(`[ACodeRuntime] \u{1F4EC} submit() queued: contextVersion=${t}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, terminated=${this.state.terminated}, message.length=${e.length}`);const o=this.submitQueue.then(()=>(u(`[ACodeRuntime] \u25B6\uFE0F executeSubmit dequeued: contextVersion=${t}`),this.executeSubmitGuarded(e,t)));return this.submitQueue=o.catch(()=>{u("[ACodeRuntime] \u{1F4EC} submit() queued item rejected (catch swallowed)")}),u("[ACodeRuntime] \u{1F4EC} submit() returning queued promise"),o}executeSubmitGuarded(e,t){const o={};this.activeTurnToken=o;const s=this.executeSubmit(e,t),l=new Promise(i=>{this.forceSettleWaiters.set(o,i)});return s.finally(()=>{this.forceSettleWaiters.delete(o),this.activeTurnToken===o&&(this.abortForceSettleTimer&&(clearTimeout(this.abortForceSettleTimer),this.abortForceSettleTimer=null),this.activeTurnToken=null)}).catch(()=>{}),Promise.race([s,l])}armAbortForceSettle(){if(!this.activeTurn||!this.activeTurnToken)return;const e=this.activeTurnToken;this.abortForceSettleTimer&&clearTimeout(this.abortForceSettleTimer);const t=Mo();this.abortForceSettleTimer=setTimeout(()=>{this.abortForceSettleTimer=null,this.activeTurn&&this.activeTurnToken===e&&this.forceSettleActiveTurn(`turn still unsettled ${t}ms after abort()`)},t),typeof this.abortForceSettleTimer.unref=="function"&&this.abortForceSettleTimer.unref()}forceSettleActiveTurn(e){const t=this.activeTurnToken;console.error(`[ACodeRuntime] \u{1F6A8} FORCE SETTLE: active turn ignored abort() (${e}). Force-settling to unblock submitQueue; late executeSubmit continuation will be superseded.`),this.contextVersion+=1,this.activeTurn=!1,this.abortRequested=!1,this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop(),this.emitAcodeEvent("error",{message:`ACode turn force-settled: ${e}`}),this.emitAcodeEvent("turn_complete",{failed:!0});const o=t?this.forceSettleWaiters.get(t):void 0;t&&this.forceSettleWaiters.delete(t),this.activeTurnToken=null,o?.()}notifyForceMessage(){this.forceMessagePending=!0,u("[ACodeRuntime] \u{1F514} Force message pending flag set")}addBuiltInTools(e){if(!this.state||!this.exposedToolsInitialized||!Array.isArray(e))return 0;const t=new Set(this.state.exposedTools.map(s=>s.exposedName)),o=[];for(const s of e)kt(s)&&(t.has(s.exposedName)||(t.add(s.exposedName),o.push(s)));return o.length===0?0:(this.state.exposedTools=[...this.state.exposedTools,...o],this.emitExposedToolsStatus(),o.length)}removeBuiltInToolsByPrefix(e){if(!this.state||!this.exposedToolsInitialized||!e)return 0;const t=this.state.exposedTools.length;this.state.exposedTools=this.state.exposedTools.filter(s=>!s.exposedName.startsWith(e));const o=t-this.state.exposedTools.length;return o>0&&this.emitExposedToolsStatus(),o}emitExposedToolsStatus(){this.emitAcodeEvent("mcp_status",{servers:this.state.mcpConnections.map(e=>e.serverName),connectedCount:this.state.mcpConnections.length,toolCount:this.state.exposedTools.length,tools:this.state.exposedTools.map(e=>({name:e.exposedName,source:e.kind==="mcp"?`${e.serverName}.${e.toolName}`:`builtin.${e.toolName}`,description:e.description||""}))})}async injectForceMessage(){if(!this.forceMessagePending)return!1;this.forceMessagePending=!1;const e=this.state.mcpConnections.find(t=>t.tools.some(o=>o.toolName==="get_message"));if(!e)return u("[ACodeRuntime] injectForceMessage: no connection with get_message found"),!1;try{const t=await E(e.client.callTool({name:"get_message",arguments:{_aws_force_inject:!0}},void 0,{timeout:1e4}),1e4,"injectForceMessage get_message"),o=O(t);let s=[];try{const r=JSON.parse(o);Array.isArray(r?.directMessages)&&(s=r.directMessages.filter(c=>c?.force===!0))}catch{return!1}if(s.length===0)return!1;const l=JSON.stringify({directMessages:s,groupMessages:[]}),i=`forced_get_msg_${Date.now()}`,a=`${e.serverName}__get_message`;return this.state.conversation.push({role:"assistant",content:null,tool_calls:[{id:i,type:"function",function:{name:a,arguments:"{}"}}]}),this.state.conversation.push({role:"tool",tool_call_id:i,content:l}),u(`[ACodeRuntime] injectForceMessage: injected ${s.length} force message(s) into conversation`),!0}catch(t){return u(`[ACodeRuntime] injectForceMessage failed: ${t}`),!1}}async resetContext(){if(!this.state.started||this.state.terminated)throw new Error("ACode runtime is not active");this.contextVersion+=1,this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.state.conversation=ue(this.initialConversation),this.state.accumulatedInputTokens=0,this.state.accumulatedOutputTokens=0}isTurnSuperseded(e){return e!==this.contextVersion}async executeSubmit(e,t){const o=Date.now();if(u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),u("[ACodeRuntime] \u25B6\uFE0F executeSubmit STARTED"),u(`[ACodeRuntime] Message: <redacted, length=${e.length}>`),u(`[ACodeRuntime] \u{1F522} Context version: ${t}`),u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),!this.state.started||this.state.terminated)throw console.error(`[ACodeRuntime] \u274C Runtime not active (started: ${this.state.started}, terminated: ${this.state.terminated})`),new Error("ACode runtime is not active");this.activeTurn=!0,this.abortRequested=!1;const s=this.activeTurnToken,l=e.trim();let i=!1;try{u("[ACodeRuntime] \u{1F50D} Dispatching UserPromptSubmit hook...");const a=Date.now(),r=await this.hookBus.dispatch("UserPromptSubmit",{prompt:l}),c=Date.now()-a;if(u(`[ACodeRuntime] \u2705 UserPromptSubmit hook completed in ${c}ms, results: ${r.length}`),this.isTurnSuperseded(t)){u("[ACodeRuntime] \u26A0\uFE0F Turn superseded after hook, aborting");return}const m=ce(r);if(m){console.error(`[ACodeRuntime] \u274C Prompt denied by hook: ${m.message}`),this.emitAcodeEvent("error",{message:m.message||"ACode prompt was denied by hook"}),this.emitAcodeEvent("turn_complete",{failed:!0}),i=!0,u("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (failed=true) due to hook deny");return}u("[ACodeRuntime] \u{1F50D} Expanding command if any...");const p=this.state.commandRegistry.expand(l),h=p?.prompt.trim()||l,k=p?.command.allowedTools;if(p&&(u(`[ACodeRuntime] \u{1F4CB} Slash command detected: ${p.command.name}`),this.emitAcodeEvent("command_status",{action:"slash_command",command:p.command.name,arguments:p.argumentsText,sourcePath:p.command.sourcePath})),!this.state.provider.baseURL||!this.state.provider.apiKey){u("[ACodeRuntime] \u26A0\uFE0F No provider configured, using fallback response");const A=ao(h,this.state);this.emitAcodeEvent("assistant_delta",{text:A}),this.emitAcodeEvent("turn_complete",{usage:{inputTokens:h.length,outputTokens:A.length}}),i=!0,u("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (fallback mode)");return}u("[ACodeRuntime] \u{1F4CE} Parsing attachment message...");const T=pe(h);if(T.hasImages&&!this.state.provider.supportsImageInput)throw console.error("[ACodeRuntime] \u274C Image input not supported by current provider"),new Error("ACode provider/model is not configured for image input. Enable provider.default.models[model].modalities.input including 'image' or options.supportsImageInput=true.");u("[ACodeRuntime] \u{1F5BC}\uFE0F Materializing image content if any...");const w=await Fe(T.content);if(this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded after materializing content, aborting");return}u("[ACodeRuntime] \u{1F4AC} Adding user message to conversation"),this.state.conversation.push({role:"user",content:w}),u(`[ACodeRuntime] Conversation size: ${this.state.conversation.length} messages`),u("[ACodeRuntime] Starting runModelTurn...");const C=Date.now(),d=await this.runModelTurn(k,t),y=Date.now()-C;if(u(`[ACodeRuntime] \u2705 runModelTurn completed in ${y}ms`),u(`[ACodeRuntime] \u{1F4DD} Result: text=${d.text?d.text.slice(0,80)+(d.text.length>80?"...":""):"(empty)"}, finishReason=${d.finishReason||"(none)"}`),this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded after runModelTurn, aborting");return}d.text&&!this.state.provider.stream&&(u("[ACodeRuntime] \u{1F4E4} Emitting assistant_delta (non-streaming mode)"),this.emitAcodeEvent("assistant_delta",{text:d.text})),u("[ACodeRuntime] \u{1F4E4} Emitting turn_complete..."),this.emitAcodeEvent("turn_complete",{usage:d.usage??{inputTokens:h.length,outputTokens:d.totalOutputTextLength??d.text.length},...d.responseBody?{responseBody:d.responseBody}:{},...d.finishReason?{finishReason:d.finishReason}:{}}),W(`turn_complete emitted: usage=${JSON.stringify(d.usage??{inputTokens:h.length,outputTokens:d.totalOutputTextLength??d.text.length})} (providerUsage=${d.usage?"real":"FALLBACK-text-length"}) textLength=${d.text.length} totalOutputTextLength=${d.totalOutputTextLength}`),u(`[ACodeRuntime] \u{1F4B0} Current context usage: input=${this.state.accumulatedInputTokens}, output=${this.state.accumulatedOutputTokens}`+(d.usage?"":" (estimated, no provider usage)")),i=!0;const S=Date.now()-o;u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),u(`[ACodeRuntime] \u2705 executeSubmit COMPLETED in ${S}ms`),u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}catch(a){const r=Date.now()-o;if(console.error(`[ACodeRuntime] \u274C executeSubmit FAILED after ${r}ms:`,a instanceof Error?a.message:String(a)),console.error("[ACodeRuntime] \u{1F50D} Error stack:",a instanceof Error?a.stack:"(no stack)"),this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded in error handler, skipping recovery");return}if(this.abortRequested){u("[ACodeRuntime] \u26A0\uFE0F Abort requested, emitting aborted turn_complete"),this.emitAcodeEvent("turn_complete",{aborted:!0}),i=!0;return}u("[ACodeRuntime] Emitting error and failed turn_complete..."),this.emitAcodeEvent("error",{message:a instanceof Error?a.message:String(a)}),this.emitAcodeEvent("turn_complete",{failed:!0}),i=!0}finally{const a=Date.now()-o;u(`[ACodeRuntime] \u{1F51A} executeSubmit finally block entered after ${a}ms`),u(`[ACodeRuntime] \u{1F50D} turnCompleted=${i}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, contextVersion=${t}, currentContextVersion=${this.contextVersion}`),this.activeTurnToken===s&&(this.activeTurn=!1,this.abortRequested=!1),!i&&!this.isTurnSuperseded(t)&&(console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: executeSubmit finally block reached without turn_complete event! Duration: ${a}ms. This indicates a code path that should have emitted turn_complete but didn't. Forcing failed turn_complete to prevent state stuck.`),this.emitAcodeEvent("error",{message:"ACode turn completed without proper completion event (internal error)"}),this.emitAcodeEvent("turn_complete",{failed:!0}),u("[ACodeRuntime] Emergency turn_complete (failed=true) emitted"))}}async abort(){const e=this.abortController!==null,t=this.activeTurn;this.abortRequested=this.activeTurn,this.abortController?(u(`[ACodeRuntime] \u26D4 abort() calling abortController.abort() (activeTurn=${t})`),this.abortController.abort()):console.warn(`[ACodeRuntime] \u26D4 abort() called but abortController is null! activeTurn=${t}, abortRequested will be set to ${t}. This means abort() CANNOT cancel any running fetch/stream \u2014 if a built-in tool is executing, it will NOT be interrupted.`),this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop(),this.armAbortForceSettle()}buildAskUserQuestionDeps(){return{onQuestion:(e,t)=>{this.emitAcodeEvent("question_request",{toolCallId:e,questions:t})},waitForAnswer:(e,t)=>new Promise((o,s)=>{this.questionWaiters.set(e,{resolve:o,reject:s,contextVersion:t})})}}async buildAllocatableToolList(){const e=this.state?.exposedTools??[],t=this.config.subAgentSuiteTools??[];let o=[];if(this.config.subAgentSuiteToolProvider)try{o=await this.config.subAgentSuiteToolProvider([])}catch{o=[]}return ne(e,ne(t,o))}buildSubAgentToolDeps(e){const t=this.config.agentId??"unknown",o=this.config.sessionId,s=this.config.workingDirectory,l=this.config;return{spawnSubAgent:async i=>{let a=this.config.subAgentSuiteTools;if(i.allowedTools&&i.allowedTools.length>0&&this.config.subAgentSuiteToolProvider)try{const c=await this.config.subAgentSuiteToolProvider(i.allowedTools);c.length>0&&(a=a?ne(a,c):c)}catch{}const r=await e.spawn({parentAgentId:t,parentSessionId:o,subagentType:i.subagentType,description:i.description,prompt:i.prompt,...i.allowedTools&&i.allowedTools.length>0?{allowedTools:i.allowedTools}:{},workingDirectory:s,parentConfig:{...l,sharedMcpConnections:this.state?.mcpConnections,...a&&a.length>0?{subAgentSuiteTools:a}:{}}});return await e.start(r),{subId:r}},listSubAgentToolCatalog:async()=>be(await this.buildAllocatableToolList()),listSubAgents:async()=>{const i=e.listByParent(t),a=[],r=[];for(const c of i){const m={subId:c.subId,subagentType:c.subagentType,description:c.description,status:c.status,startedAt:c.startedAt,endedAt:c.endedAt,durationMs:c.durationMs};c.status==="pending"||c.status==="running"?a.push(m):r.push(m)}return r.sort((c,m)=>(m.endedAt??0)-(c.endedAt??0)),{running:a,history:r}},getSubAgentStatus:async i=>{const a=e.getSubAgent(i);return a?{subId:a.subId,subagentType:a.subagentType,description:a.description,status:a.status,startedAt:a.startedAt,endedAt:a.endedAt,durationMs:a.durationMs,prompt:a.prompt,result:a.result,errorMessage:a.errorMessage,contextSnapshot:a.contextSnapshot}:null},awaitSubAgents:async(i,a)=>{const r=new Set;for(const c of i)this.pendingAwaitedSubIds.add(c),r.add(c);try{return await e.awaitCompletion(i,a)}finally{for(const c of r)this.pendingAwaitedSubIds.delete(c)}},abortSubAgent:i=>e.abort(i)}}buildAwaitCompleteDeps(){const e=this.config.agentId??"unknown",t=this.config.subAgentManager;return{agentId:e,awaitSubAgents:t?(o,s)=>{for(const l of o)this.pendingAwaitedSubIds.add(l);try{return this.buildSubAgentToolDeps(t).awaitSubAgents(o,s)}finally{for(const l of o)this.pendingAwaitedSubIds.delete(l)}}:null,awaitBgCommands:(o,s)=>{for(const l of o)this.pendingAwaitedBgTaskIds.add(l);try{return Q.awaitCompletion(e,o,s)}finally{for(const l of o)this.pendingAwaitedBgTaskIds.delete(l)}}}}submitQuestionAnswer(e,t){const o=this.questionWaiters.get(e);return o?(this.questionWaiters.delete(e),this.isTurnSuperseded(o.contextVersion)?(o.reject(new Error("turn superseded before user answered")),!1):(o.resolve(t),!0)):!1}rejectAllQuestionWaiters(e){if(this.questionWaiters.size!==0){for(const[,t]of this.questionWaiters)t.reject(new Error(`ask_user_question ${e}`));this.questionWaiters.clear()}}cancelPendingAwaitedSubAgents(){if(this.pendingAwaitedSubIds.size===0)return;const e=this.config.subAgentManager;if(!e)return;const t=Array.from(this.pendingAwaitedSubIds);u(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited sub-agent(s): ${t.join(", ")}`);for(const o of t)e.abort(o).catch(s=>{u(`[ACodeRuntime] \u26A0\uFE0F manager.abort(${o}) threw: ${s instanceof Error?s.message:String(s)}`)})}cancelPendingAwaitedBgTasks(){if(this.pendingAwaitedBgTaskIds.size===0)return;const e=this.config.agentId??"unknown",t=Array.from(this.pendingAwaitedBgTaskIds);u(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited background command(s): ${t.join(", ")}`),Q.interruptAwaits(e,t)}detachCurrentToolExecution(){const e=this.toolSettleSlot;return!e||!e.settlable||e.settled?!1:(e.settled=!0,e.resolveSettle?.(),u(`[ACodeRuntime] \u{1F680} detachCurrentToolExecution: settling tool ${e.toolName} (toolCallId=${e.toolCallId}) to background`),!0)}beginToolSettle(e,t,o){const s=e.toolName==="Bash"&&typeof t.wait_seconds=="number"&&t.wait_seconds>0,l=++this.toolSettleSeq;let i;const a=s?new Promise(r=>{i=r}):void 0;return this.toolSettleSlot={seq:l,toolName:e.toolName,toolCallId:o,settlable:s,settled:!1,resolveSettle:i??null},{seq:l,signal:a}}clearToolSettle(e){this.toolSettleSlot?.seq===e&&(this.toolSettleSlot=null)}async stop(){if(this.state.terminated)return;this.state.terminated=!0,this.notifyRuntimeStop(),this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.armAbortForceSettle(),this.rejectAllQuestionWaiters("session terminated"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),await V("ACode active turn shutdown",this.submitQueue,t=>{this.emitAcodeEvent("error",{message:t})}),await this.disconnectMcpServers();const e=this.config.agentId||"unknown";if(await V("ACode background commands shutdown",Q.disposeAgent(e),t=>{this.emitAcodeEvent("error",{message:t})}),V("ACode agent PTY sessions shutdown",Promise.resolve(this.config.terminalManager?.killByAgent?.(e)),t=>{this.emitAcodeEvent("error",{message:t})}),await V("ACode Stop hooks",this.hookBus.dispatch("Stop",{reason:"session_stop"}),t=>{this.emitAcodeEvent("error",{message:t})}),this.state.terminated=!0,this.sessionCompleteEmitted||(this.sessionCompleteEmitted=!0,this.emitAcodeEvent("session_complete",{exitCode:0})),this.stopCompactionDebugLog(),this.stopSkillWatchers(),ae)try{G.promises.appendFile(Oe,`[${new Date().toISOString()}] runtime stopped | ---
11
+ `,"utf-8").catch(()=>{})}catch{}},5e3)}stopCompactionDebugLog(){this.compactionDebugTimer&&(clearInterval(this.compactionDebugTimer),this.compactionDebugTimer=null)}async submit(e){const t=this.contextVersion;u(`[ACodeRuntime] \u{1F4EC} submit() queued: contextVersion=${t}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, terminated=${this.state.terminated}, message.length=${e.length}`);const o=this.submitQueue.then(()=>(u(`[ACodeRuntime] \u25B6\uFE0F executeSubmit dequeued: contextVersion=${t}`),this.executeSubmitGuarded(e,t)));return this.submitQueue=o.catch(()=>{u("[ACodeRuntime] \u{1F4EC} submit() queued item rejected (catch swallowed)")}),u("[ACodeRuntime] \u{1F4EC} submit() returning queued promise"),o}executeSubmitGuarded(e,t){const o={};this.activeTurnToken=o;const s=this.executeSubmit(e,t),l=new Promise(i=>{this.forceSettleWaiters.set(o,i)});return s.finally(()=>{this.forceSettleWaiters.delete(o),this.activeTurnToken===o&&(this.abortForceSettleTimer&&(clearTimeout(this.abortForceSettleTimer),this.abortForceSettleTimer=null),this.activeTurnToken=null)}).catch(()=>{}),Promise.race([s,l])}armAbortForceSettle(){if(!this.activeTurn||!this.activeTurnToken)return;const e=this.activeTurnToken;this.abortForceSettleTimer&&clearTimeout(this.abortForceSettleTimer);const t=Mo();this.abortForceSettleTimer=setTimeout(()=>{this.abortForceSettleTimer=null,this.activeTurn&&this.activeTurnToken===e&&this.forceSettleActiveTurn(`turn still unsettled ${t}ms after abort()`)},t),typeof this.abortForceSettleTimer.unref=="function"&&this.abortForceSettleTimer.unref()}forceSettleActiveTurn(e){const t=this.activeTurnToken;console.error(`[ACodeRuntime] \u{1F6A8} FORCE SETTLE: active turn ignored abort() (${e}). Force-settling to unblock submitQueue; late executeSubmit continuation will be superseded.`),this.contextVersion+=1,this.activeTurn=!1,this.abortRequested=!1,this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop(),this.emitAcodeEvent("error",{message:`ACode turn force-settled: ${e}`}),this.emitAcodeEvent("turn_complete",{failed:!0});const o=t?this.forceSettleWaiters.get(t):void 0;t&&this.forceSettleWaiters.delete(t),this.activeTurnToken=null,o?.()}notifyForceMessage(){this.forceMessagePending=!0,u("[ACodeRuntime] \u{1F514} Force message pending flag set")}addBuiltInTools(e){if(!this.state||!this.exposedToolsInitialized||!Array.isArray(e))return 0;const t=new Set(this.state.exposedTools.map(s=>s.exposedName)),o=[];for(const s of e)kt(s)&&(t.has(s.exposedName)||(t.add(s.exposedName),o.push(s)));return o.length===0?0:(this.state.exposedTools=[...this.state.exposedTools,...o],this.emitExposedToolsStatus(),o.length)}removeBuiltInToolsByPrefix(e){if(!this.state||!this.exposedToolsInitialized||!e)return 0;const t=this.state.exposedTools.length;this.state.exposedTools=this.state.exposedTools.filter(s=>!s.exposedName.startsWith(e));const o=t-this.state.exposedTools.length;return o>0&&this.emitExposedToolsStatus(),o}emitExposedToolsStatus(){this.emitAcodeEvent("mcp_status",{servers:this.state.mcpConnections.map(e=>e.serverName),connectedCount:this.state.mcpConnections.length,toolCount:this.state.exposedTools.length,tools:this.state.exposedTools.map(e=>({name:e.exposedName,source:e.kind==="mcp"?`${e.serverName}.${e.toolName}`:`builtin.${e.toolName}`,description:e.description||""}))})}async injectForceMessage(){if(!this.forceMessagePending)return!1;this.forceMessagePending=!1;const e=this.state.mcpConnections.find(t=>t.tools.some(o=>o.toolName==="get_message"));if(!e)return u("[ACodeRuntime] injectForceMessage: no connection with get_message found"),!1;try{const t=await E(e.client.callTool({name:"get_message",arguments:{_aws_force_inject:!0}},void 0,{timeout:1e4}),1e4,"injectForceMessage get_message"),o=O(t);let s=[];try{const r=JSON.parse(o);Array.isArray(r?.directMessages)&&(s=r.directMessages.filter(c=>c?.force===!0))}catch{return!1}if(s.length===0)return!1;const l=JSON.stringify({directMessages:s,groupMessages:[]}),i=`forced_get_msg_${Date.now()}`,a=`${e.serverName}__get_message`;return this.state.conversation.push({role:"assistant",content:null,tool_calls:[{id:i,type:"function",function:{name:a,arguments:"{}"}}]}),this.state.conversation.push({role:"tool",tool_call_id:i,content:l}),u(`[ACodeRuntime] injectForceMessage: injected ${s.length} force message(s) into conversation`),!0}catch(t){return u(`[ACodeRuntime] injectForceMessage failed: ${t}`),!1}}async resetContext(){if(!this.state.started||this.state.terminated)throw new Error("ACode runtime is not active");this.contextVersion+=1,this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.state.conversation=ue(this.initialConversation),this.state.accumulatedInputTokens=0,this.state.accumulatedOutputTokens=0}isTurnSuperseded(e){return e!==this.contextVersion}async executeSubmit(e,t){const o=Date.now();if(u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),u("[ACodeRuntime] \u25B6\uFE0F executeSubmit STARTED"),u(`[ACodeRuntime] Message: <redacted, length=${e.length}>`),u(`[ACodeRuntime] \u{1F522} Context version: ${t}`),u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),!this.state.started||this.state.terminated)throw console.error(`[ACodeRuntime] \u274C Runtime not active (started: ${this.state.started}, terminated: ${this.state.terminated})`),new Error("ACode runtime is not active");this.activeTurn=!0,this.abortRequested=!1;const s=this.activeTurnToken,l=e.trim();let i=!1;try{u("[ACodeRuntime] \u{1F50D} Dispatching UserPromptSubmit hook...");const a=Date.now(),r=await this.hookBus.dispatch("UserPromptSubmit",{prompt:l}),c=Date.now()-a;if(u(`[ACodeRuntime] \u2705 UserPromptSubmit hook completed in ${c}ms, results: ${r.length}`),this.isTurnSuperseded(t)){u("[ACodeRuntime] \u26A0\uFE0F Turn superseded after hook, aborting");return}const m=ce(r);if(m){console.error(`[ACodeRuntime] \u274C Prompt denied by hook: ${m.message}`),this.emitAcodeEvent("error",{message:m.message||"ACode prompt was denied by hook"}),this.emitAcodeEvent("turn_complete",{failed:!0}),i=!0,u("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (failed=true) due to hook deny");return}u("[ACodeRuntime] \u{1F50D} Expanding command if any...");const p=this.state.commandRegistry.expand(l),h=p?.prompt.trim()||l,k=p?.command.allowedTools;if(p&&(u(`[ACodeRuntime] \u{1F4CB} Slash command detected: ${p.command.name}`),this.emitAcodeEvent("command_status",{action:"slash_command",command:p.command.name,arguments:p.argumentsText,sourcePath:p.command.sourcePath})),!this.state.provider.baseURL||!this.state.provider.apiKey){u("[ACodeRuntime] \u26A0\uFE0F No provider configured, using fallback response");const A=ao(h,this.state);this.emitAcodeEvent("assistant_delta",{text:A}),this.emitAcodeEvent("turn_complete",{usage:{inputTokens:h.length,outputTokens:A.length}}),i=!0,u("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (fallback mode)");return}u("[ACodeRuntime] \u{1F4CE} Parsing attachment message...");const T=pe(h);if(T.hasImages&&!this.state.provider.supportsImageInput)throw console.error("[ACodeRuntime] \u274C Image input not supported by current provider"),new Error("ACode provider/model is not configured for image input. Enable provider.default.models[model].modalities.input including 'image' or options.supportsImageInput=true.");u("[ACodeRuntime] \u{1F5BC}\uFE0F Materializing image content if any...");const w=await Fe(T.content);if(this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded after materializing content, aborting");return}u("[ACodeRuntime] \u{1F4AC} Adding user message to conversation"),this.state.conversation.push({role:"user",content:w}),u(`[ACodeRuntime] Conversation size: ${this.state.conversation.length} messages`),u("[ACodeRuntime] Starting runModelTurn...");const C=Date.now(),d=await this.runModelTurn(k,t),y=Date.now()-C;if(u(`[ACodeRuntime] \u2705 runModelTurn completed in ${y}ms`),u(`[ACodeRuntime] \u{1F4DD} Result: text=${d.text?d.text.slice(0,80)+(d.text.length>80?"...":""):"(empty)"}, finishReason=${d.finishReason||"(none)"}`),this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded after runModelTurn, aborting");return}d.text&&!this.state.provider.stream&&(u("[ACodeRuntime] \u{1F4E4} Emitting assistant_delta (non-streaming mode)"),this.emitAcodeEvent("assistant_delta",{text:d.text})),u("[ACodeRuntime] \u{1F4E4} Emitting turn_complete..."),this.emitAcodeEvent("turn_complete",{usage:d.usage??{inputTokens:h.length,outputTokens:d.totalOutputTextLength??d.text.length},...d.responseBody?{responseBody:d.responseBody}:{},...d.finishReason?{finishReason:d.finishReason}:{}}),W(`turn_complete emitted: usage=${JSON.stringify(d.usage??{inputTokens:h.length,outputTokens:d.totalOutputTextLength??d.text.length})} (providerUsage=${d.usage?"real":"FALLBACK-text-length"}) textLength=${d.text.length} totalOutputTextLength=${d.totalOutputTextLength}`),u(`[ACodeRuntime] \u{1F4B0} Current context usage: input=${this.state.accumulatedInputTokens}, output=${this.state.accumulatedOutputTokens}`+(d.usage?"":" (estimated, no provider usage)")),i=!0;const S=Date.now()-o;u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),u(`[ACodeRuntime] \u2705 executeSubmit COMPLETED in ${S}ms`),u("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}catch(a){const r=Date.now()-o;if(console.error(`[ACodeRuntime] \u274C executeSubmit FAILED after ${r}ms:`,a instanceof Error?a.message:String(a)),console.error("[ACodeRuntime] \u{1F50D} Error stack:",a instanceof Error?a.stack:"(no stack)"),this.isTurnSuperseded(t)){u("[ACodeRuntime] \uFE0F Turn superseded in error handler, skipping recovery");return}if(this.abortRequested){u("[ACodeRuntime] \u26A0\uFE0F Abort requested, emitting aborted turn_complete"),this.emitAcodeEvent("turn_complete",{aborted:!0}),i=!0;return}u("[ACodeRuntime] Emitting error and failed turn_complete..."),this.emitAcodeEvent("error",{message:a instanceof Error?a.message:String(a)}),this.emitAcodeEvent("turn_complete",{failed:!0}),i=!0}finally{const a=Date.now()-o;u(`[ACodeRuntime] \u{1F51A} executeSubmit finally block entered after ${a}ms`),u(`[ACodeRuntime] \u{1F50D} turnCompleted=${i}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, contextVersion=${t}, currentContextVersion=${this.contextVersion}`),this.activeTurnToken===s&&(this.activeTurn=!1,this.abortRequested=!1),!i&&!this.isTurnSuperseded(t)&&(console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: executeSubmit finally block reached without turn_complete event! Duration: ${a}ms. This indicates a code path that should have emitted turn_complete but didn't. Forcing failed turn_complete to prevent state stuck.`),this.emitAcodeEvent("error",{message:"ACode turn completed without proper completion event (internal error)"}),this.emitAcodeEvent("turn_complete",{failed:!0}),u("[ACodeRuntime] Emergency turn_complete (failed=true) emitted"))}}async abort(){const e=this.abortController!==null,t=this.activeTurn;this.abortRequested=this.activeTurn,this.abortController?(u(`[ACodeRuntime] \u26D4 abort() calling abortController.abort() (activeTurn=${t})`),this.abortController.abort()):console.warn(`[ACodeRuntime] \u26D4 abort() called but abortController is null! activeTurn=${t}, abortRequested will be set to ${t}. This means abort() CANNOT cancel any running fetch/stream \u2014 if a built-in tool is executing, it will NOT be interrupted.`),this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop(),this.armAbortForceSettle()}buildAskUserQuestionDeps(){return{onQuestion:(e,t)=>{this.emitAcodeEvent("question_request",{toolCallId:e,questions:t})},waitForAnswer:(e,t)=>new Promise((o,s)=>{this.questionWaiters.set(e,{resolve:o,reject:s,contextVersion:t})})}}async buildAllocatableToolList(){const e=this.state?.exposedTools??[],t=this.config.subAgentSuiteTools??[];let o=[];if(this.config.subAgentSuiteToolProvider)try{o=await this.config.subAgentSuiteToolProvider([])}catch{o=[]}return ne(e,ne(t,o))}buildSubAgentToolDeps(e){const t=this.config.agentId??"unknown",o=this.config.sessionId,s=this.config.workingDirectory,l=this.config;return{spawnSubAgent:async i=>{let a=this.config.subAgentSuiteTools;if(i.allowedTools&&i.allowedTools.length>0&&this.config.subAgentSuiteToolProvider)try{const c=await this.config.subAgentSuiteToolProvider(i.allowedTools);c.length>0&&(a=a?ne(a,c):c)}catch{}const r=await e.spawn({parentAgentId:t,parentSessionId:o,subagentType:i.subagentType,description:i.description,prompt:i.prompt,...i.allowedTools&&i.allowedTools.length>0?{allowedTools:i.allowedTools}:{},workingDirectory:s,parentConfig:{...l,sharedMcpConnections:this.state?.mcpConnections,...a&&a.length>0?{subAgentSuiteTools:a}:{}}});return await e.start(r),{subId:r}},listSubAgentToolCatalog:async()=>be(await this.buildAllocatableToolList()),listSubAgents:async()=>{const i=e.listByParent(t),a=[],r=[];for(const c of i){const m={subId:c.subId,subagentType:c.subagentType,description:c.description,status:c.status,startedAt:c.startedAt,endedAt:c.endedAt,durationMs:c.durationMs};c.status==="pending"||c.status==="running"?a.push(m):r.push(m)}return r.sort((c,m)=>(m.endedAt??0)-(c.endedAt??0)),{running:a,history:r}},getSubAgentStatus:async i=>{const a=e.getSubAgent(i);return a?{subId:a.subId,subagentType:a.subagentType,description:a.description,status:a.status,startedAt:a.startedAt,endedAt:a.endedAt,durationMs:a.durationMs,prompt:a.prompt,result:a.result,errorMessage:a.errorMessage,contextSnapshot:a.contextSnapshot}:null},awaitSubAgents:async(i,a)=>{const r=new Set;for(const c of i)this.pendingAwaitedSubIds.add(c),r.add(c);try{return await e.awaitCompletion(i,a)}finally{for(const c of r)this.pendingAwaitedSubIds.delete(c)}},abortSubAgent:i=>e.abort(i)}}buildAwaitCompleteDeps(){const e=this.config.agentId??"unknown",t=this.config.subAgentManager;return{agentId:e,awaitSubAgents:t?(o,s)=>{for(const l of o)this.pendingAwaitedSubIds.add(l);try{return this.buildSubAgentToolDeps(t).awaitSubAgents(o,s)}finally{for(const l of o)this.pendingAwaitedSubIds.delete(l)}}:null,awaitBgCommands:(o,s)=>{for(const l of o)this.pendingAwaitedBgTaskIds.add(l);try{return Q.awaitCompletion(e,o,s)}finally{for(const l of o)this.pendingAwaitedBgTaskIds.delete(l)}}}}submitQuestionAnswer(e,t){const o=this.questionWaiters.get(e);return o?(this.questionWaiters.delete(e),this.isTurnSuperseded(o.contextVersion)?(o.reject(new Error("turn superseded before user answered")),!1):(o.resolve(t),!0)):!1}rejectAllQuestionWaiters(e){if(this.questionWaiters.size!==0){for(const[,t]of this.questionWaiters)t.reject(new Error(`ask_user_question ${e}`));this.questionWaiters.clear()}}cancelPendingAwaitedSubAgents(){if(this.pendingAwaitedSubIds.size===0)return;const e=this.config.subAgentManager;if(!e)return;const t=Array.from(this.pendingAwaitedSubIds);u(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited sub-agent(s): ${t.join(", ")}`);for(const o of t)e.abort(o).catch(s=>{u(`[ACodeRuntime] \u26A0\uFE0F manager.abort(${o}) threw: ${s instanceof Error?s.message:String(s)}`)})}cancelPendingAwaitedBgTasks(){if(this.pendingAwaitedBgTaskIds.size===0)return;const e=this.config.agentId??"unknown",t=Array.from(this.pendingAwaitedBgTaskIds);u(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited background command(s): ${t.join(", ")}`),Q.interruptAwaits(e,t)}detachCurrentToolExecution(){const e=this.toolSettleSlot;return!e||!e.settlable||e.settled?!1:(e.settled=!0,e.resolveSettle?.(),u(`[ACodeRuntime] \u{1F680} detachCurrentToolExecution: settling tool ${e.toolName} (toolCallId=${e.toolCallId}) to background`),!0)}beginToolSettle(e,t,o){const s=e.toolName==="Bash",l=++this.toolSettleSeq;let i;const a=s?new Promise(r=>{i=r}):void 0;return this.toolSettleSlot={seq:l,toolName:e.toolName,toolCallId:o,settlable:s,settled:!1,resolveSettle:i??null},{seq:l,signal:a}}clearToolSettle(e){this.toolSettleSlot?.seq===e&&(this.toolSettleSlot=null)}async stop(){if(this.state.terminated)return;this.state.terminated=!0,this.notifyRuntimeStop(),this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.armAbortForceSettle(),this.rejectAllQuestionWaiters("session terminated"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),await V("ACode active turn shutdown",this.submitQueue,t=>{this.emitAcodeEvent("error",{message:t})}),await this.disconnectMcpServers();const e=this.config.agentId||"unknown";if(await V("ACode background commands shutdown",Q.disposeAgent(e),t=>{this.emitAcodeEvent("error",{message:t})}),V("ACode agent PTY sessions shutdown",Promise.resolve(this.config.terminalManager?.killByAgent?.(e)),t=>{this.emitAcodeEvent("error",{message:t})}),await V("ACode Stop hooks",this.hookBus.dispatch("Stop",{reason:"session_stop"}),t=>{this.emitAcodeEvent("error",{message:t})}),this.state.terminated=!0,this.sessionCompleteEmitted||(this.sessionCompleteEmitted=!0,this.emitAcodeEvent("session_complete",{exitCode:0})),this.stopCompactionDebugLog(),this.stopSkillWatchers(),ae)try{G.promises.appendFile(Oe,`[${new Date().toISOString()}] runtime stopped | ---
12
12
  `,"utf-8").catch(()=>{})}catch{}}resolveCommandDirs(e){const t=[...this.config.commandDirs??[],...e.commandDirs??[],...e.commands?.directories??[]];return[...new Set(t.map(o=>o.trim()).filter(Boolean))]}async runModelTurn(e,t=this.contextVersion){let o="",s,l,i,a="",r=0;this.lastIterationUsage=void 0,u(`[ACodeRuntime] \u25B6\uFE0F runModelTurn started (contextVersion: ${t})`);const c=50;for(let m=0;m<c;m+=1){if(u(`[ACodeRuntime] \u{1F504} Iteration ${m+1} starting...`),this.isTurnSuperseded(t))return u(`[ACodeRuntime] \u26A0\uFE0F Turn superseded at iteration ${m+1}, aborting`),{text:"",totalOutputTextLength:a.length};await this.injectForceMessage();const p=Date.now(),h=await this.requestChatCompletion(e),k=Date.now()-p;if(u(`[ACodeRuntime] \u{1F916} AI request completed in ${k}ms, choices: ${h.choices?.length||0}`),this.isTurnSuperseded(t))return u(`[ACodeRuntime] \u26A0\uFE0F Turn superseded after AI response at iteration ${m+1}`),{text:"",totalOutputTextLength:a.length};if(this.state.terminated)throw u(`[ACodeRuntime] Runtime terminated at iteration ${m+1}`),new Error("ACode runtime was stopped during the active turn");const T=h.choices?.[0];l=JSON.stringify(h,null,2),i=typeof T?.finish_reason=="string"?T.finish_reason:void 0;const w=T?.message,C=typeof w?.content=="string"?w.content:"",d=Array.isArray(w?.tool_calls)?w.tool_calls:[];if(u(`[ACodeRuntime] AI response: content=${C?C.slice(0,100)+(C.length>100?"...":""):"(empty)"}, toolCalls=${d.length}, finishReason=${i||"(none)"}`),h.usage){const M=U(h.usage.prompt_tokens??h.usage.input_tokens),$=U(h.usage.completion_tokens??h.usage.output_tokens),R=M!==void 0,f=$!==void 0;if(W(`provider usage parsed: raw=${JSON.stringify(h.usage)} input=${M} output=${$} hasInput=${R} hasOutput=${f} iteration=${m+1}`),R||f){const _=vt(h.usage,M),v=s?.outputTokens??a.length+C.length;this.state.accumulatedCacheHitTokens+=_?.cacheHitTokens??0,this.state.accumulatedCacheMissTokens+=_?.cacheMissTokens??0,s={inputTokens:R?M:s?.inputTokens??0,outputTokens:f?$:v,cacheHitTokens:this.state.accumulatedCacheHitTokens,cacheMissTokens:this.state.accumulatedCacheMissTokens},this.lastIterationUsage=s,r+=R?M:0,this.state.accumulatedInputTokens=R?M:this.state.accumulatedInputTokens,this.state.accumulatedOutputTokens=f?$:this.state.accumulatedOutputTokens,W(`finalUsage updated: ${JSON.stringify(s)} (cacheUsage=${JSON.stringify(_??null)})`),u(`[ACodeRuntime] \u{1F4B0} Token usage: input=${s.inputTokens}, output=${s.outputTokens} (turn accumulated cost: input=${r})`),u(_?`[ACodeRuntime] \u{1F4B0} Cache usage (provider reported): hit=${_.cacheHitTokens}, miss=${_.cacheMissTokens}`:'[ACodeRuntime] \u{1F4B0} Cache usage: provider usage \u4E2D\u65E0\u7F13\u5B58\u5B57\u6BB5\uFF08\u9762\u677F\u5C06\u663E\u793A "-"\uFF09')}}else W(`provider response has NO usage field (fallback to text-length estimate), iteration=${m+1}`);if(this.state.conversation.push({role:"assistant",content:C||null,tool_calls:d.length>0?d:void 0}),C&&(o=C,a+=C,u(`[ACodeRuntime] \u2728 Captured assistant text: ${C.slice(0,80)}...`)),d.length===0)return u(`[ACodeRuntime] \u2705 No tool calls at iteration ${m+1}, completing turn`),{text:o,usage:s,responseBody:l,finishReason:i,totalOutputTextLength:a.length};const y=d.map(M=>M.function.name).join(", ");u(`[ACodeRuntime] \u{1F527} Executing ${d.length} tool(s) at iteration ${m+1}: ${y}`);const S=Date.now();await this.handleToolCalls(d,e,t);const A=Date.now()-S;if(u(`[ACodeRuntime] \u2699\uFE0F Tool execution completed in ${A}ms at iteration ${m+1}`),this.isTurnSuperseded(t))return u(`[ACodeRuntime] \u26A0\uFE0F Turn superseded after tool execution at iteration ${m+1}`),{text:"",totalOutputTextLength:a.length};u(`[ACodeRuntime] \u{1F504} Preparing iteration ${m+2}...`)}return console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: Reached safety limit of ${c} iterations! This indicates a potential infinite loop. Forcing turn completion.`),{text:o||`ACode \u5DF2\u8FBE\u5230\u5B89\u5168\u8FED\u4EE3\u4E0A\u9650\uFF08${c}\u6B21\uFF09\uFF0C\u5F3A\u5236\u7ED3\u675F\u672C\u8F6E\u5BF9\u8BDD\u3002`,usage:s,responseBody:l,finishReason:i||"safety_iteration_limit_reached",totalOutputTextLength:a.length}}async summarizeConversationWithLLM(e){const t=this.state.provider,o=t.baseURL;if(!o)throw new Error("ACode provider baseURL is not configured for LLM summarization");const s={"Content-Type":"application/json"};t.apiKey&&(s.Authorization=`Bearer ${t.apiKey}`);const l={model:t.model,stream:!1,messages:[{role:"system",content:"You are a helpful AI assistant tasked with summarizing conversations. When asked to summarize, provide a detailed but concise summary of the conversation. Focus on information that would be helpful for continuing the conversation, especially what was in progress and what needs to happen next. Do not respond to any questions in the conversation, only output the summary."},{role:"user",content:`Below is an earlier conversation (${e.messageCount} messages) that needs to be summarized to free context space (current context limit: ${e.contextLimit} tokens).
13
13
 
14
14
  --- Conversation Start ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.9.126",
3
+ "version": "1.9.130",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",