aws-runtime-bridge 1.9.149 → 1.9.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,61 @@ files 白名单),供调度中心「查看本次更新内容」弹窗按 `##
5
5
  展示。发布新版本时须在仓库中先写好 `## v<version>` 段(prepack-guard 校验缺段
6
6
  即阻断发布);Bridge 自更新成功后的运行时追加仅作为包内缺段时的兜底。
7
7
 
8
+ ## v1.9.151
9
+
10
+ _发布日期:2026-09-07_
11
+
12
+ - 工作区 LSP(TypeScript/JavaScript 语言服务)全链路接入:Dashboard 文件编辑器可对实例工作区的
13
+ TS/TSX/JS/JSX 文件提供语义级定义跳转与引用查找,不再依赖字符串搜索。
14
+ - runtime-bridge:新增 `typescript-language-server` 与 `typescript` 运行时依赖;`src/services/lsp-stdio-bridge.ts`
15
+ 封装 LSP stdio 帧编解码(Content-Length 增量解析、畸形流防护)与子进程桥(自动解析 tls 入口、注入
16
+ TSSERVER_PATH 保证 tsserver 可启动);`src/routes/lsp.ts` 暴露 `/ws/lsp` WebSocket——单 workspace 单进程
17
+ 复用、60s 空闲回收、复用进程再次收到 initialize 时重建进程(避免 tsserver 沿用旧文档状态陷入
18
+ "No Project"、跳转恒空)。
19
+ - aws-mcp-server:`WorkspaceLspProxyService` 在 HTTP 侧校验 workspace 归属后签发 5 分钟一次性令牌
20
+ (TTL 放宽覆盖慢建连、消费即失效防重放);`WorkspaceLspWebSocketHandler` 把浏览器 `/ws/workspace-lsp/{token}`
21
+ 双向转发到 bridge `/ws/lsp`,隧道模式经 BridgeTunnelService 虚拟流、直连模式带 X-Runtime-Token 握手头;
22
+ `FileBrowserProxyService.resolveWorkspaceLspBridge` 解析 bridge 目标(保留隧道实例前缀)与鉴权令牌。
23
+ - aws-dashboard:`workspaceLsp.ts` 封装 CodeMirror LSP 会话——同一 bridge+workspace 仅建立一条 WS 与
24
+ 一次 initialize,切换文件仅替换 per-file 句柄(didOpen/didClose),避免重复 initialize 导致跳转失效;
25
+ 补充库未内置的 textDocument/references 与 definition 请求、自定义 transport 占位。`WorkspacePanel.vue`
26
+ 接入 LSP 扩展、Ctrl+点击/快捷键跳转定义与引用查找;对 Windows 盘符修正 file:// 三层斜杠与 URI 归一比较。
27
+ - 确认弹窗交互调整:确认按钮移至取消按钮左侧并作为默认焦点,Enter 键即触发确认,Tab 焦点循环顺序与
28
+ 视觉排布一致(确认左、取消右)。
29
+ - 工作区面板媒体预览:图片 / SVG / 视频预览改为经下载接口生成 object URL 展示,支持 B 站式画面缩放
30
+ (Ctrl+滚轮 / 缩放按钮)与按住空格+拖动平移放大后画面,双击复位;预览状态栏同步显示媒体尺寸与状态。
31
+
32
+ ## v1.9.150
33
+
34
+ _发布日期:2026-09-07_
35
+
36
+ - 长文引用(LTR)全链路化:私信超长文本粘贴不再全文进消息,改为引用上传——MCP 与 bridge
37
+ 新增 `/api/ltr/upload`,把大文本片段落盘到目标机器 `<系统临时目录>/aw-temp/LTR/YYYYMMDD/{uuid}.txt`
38
+ (单篇 ≤512KB,按天归档、写入时顺带清理超 7 天的目录),消息只传 `[bridgeName:绝对路径]` 引用,
39
+ agent 用 read_file 读取全文,避免超长文本撑爆消息表(MySQL TEXT 65535 字节)与 LLM 上下文。
40
+ 前端配套:粘贴转胶囊 token(发送时统一上传 + 进度提示、hover 预览全文、未上传时随草稿保留并在
41
+ 切换会话后恢复);LTR 图标与 capsule 颜色跟随主题。
42
+ - 后台命令僵尸状态根治(L1–L4):读路径对账(面板每次刷新先翻"磁盘 running 但本进程内存不命中"
43
+ 的孤儿为 killed(runtime_crashed) 并原子回写);状态写入事务化(per-agent 串行写链消除并发
44
+ heartbeat/finalize 的丢失更新);生命周期补齐(exit+close 双监听,exit 后 2s 宽限强制 finalize,
45
+ 孙进程持续持有管道不再留下永远 running 的状态;进程退出时落盘 killed(process_exit));running
46
+ 任务心跳节流落盘(5s 或输出增量 64KB 触发)+ 启动时全量扫描所有 agent 目录孤儿。
47
+ 面板改为只展示"真后台/运行中"命令,前台同步执行、从未转后台的命令不再占用面板。
48
+ - open-pencil CLI 多路径探测:pencil_export_document 依次探测 `AWS_OPEN_PENCIL_HOME` →
49
+ `<工作区>/open-pencil` → 仓库相对路径 → 逐级向上回溯,未命中时错误信息列出全部探测路径;
50
+ README 补充生产部署前置(工作区锚点或环境变量二选一,须用仓库构建版 CLI 以支持 GraphSnapshot)。
51
+ - create_meeting 有效性预检:角色占坑席位无可认领实例(如该角色唯一实例就是创建人)、或创建人把
52
+ 自己重复加入受邀成员时直接返回错误,不再进入无谓的等待。
53
+ - 群消息撤回语义修正:发送者本人游标越过已撤回消息仍可撤回;他人游标越过则拒绝撤回(撤回不再
54
+ 被"阅读过即作废"误伤)。两项修复均补齐单测。
55
+ - 输入框编辑体验:自建 undo/redo 栈(浏览器原生撤销对 contenteditable + 手动 DOM 不可靠),
56
+ mention / command / LTR 原子 token 可整体撤销;@mention 输入确认后自动把自由文本原地转换为
57
+ mention 高亮 token(`--cp-mention-highlight-*` 亮暗主题双套);「调用式回复」更名为「强调回复」。
58
+ - 文件下载回源文件名:下载/预览响应 Content-Disposition 改用上传时的原始文件名(URL name 参数),
59
+ 并清洗控制字符、引号、分号与路径分隔符,避免 header 畸形与路径穿越。
60
+ - 消息中心:项目在线成员列表去重(同一 agent 绑定多实例时不再重复头像)。
61
+ - 健壮性:后台命令状态持久化失败仅告警不再崩进程;LTR 上传上游失败统一 502 且不回传内部错误细节。
62
+
8
63
  ## v1.9.149
9
64
 
10
65
  _发布日期:2026-09-06_
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{existsSync as T,readFileSync as D}from"node:fs";import M from"node:os";import S from"node:path";import E from"cors";import P from"express";import{adapterRegistry as x}from"./adapter/index.js";import{attachBrowserWebSocketServer as F}from"./browser/browser-ws-bridge.js";import{getAcodeDataHome as G,getActiveBridgeHttpPort as I,getAutoRegisterConfigFilePath as O,getBridgeDataHome as _,getBridgeRuntimePropertiesFilePath as K,getBridgeTemplateConfigFile as L,reserveBridgeListenPort as N,validateProductionToken as j,writeBridgeRuntimePropertiesFile as W}from"./config.js";import{attachRemoteDesktopWebSocketServer as H}from"./remote-desktop/index.js";import{acodeTodosRouter as U}from"./routes/acode-todos.js";import{aiSourcesRouter as z}from"./routes/ai-sources.js";import{backgroundTasksRouter as J}from"./routes/background-tasks.js";import{browserListenersRouter as Y}from"./routes/browser-listeners.js";import{browserSessionsRouter as Z}from"./routes/browser-sessions.js";import{capabilitiesRouter as q}from"./routes/capabilities.js";import{computerRouter as Q}from"./routes/computer.js";import{dashboardRouter as V}from"./routes/dashboard.js";import{eventsRouter as X}from"./routes/events.js";import{fileBrowserRouter as ee}from"./routes/file-browser.js";import{fileChangeDiffRouter as re}from"./routes/file-change-diff.js";import{fileDiffRollbackRouter as te}from"./routes/file-diff-rollback.js";import{fileSnapshotRouter as ie}from"./routes/file-snapshot.js";import{gitRouter as oe}from"./routes/git.js";import{instanceRouter as ne,setGracefulShutdownFn as se}from"./routes/instance.js";import{jdksRouter as ae}from"./routes/jdks.js";import{launchRouter as me}from"./routes/launch.js";import{mcpExtraRouter as ue}from"./routes/mcp-extra.js";import{mcpRouter as ce}from"./routes/mcp.js";import{pluginsRouter as de}from"./routes/plugins.js";import{processesRouter as ge}from"./routes/processes.js";import{propertiesRouter as pe}from"./routes/properties.js";import{attachPtyWebSocketServer as fe,closeAllPtySessions as le,ptyRouter as be,ptySessions as we}from"./routes/pty.js";import{runtimeBindingRouter as he,logRuntimeBindingStartupState as Re}from"./routes/runtime-binding.js";import{sessionsRouter as Se}from"./routes/sessions.js";import{skillsRouter as Pe}from"./routes/skills.js";import{subAgentsRouter as Ie}from"./routes/sub-agents.js";import{systemMetricsRouter as ye}from"./routes/system-metrics.js";import{taskContextRouter as $e}from"./routes/task-context.js";import{terminalRouter as ke,sdkSessions as h}from"./routes/terminal.js";import{ymlRouter as Ce}from"./routes/yml.js";import{accessControlMiddleware as ve}from"./services/access-control.js";import{ensureAcodePackageReleased as Ae}from"./services/acode-package.js";import{getAgentProcessManager as y}from"./services/agent-process-manager.js";import{autoRegister as Be,unregister as Te,bridgeRestartCleanup as De,cancelBackgroundRegisterRetries as Me}from"./services/auto-register.js";import{ensureAwsClientAgentMcpReleased as Ee}from"./services/aws-client-agent-mcp.js";import{disposeBrowserSessionManager as xe}from"./services/browser-session-manager.js";import{handleCliCommand as Fe}from"./services/cli-commands.js";import{disposeDesktopMcpManager as Ge}from"./services/desktop-mcp-manager.js";import{getLaunchManager as Oe}from"./services/launch-manager.js";import{migrateOldPathsOnStartup as _e}from"./services/legacy-path-migration.js";import{deliverPendingBridgeLifecycleNotifications as Ke}from"./services/lifecycle-state.js";import{startOrphanMonitor as Le,stopOrphanMonitor as Ne}from"./services/orphan-monitor.js";import{pluginManager as je}from"./services/plugin-manager.js";import{detectOrphanProcesses as We}from"./services/process-detector.js";import{buildGracefulShutdownPlan as He,shouldCleanupPersistedSessionsOnStartup as Ue}from"./services/runtime-lifecycle-policy.js";import{ensureStartupConfig as ze}from"./services/startup-config-wizard.js";import{subAgentStore as Je}from"./services/sub-agent-manager-instance.js";import{loadPersistedSessions as $,savePersistedSessions as k}from"./services/terminal-persistence.js";import{stopTunnelClient as Ye}from"./services/tunnel-client.js";import{logger as e}from"./utils/logger.js";const C=await Fe();C.handled&&process.exit(C.exitCode),await ze(),j(),Ee(),Ae(),_e(),Re(),await N(),W(),e.info(`[runtime-bridge] \u5DF2\u5199\u5165\u8FD0\u884C\u65F6\u914D\u7F6E ${K()}\uFF08port=${I()}\uFF09`),e.info(`[runtime-bridge] ACode \u6570\u636E\u76EE\u5F55(\u7528\u6237): ${G()}`),e.info(`[runtime-bridge] Bridge \u6570\u636E\u76EE\u5F55: ${_()}`),e.info(`[runtime-bridge] \u6A21\u677F\u914D\u7F6E: ${L()}`);async function Ze(){try{if(await Be())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:o}=await import("./services/auto-register.js"),s=o();!!s.userKey&&s.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info(`[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ${O()}`))}}catch(r){const o=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",o.message)}}Ze().then(()=>qe()).then(()=>Ke()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)}),import("./services/update-notifier.js").then(({startUpdateNotifier:r})=>r()).catch(r=>{e.warn("[runtime-bridge] \u7248\u672C\u68C0\u67E5\u670D\u52A1\u542F\u52A8\u5931\u8D25:",r.message)});async function qe(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),o=r();if(!o.registered||!o.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const s=await De();s.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${s.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${s.error}`)}catch(r){const o=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",o.message)}}async function Qe(){try{const r=await $();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await y().rebuildRegistry(r.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const o=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",o.message)}}Qe().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u51FA\u9519:",r)});async function Ve(){try{const r=await import("node:fs/promises"),o=await import("node:path"),s=await import("node:os"),f=o.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await $();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!Ue(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await y().rebuildRegistry(a.map(n=>({agentId:n.agentId,sessionId:n.sessionId,pid:n.pid,workspacePath:n.workspacePath,command:n.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await We(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:n}=await import("node:child_process");n(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(n){const c=n;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await k([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const o=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",o.message)}}Ve().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),Le().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const i=P();i.use(ve),i.use(E()),i.use(P.json({limit:"1mb"})),i.use("/runtime",he),i.use("/runtime",pe),i.use("/runtime",Ce),i.use("/runtime",ne),i.use("/runtime",Pe),i.use("/runtime",ce),i.use("/runtime",q),i.use("/runtime",ke),i.use("/runtime",Se),i.use("/runtime",ge),i.use("/runtime",me),i.use("/runtime",ae),i.use("/runtime",ye),i.use("/runtime",X),i.use("/runtime",oe),i.use("/runtime",ie),i.use("/runtime",U),i.use("/runtime",J),i.use("/runtime",Ie),i.use("/runtime",Z),i.use("/runtime",Y),i.use("/runtime",de),i.use("/runtime",ue),i.use("/runtime",z),i.use("/runtime",$e),i.use("/runtime",te),i.use("/runtime",re),i.use("/runtime/computer",Q),i.use("/pty",be),i.use("/",V),i.use("/api/file-browser",ee);function Xe(){const r=I(),o=i.listen(r,()=>{e.info(`[runtime-bridge] listening on ${r}`)});return fe(o),H(o),F(o),o.on("error",s=>{if(s.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${r} became unavailable after reservation (another process grabbed it concurrently). Set AWS_RUNTIME_BRIDGE_PORT to a free port and restart.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",s),process.exitCode=1}),o}let v=!1;async function b(r,o,s=!1){if(v){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}v=!0,s||R()&&(s=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${s})`);const f=Date.now(),u=He(s);try{const a=h.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,n]of h.entries())try{const c=n.providerId||"claude-code",g=x.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),h.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),h.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=we.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),le("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`);try{const{running:t,stopped:n}=await Oe().stopAll();t>0&&e.info(`[runtime-bridge] \u542F\u52A8\u9879\u8FDB\u7A0B\u5DF2\u505C\u6B62: running=${t}, stopped=${n}`)}catch(t){e.warn("[runtime-bridge] \u505C\u6B62\u542F\u52A8\u9879\u8FDB\u7A0B\u5931\u8D25:",t.message)}e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+S.join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(Ne(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:n}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const B=await n(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${B.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const n=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",n.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await Te()}catch(t){const n=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(Me(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),Ye()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await k([])}catch(t){const n=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await xe(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}try{await Ge(),e.info("[runtime-bridge] desktop \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] desktop \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}try{await je.stopAll(),e.info("[runtime-bridge] \u63D2\u4EF6\u76D1\u542C\u8FDB\u7A0B\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] \u63D2\u4EF6\u76D1\u542C\u8FDB\u7A0B\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),o.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Je.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const w=Xe(),A=S.join(M.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(T(A)){const r=D(A,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}se(async(r,o)=>{await b(r,w,o)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",w,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",w,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",w,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",w)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
2
+ import{existsSync as T,readFileSync as D}from"node:fs";import M from"node:os";import S from"node:path";import{pathToFileURL as E}from"node:url";import x from"cors";import P from"express";import{adapterRegistry as F}from"./adapter/index.js";import{attachBrowserWebSocketServer as G}from"./browser/browser-ws-bridge.js";import{getAcodeDataHome as O,getActiveBridgeHttpPort as y,getAutoRegisterConfigFilePath as L,getBridgeDataHome as _,getBridgeRuntimePropertiesFilePath as j,getBridgeTemplateConfigFile as K,reserveBridgeListenPort as N,validateProductionToken as W,writeBridgeRuntimePropertiesFile as H}from"./config.js";import{attachRemoteDesktopWebSocketServer as U}from"./remote-desktop/index.js";import{acodeTodosRouter as z}from"./routes/acode-todos.js";import{aiSourcesRouter as J}from"./routes/ai-sources.js";import{backgroundTasksRouter as Y}from"./routes/background-tasks.js";import{browserListenersRouter as Z}from"./routes/browser-listeners.js";import{browserSessionsRouter as q}from"./routes/browser-sessions.js";import{capabilitiesRouter as Q}from"./routes/capabilities.js";import{computerRouter as V}from"./routes/computer.js";import{dashboardRouter as X}from"./routes/dashboard.js";import{eventsRouter as ee}from"./routes/events.js";import{fileBrowserRouter as re}from"./routes/file-browser.js";import{fileChangeDiffRouter as te}from"./routes/file-change-diff.js";import{fileDiffRollbackRouter as ie}from"./routes/file-diff-rollback.js";import{fileSnapshotRouter as oe}from"./routes/file-snapshot.js";import{gitRouter as ne}from"./routes/git.js";import{instanceRouter as se,setGracefulShutdownFn as ae}from"./routes/instance.js";import{jdksRouter as me}from"./routes/jdks.js";import{launchRouter as ue}from"./routes/launch.js";import{attachLspWebSocketServer as ce}from"./routes/lsp.js";import{ltrRouter as de}from"./routes/ltr.js";import{mcpExtraRouter as ge}from"./routes/mcp-extra.js";import{mcpRouter as pe}from"./routes/mcp.js";import{pluginsRouter as fe}from"./routes/plugins.js";import{processesRouter as le}from"./routes/processes.js";import{propertiesRouter as be}from"./routes/properties.js";import{attachPtyWebSocketServer as he,closeAllPtySessions as we,ptyRouter as Re,ptySessions as Se}from"./routes/pty.js";import{runtimeBindingRouter as Pe,logRuntimeBindingStartupState as ye}from"./routes/runtime-binding.js";import{sessionsRouter as Ie}from"./routes/sessions.js";import{skillsRouter as $e}from"./routes/skills.js";import{subAgentsRouter as ke}from"./routes/sub-agents.js";import{systemMetricsRouter as Ae}from"./routes/system-metrics.js";import{taskContextRouter as Ce}from"./routes/task-context.js";import{terminalRouter as ve,sdkSessions as w}from"./routes/terminal.js";import{ymlRouter as Be}from"./routes/yml.js";import{accessControlMiddleware as Te}from"./services/access-control.js";import{ensureAcodePackageReleased as De,getReleasedAcodeEntryPath as Me}from"./services/acode-package.js";import{getAgentProcessManager as I}from"./services/agent-process-manager.js";import{autoRegister as Ee,unregister as xe,bridgeRestartCleanup as Fe,cancelBackgroundRegisterRetries as Ge}from"./services/auto-register.js";import{ensureAwsClientAgentMcpReleased as Oe}from"./services/aws-client-agent-mcp.js";import{disposeBrowserSessionManager as Le}from"./services/browser-session-manager.js";import{handleCliCommand as _e}from"./services/cli-commands.js";import{disposeDesktopMcpManager as je}from"./services/desktop-mcp-manager.js";import{getLaunchManager as Ke}from"./services/launch-manager.js";import{migrateOldPathsOnStartup as Ne}from"./services/legacy-path-migration.js";import{deliverPendingBridgeLifecycleNotifications as We}from"./services/lifecycle-state.js";import{startOrphanMonitor as He,stopOrphanMonitor as Ue}from"./services/orphan-monitor.js";import{pluginManager as ze}from"./services/plugin-manager.js";import{detectOrphanProcesses as Je}from"./services/process-detector.js";import{buildGracefulShutdownPlan as Ye,shouldCleanupPersistedSessionsOnStartup as Ze}from"./services/runtime-lifecycle-policy.js";import{ensureStartupConfig as qe}from"./services/startup-config-wizard.js";import{subAgentStore as Qe}from"./services/sub-agent-manager-instance.js";import{loadPersistedSessions as $,savePersistedSessions as k}from"./services/terminal-persistence.js";import{stopTunnelClient as Ve}from"./services/tunnel-client.js";import{logger as e}from"./utils/logger.js";const A=await _e();A.handled&&process.exit(A.exitCode),await qe(),W(),Oe(),De(),Ne(),ye(),await N(),H(),e.info(`[runtime-bridge] \u5DF2\u5199\u5165\u8FD0\u884C\u65F6\u914D\u7F6E ${j()}\uFF08port=${y()}\uFF09`),e.info(`[runtime-bridge] ACode \u6570\u636E\u76EE\u5F55(\u7528\u6237): ${O()}`),e.info(`[runtime-bridge] Bridge \u6570\u636E\u76EE\u5F55: ${_()}`),e.info(`[runtime-bridge] \u6A21\u677F\u914D\u7F6E: ${K()}`);async function Xe(){try{if(await Ee())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:o}=await import("./services/auto-register.js"),n=o();!!n.userKey&&n.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info(`[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ${L()}`))}}catch(r){const o=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",o.message)}}Xe().then(()=>er()).then(()=>We()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)}),import("./services/update-notifier.js").then(({startUpdateNotifier:r})=>r()).catch(r=>{e.warn("[runtime-bridge] \u7248\u672C\u68C0\u67E5\u670D\u52A1\u542F\u52A8\u5931\u8D25:",r.message)});async function er(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),o=r();if(!o.registered||!o.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const n=await Fe();n.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${n.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${n.error}`)}catch(r){const o=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",o.message)}}async function rr(){try{const r=await $();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await I().rebuildRegistry(r.map(n=>({agentId:n.agentId,sessionId:n.sessionId,pid:n.pid,workspacePath:n.workspacePath,command:n.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const o=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",o.message)}}rr().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",r)}),import(E(S.join(Me(),"..","background-command-manager.js")).href).then(async r=>{const o=r.backgroundCommandManager;if(!o||typeof o.reconcileAllAgents!="function"){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u7BA1\u7406\u5668\u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u542F\u52A8\u5B64\u513F\u5BF9\u8D26");return}const n=await o.reconcileAllAgents();n>0&&e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u5B64\u513F\u5BF9\u8D26\u5B8C\u6210\uFF1A\u4FEE\u590D ${n} \u6761\u540E\u53F0\u547D\u4EE4\u72B6\u6001`)}).catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u5BF9\u8D26\u5931\u8D25:",r.message)});async function tr(){try{const r=await import("node:fs/promises"),o=await import("node:path"),n=await import("node:os"),f=o.join(n.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await $();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!Ze(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await I().rebuildRegistry(a.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await Je(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:s}=await import("node:child_process");s(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(s){const c=s;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await k([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const o=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",o.message)}}tr().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),He().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const i=P();i.use(Te),i.use(x()),i.use(P.json({limit:"1mb"})),i.use("/runtime",Pe),i.use("/runtime",be),i.use("/runtime",Be),i.use("/runtime",se),i.use("/runtime",$e),i.use("/runtime",pe),i.use("/runtime",Q),i.use("/runtime",ve),i.use("/runtime",Ie),i.use("/runtime",le),i.use("/runtime",ue),i.use("/runtime",me),i.use("/runtime",Ae),i.use("/runtime",ee),i.use("/runtime",ne),i.use("/runtime",oe),i.use("/runtime",z),i.use("/runtime",Y),i.use("/runtime",ke),i.use("/runtime",q),i.use("/runtime",Z),i.use("/runtime",fe),i.use("/runtime",ge),i.use("/runtime",J),i.use("/runtime",Ce),i.use("/runtime",ie),i.use("/runtime",te),i.use("/runtime/computer",V),i.use("/pty",Re),i.use("/",X),i.use("/api/file-browser",re),i.use("/api/ltr",de);function ir(){const r=y(),o=i.listen(r,()=>{e.info(`[runtime-bridge] listening on ${r}`)});return he(o),U(o),G(o),ce(o),o.on("error",n=>{if(n.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${r} became unavailable after reservation (another process grabbed it concurrently). Set AWS_RUNTIME_BRIDGE_PORT to a free port and restart.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",n),process.exitCode=1}),o}let C=!1;async function b(r,o,n=!1){if(C){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}C=!0,n||R()&&(n=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${n})`);const f=Date.now(),u=Ye(n);try{const a=w.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,s]of w.entries())try{const c=s.providerId||"claude-code",g=F.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),w.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),w.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=Se.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),we("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`);try{const{running:t,stopped:s}=await Ke().stopAll();t>0&&e.info(`[runtime-bridge] \u542F\u52A8\u9879\u8FDB\u7A0B\u5DF2\u505C\u6B62: running=${t}, stopped=${s}`)}catch(t){e.warn("[runtime-bridge] \u505C\u6B62\u542F\u52A8\u9879\u8FDB\u7A0B\u5931\u8D25:",t.message)}e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+S.join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(Ue(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:s}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const B=await s(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${B.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const s=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",s.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await xe()}catch(t){const s=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",s.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(Ge(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),Ve()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await k([])}catch(t){const s=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",s.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await Le(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}try{await je(),e.info("[runtime-bridge] desktop \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] desktop \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}try{await ze.stopAll(),e.info("[runtime-bridge] \u63D2\u4EF6\u76D1\u542C\u8FDB\u7A0B\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] \u63D2\u4EF6\u76D1\u542C\u8FDB\u7A0B\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),o.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Qe.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const h=ir(),v=S.join(M.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(T(v)){const r=D(v,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}ae(async(r,o)=>{await b(r,h,o)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",h,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",h,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",h,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",h)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
3
3
 
@@ -1,8 +1,8 @@
1
- import{promises as h}from"node:fs";import y from"node:path";import{pathToFileURL as b}from"node:url";import{Router as j}from"express";import{getAcodeDataHome as B}from"../config.js";import{validateToken as p}from"../middleware/auth.js";import{getAcodeBgManager as I}from"../services/acode-bg-manager.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{createLogger as E}from"../utils/logger.js";import{listPtySessions as _}from"./pty.js";let m=null;function S(){return m||(m=(async()=>{try{const a=T(),t=y.join(y.dirname(a),"background-command-store.js"),r=await import(b(t).href);return r.BackgroundCommandStore?new r.BackgroundCommandStore:(g.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(a){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",a?.message),null}})().then(a=>(a||(m=null),a)),m)}const g=E("acode-bg-tasks"),k=j();function L(a){return String(a).replace(/[^a-zA-Z0-9_-]/g,"_")}function v(a){return y.join(B(),"bg-tasks",L(a),"state.json")}k.get("/acode-bg-tasks/:agentId",p,async(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=v(String(i));try{const s=await h.readFile(o,"utf-8"),d=JSON.parse(s),u=(Array.isArray(d.tasks)?d.tasks:[]).map(c=>{const e=c;return{taskId:e.taskId,status:e.status,command:e.commandPreview??e.command,commandPreview:e.commandPreview,description:typeof e.description=="string"?e.description:void 0,cwd:e.cwd,startedAt:e.startedAt,finishedAt:e.finishedAt,exitCode:e.exitCode,signal:e.signal,outputLineCount:e.outputLineCount,outputBytes:e.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof d.updatedAt=="number"?d.updatedAt:null,tasks:u})}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(s instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",o),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const n=s;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",n.message),t.status(500).json({ok:!1,error:n.message})}}),k.get("/acode-agent-pty-sessions/:agentId",p,(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=String(i),d=_().filter(n=>n.ownerUserId===o);t.json({ok:!0,agentId:o,sessions:d})});const P=15e3;function x(a){return{taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:a.lines,nextSeq:a.nextSeq,totalLines:a.totalLines,totalBytes:a.totalBytes,truncated:a.truncated}}async function A(a,t){let i=null;try{i=await h.open(a,"r")}catch(o){if(o?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw o}try{const s=(await i.stat()).size;if(s===0)return{text:[],fileExists:!0,truncated:!1};const d=Math.min(s,2097152),n=s-d,u=Buffer.alloc(d);await i.read(u,0,d,n);const e=u.toString("utf-8").split(`
2
- `);return n>0&&e.shift(),{text:e.slice(-t),fileExists:!0,truncated:d<s}}finally{await i.close()}}k.get("/acode-bg-tasks/:agentId/:taskId/output",p,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId),o=Math.min(2e3,Math.max(100,Number(a.query.tailLines)||500));try{const s=await I();if(s){const d=s.getTaskOutputSnapshot(r,i);if(d){t.json({ok:!0,source:"memory",...x(d)});return}}}catch(s){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",s?.message)}try{const s=await S();if(!s){t.status(503).json({ok:!1,error:"store unavailable"});return}const d=await s.readSession(r);if(!d){t.status(404).json({ok:!1,error:"task_not_found"});return}const n=d.tasks.find(f=>f.taskId===i);if(!n){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:u,fileExists:c,truncated:e}=await A(n.outputPath,o);t.json({ok:!0,source:"file",taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:u,nextSeq:0,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:e||!c})}catch(s){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",s?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",r?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),k.get("/acode-bg-tasks/:agentId/:taskId/stream",p,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const o=(u,c)=>{t.writableEnded||t.destroyed||(c!==void 0&&t.write(`id: ${c}
1
+ import{promises as S}from"node:fs";import y from"node:path";import{pathToFileURL as C}from"node:url";import{Router as j}from"express";import{getAcodeDataHome as T}from"../config.js";import{validateToken as k}from"../middleware/auth.js";import{getAcodeBgManager as h}from"../services/acode-bg-manager.js";import{getReleasedAcodeEntryPath as B}from"../services/acode-package.js";import{createLogger as E}from"../utils/logger.js";import{listPtySessions as _}from"./pty.js";let m=null;function I(){return m||(m=(async()=>{try{const e=B(),t=y.join(y.dirname(e),"background-command-store.js"),d=await import(C(t).href);return d.BackgroundCommandStore?new d.BackgroundCommandStore:(l.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(e){return l.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",e?.message),null}})().then(e=>(e||(m=null),e)),m)}const l=E("acode-bg-tasks"),p=j();function L(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}function v(e){return y.join(T(),"bg-tasks",L(e),"state.json")}p.get("/acode-bg-tasks/:agentId",k,async(e,t)=>{const d=e.params.agentId,o=Array.isArray(d)?d[0]:d;if(!o||!String(o).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}try{const s=await h();if(s&&typeof s.listTasksWithReconcile=="function"){const r=e.query.status&&String(e.query.status)!=="all"?String(e.query.status):void 0,n=await s.listTasksWithReconcile(String(o),r);t.json({ok:!0,agentId:String(o),updatedAt:Date.now(),tasks:n});return}}catch(s){l.warn("[acode-bg-tasks] \u5185\u5B58\u5BF9\u8D26\u5217\u8868\u5931\u8D25\uFF0C\u964D\u7EA7\u78C1\u76D8\u8BFB\u53D6: %s",s?.message)}const i=v(String(o));try{const s=await S.readFile(i,"utf-8"),r=JSON.parse(s),u=(Array.isArray(r.tasks)?r.tasks:[]).map(c=>{const a=c;return{taskId:a.taskId,status:a.status,command:a.commandPreview??a.command,commandPreview:a.commandPreview,description:typeof a.description=="string"?a.description:void 0,cwd:a.cwd,startedAt:a.startedAt,finishedAt:a.finishedAt,exitCode:a.exitCode,signal:a.signal,outputLineCount:a.outputLineCount,outputBytes:a.outputBytes}});t.json({ok:!0,agentId:String(o),updatedAt:typeof r.updatedAt=="number"?r.updatedAt:null,tasks:u})}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(o),updatedAt:null,tasks:[]});return}if(s instanceof SyntaxError){l.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",i),t.json({ok:!0,agentId:String(o),updatedAt:null,tasks:[]});return}const n=s;l.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",n.message),t.status(500).json({ok:!1,error:n.message})}}),p.get("/acode-agent-pty-sessions/:agentId",k,(e,t)=>{const d=e.params.agentId,o=Array.isArray(d)?d[0]:d;if(!o||!String(o).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const i=String(o),r=_().filter(n=>n.ownerUserId===i);t.json({ok:!0,agentId:i,sessions:r})});const P=15e3;function w(e){return{taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:e.lines,nextSeq:e.nextSeq,totalLines:e.totalLines,totalBytes:e.totalBytes,truncated:e.truncated}}async function x(e,t){let o=null;try{o=await S.open(e,"r")}catch(i){if(i?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw i}try{const s=(await o.stat()).size;if(s===0)return{text:[],fileExists:!0,truncated:!1};const r=Math.min(s,2097152),n=s-r,u=Buffer.alloc(r);await o.read(u,0,r,n);const a=u.toString("utf-8").split(`
2
+ `);return n>0&&a.shift(),{text:a.slice(-t),fileExists:!0,truncated:r<s}}finally{await o.close()}}p.get("/acode-bg-tasks/:agentId/:taskId/output",k,(e,t)=>{(async()=>{const d=String(e.params.agentId),o=String(e.params.taskId),i=Math.min(2e3,Math.max(100,Number(e.query.tailLines)||500));try{const s=await h();if(s){const r=s.getTaskOutputSnapshot(d,o);if(r){t.json({ok:!0,source:"memory",...w(r)});return}}}catch(s){l.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",s?.message)}try{const s=await I();if(!s){t.status(503).json({ok:!1,error:"store unavailable"});return}const r=await s.readSession(d);if(!r){t.status(404).json({ok:!1,error:"task_not_found"});return}const n=r.tasks.find(f=>f.taskId===o);if(!n){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:u,fileExists:c,truncated:a}=await x(n.outputPath,i);t.json({ok:!0,source:"file",taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:u,nextSeq:0,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:a||!c})}catch(s){l.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",s?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(d=>{l.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",d?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),p.get("/acode-bg-tasks/:agentId/:taskId/stream",k,(e,t)=>{(async()=>{const d=String(e.params.agentId),o=String(e.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const i=(u,c)=>{t.writableEnded||t.destroyed||(c!==void 0&&t.write(`id: ${c}
3
3
  `),t.write(`data: ${JSON.stringify(u)}
4
4
 
5
- `))};let s,d=null,n=null;try{s=await I(),s&&(d=s,n=s.getTaskOutputSnapshot(r,i))}catch(u){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",u?.message)}if(n){if(!d){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}if(o({type:"snapshot",source:"memory",...x(n)}),n.status!=="running"){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),t.end();return}let u=null,c=!1;const e=()=>{c||(c=!0,u?.(),clearInterval(f))},f=setInterval(()=>{if(t.writableEnded||t.destroyed){e();return}try{t.write(`: ping
5
+ `))};let s,r=null,n=null;try{s=await h(),s&&(r=s,n=s.getTaskOutputSnapshot(d,o))}catch(u){l.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",u?.message)}if(n){if(!r){i({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}if(i({type:"snapshot",source:"memory",...w(n)}),n.status!=="running"){i({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),t.end();return}let u=null,c=!1;const a=()=>{c||(c=!0,u?.(),clearInterval(f))},f=setInterval(()=>{if(t.writableEnded||t.destroyed){a();return}try{t.write(`: ping
6
6
 
7
- `)}catch{e()}},P);if(f.unref?.(),u=d.subscribeTaskOutput(r,i,{onLine:l=>o({type:"output",stream:l.stream,text:l.text,seq:l.seq},l.seq),onDone:l=>{o({type:"done",status:l.status,exitCode:l.exitCode,signal:l.signal}),e(),t.end()}}),!u){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),e(),t.end();return}a.on("close",()=>{e(),t.writableEnded||t.end()}),a.on("aborted",()=>e());return}try{const u=await S();if(!u){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const c=await u.readSession(r);if(!c){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const e=c.tasks.find(C=>C.taskId===i);if(!e){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:f,fileExists:l,truncated:w}=await A(e.outputPath,500);o({type:"snapshot",source:"file",taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:f,nextSeq:0,totalLines:e.outputLineCount,totalBytes:e.outputBytes,truncated:w||!l}),o({type:"done",status:e.status,exitCode:e.exitCode,signal:e.signal}),t.end()}catch(u){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",u?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",r?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{k as backgroundTasksRouter};
7
+ `)}catch{a()}},P);if(f.unref?.(),u=r.subscribeTaskOutput(d,o,{onLine:g=>i({type:"output",stream:g.stream,text:g.text,seq:g.seq},g.seq),onDone:g=>{i({type:"done",status:g.status,exitCode:g.exitCode,signal:g.signal}),a(),t.end()}}),!u){i({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),a(),t.end();return}e.on("close",()=>{a(),t.writableEnded||t.end()}),e.on("aborted",()=>a());return}try{const u=await I();if(!u){i({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const c=await u.readSession(d);if(!c){i({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const a=c.tasks.find(b=>b.taskId===o);if(!a){i({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:f,fileExists:g,truncated:A}=await x(a.outputPath,500);i({type:"snapshot",source:"file",taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:f,nextSeq:0,totalLines:a.outputLineCount,totalBytes:a.outputBytes,truncated:A||!g}),i({type:"done",status:a.status,exitCode:a.exitCode,signal:a.signal}),t.end()}catch(u){l.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",u?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(d=>{l.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",d?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{p as backgroundTasksRouter};
8
8
 
@@ -0,0 +1,6 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import type { Socket } from "node:net";
3
+
4
+ export declare function attachLspWebSocketServer(server: {
5
+ on: (event: "upgrade", listener: (request: IncomingMessage, socket: Socket, head: Buffer) => void) => unknown;
6
+ }): void;
@@ -0,0 +1,4 @@
1
+ import h from"node:path";import w,{WebSocketServer as m}from"ws";import{isUpgradeAllowed as P}from"../services/access-control.js";import{LspProcessBridge as S}from"../services/lsp-stdio-bridge.js";import{createLogger as $}from"../utils/logger.js";const o=$("lsp-ws"),k=6e4,b={typescript:"typescript",ts:"typescript",javascript:"javascript",js:"javascript",tsx:"typescript",jsx:"javascript"};function v(e){return b[e.toLowerCase()]??null}const l=new Map;function L(e){return h.resolve(e)}function E(e){const s=e.toString("utf8");if(!s.trim())return null;try{return JSON.parse(s)}catch{return null}}function u(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function T(e){u(e),e.idleTimer=setTimeout(()=>{if(!(e.ws!==null&&e.ws.readyState===w.OPEN)){for(const[t,a]of l)if(a===e){l.delete(t);break}e.bridge.dispose()}},k)}function d(e,s){const t=new S({workspacePath:s.workspacePath,language:s.language},{onMessage:a=>{const r=l.get(e)?.ws;if(r&&r.readyState===w.OPEN)try{r.send(JSON.stringify(a))}catch(n){o.warn(`[lsp-ws] \u8F6C\u53D1 server \u6D88\u606F\u5931\u8D25: ${n.message}`)}},onExit:()=>{l.get(e)?.bridge===t&&l.delete(e)},onError:a=>{o.warn(`[lsp-ws] \u8BED\u8A00\u670D\u52A1\u9519\u8BEF ${s.workspacePath}: ${a.message}`)}});return t.start(),t}function y(e,s){return{bridge:d(e,s),ws:null,idleTimer:null,initialized:!1}}function z(e,s){const t=L(s.workspacePath);let a=l.get(t);a||(a=y(t,s),l.set(t,a)),u(a);const r=a;r.ws&&r.ws!==e&&r.ws.readyState===w.OPEN&&(o.info(`[lsp-ws] \u6324\u5360\u65E7\u8FDE\u63A5 workspace=${s.workspacePath}\uFF08\u65B0\u8FDE\u63A5\u63A5\u7BA1\u540C\u4E00\u8FDB\u7A0B\uFF09`),r.ws.close(1e3,"superseded by new lsp connection")),r.ws=e,e.on("message",n=>{const i=E(n);if(i===null){o.warn(`[lsp-ws] \u5FFD\u7565\u65E0\u6CD5\u89E3\u6790\u7684\u6D88\u606F: ${n.toString("utf8").slice(0,200)}`);return}i.method==="initialize"&&(r.initialized&&(o.info(`[lsp-ws] \u590D\u7528\u8FDB\u7A0B\u6536\u5230\u4E8C\u6B21 initialize\uFF0C\u91CD\u5EFA\u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B workspace=${s.workspacePath}`),r.bridge.dispose(),r.bridge=d(t,s)),r.initialized=!0),r.bridge.send(i)}),e.on("close",()=>{r.ws===e&&(r.ws=null),T(r)}),e.on("error",n=>{o.warn(`[lsp-ws] \u8FDE\u63A5\u5F02\u5E38 workspace=${s.workspacePath}: ${n.message}`)}),o.info(`[lsp-ws] \u8FDE\u63A5\u5EFA\u7ACB workspace=${s.workspacePath} file=${s.filePath} language=${s.language} reused=${a.bridge.running}`)}function U(e){const s=new m({noServer:!0});e.on("upgrade",(t,a,r)=>{if(!P(t,a))return;const n=t.headers.host||"localhost",i=new URL(t.url||"/",`http://${n}`);if(i.pathname!=="/ws/lsp")return;const c=i.searchParams.get("workspacePath")||"",p=i.searchParams.get("file")||"",g=v(i.searchParams.get("language")||"");if(!c||!p||!g){o.warn(`[lsp-ws] upgrade \u53C2\u6570\u7F3A\u5931: workspacePath=${!!c}, file=${!!p}, language=${g}`),a.write(`HTTP/1.1 400 Bad Request\r
2
+ \r
3
+ `),a.destroy();return}s.handleUpgrade(t,a,r,f=>{z(f,{workspacePath:c,filePath:p,language:g})})})}export{U as attachLspWebSocketServer};
4
+
@@ -0,0 +1,3 @@
1
+ export declare const ltrRouter: import("express-serve-static-core").Router;
2
+
3
+ export declare const LTR_ROOT: string;
@@ -0,0 +1,2 @@
1
+ import m from"node:os";import n from"node:path";import{promises as i}from"node:fs";import{randomUUID as p}from"node:crypto";import{Router as l}from"express";import{validateToken as u}from"../middleware/auth.js";import{createLogger as d}from"../utils/logger.js";const s=d("ltr"),w=l(),c=n.join(m.tmpdir(),"aw-temp","LTR"),y=7,f=512*1024;function g(o=new Date){const e=o.getFullYear(),t=String(o.getMonth()+1).padStart(2,"0"),r=String(o.getDate()).padStart(2,"0");return n.join(c,`${e}${t}${r}`)}async function h(){const o=Date.now()-y*24*60*60*1e3;try{const e=await i.readdir(c,{withFileTypes:!0});for(const t of e){if(!t.isDirectory())continue;const r=n.join(c,t.name);try{(await i.stat(r)).mtimeMs<o&&(await i.rm(r,{recursive:!0,force:!0}),s.info(`[ltr] removed expired temp dir: ${r}`))}catch{}}}catch(e){const t=e;t.code!=="ENOENT"&&s.warn("[ltr] cleanup expired dirs failed:",t)}}async function T(o){const e=String(o??"");if(!e.trim())throw new Error("ltr content is empty");const t=Buffer.byteLength(e,"utf8");if(t>f)throw new Error(`ltr content too large: ${t} bytes, max ${f} bytes`);const r=g();await i.mkdir(r,{recursive:!0});const a=n.join(r,`${p()}.txt`);return await i.writeFile(a,e,"utf8"),h(),a}w.post("/upload",u,async(o,e)=>{try{const{content:t}=o.body||{},r=await T(String(t??""));e.json({path:r})}catch(t){const r=t;s.error("[ltr] failed to write temp text file:",r),e.status(400).json({error:r.message})}});export{c as LTR_ROOT,w as ltrRouter};
2
+
@@ -28,9 +28,26 @@ export interface BgTaskOutputSnapshot {
28
28
  truncated: boolean;
29
29
  }
30
30
 
31
+ export interface BgTaskSummary {
32
+ taskId: string;
33
+ status: string;
34
+ command: string;
35
+ commandPreview: string;
36
+ description?: string;
37
+ cwd: string;
38
+ startedAt: number;
39
+ finishedAt: number | null;
40
+ exitCode: number | null;
41
+ signal: string | null;
42
+ outputLineCount: number;
43
+ outputBytes: number;
44
+ }
45
+
31
46
  export interface BgManagerLike {
32
47
  getTaskOutputSnapshot(agentId: string, taskId: string, tailLines?: number): BgTaskOutputSnapshot | null;
33
48
  subscribeTaskOutput(agentId: string, taskId: string, subscriber: BgTaskOutputSubscriber): (() => void) | null;
49
+
50
+ listTasksWithReconcile?(agentId: string, statusFilter?: string): Promise<BgTaskSummary[]>;
34
51
  }
35
52
 
36
53
  export declare function getAcodeBgManager(): Promise<BgManagerLike | null>;
@@ -0,0 +1,48 @@
1
+
2
+ export declare function encodeLspStdioFrame(jsonText: string): Buffer;
3
+
4
+ export declare class LspStdioFrameReader {
5
+ private buffer;
6
+
7
+ private headerDone;
8
+ private contentLength;
9
+
10
+ private static readonly MAX_HEADER_BYTES;
11
+
12
+ push(chunk: Buffer): string[];
13
+ reset(): void;
14
+ }
15
+
16
+ export declare function resolveTypeScriptLanguageServerEntry(): string;
17
+
18
+ export declare function resolveTsserverPath(): string;
19
+ export interface LspProcessBridgeCallbacks {
20
+
21
+ onMessage: (message: unknown) => void;
22
+
23
+ onStderr?: (chunk: string) => void;
24
+
25
+ onExit?: (code: number | null, signal: NodeJS.Signals | null) => void;
26
+
27
+ onError?: (error: Error) => void;
28
+ }
29
+
30
+ export declare class LspProcessBridge {
31
+ readonly callbacks: LspProcessBridgeCallbacks;
32
+ readonly workspacePath: string;
33
+ readonly language: string;
34
+ private child;
35
+ private readonly reader;
36
+ private disposed;
37
+ constructor(options: {
38
+ workspacePath: string;
39
+ language: string;
40
+ }, callbacks: LspProcessBridgeCallbacks);
41
+ get running(): boolean;
42
+
43
+ start(): void;
44
+
45
+ send(message: unknown): void;
46
+
47
+ dispose(): void;
48
+ }
@@ -0,0 +1,6 @@
1
+ import{spawn as f}from"node:child_process";import d from"node:fs";import{createRequire as p}from"node:module";import c from"node:path";import{createLogger as u}from"../utils/logger.js";const i=u("lsp-stdio-bridge"),l=p(import.meta.url);function g(n){const e=Buffer.from(n,"utf8"),t=`Content-Length: ${e.length}\r
2
+ \r
3
+ `;return Buffer.concat([Buffer.from(t,"ascii"),e])}class a{constructor(){this.buffer=Buffer.alloc(0),this.headerDone=!1,this.contentLength=0}push(e){this.buffer=this.buffer.length===0?e:Buffer.concat([this.buffer,e]);const t=[];for(;;){if(!this.headerDone){const s=this.buffer.indexOf(`\r
4
+ \r
5
+ `);if(s===-1){if(this.buffer.length>a.MAX_HEADER_BYTES)throw new Error(`lsp stdio: header too large (${this.buffer.length} bytes), stream is malformed`);break}const o=this.buffer.subarray(0,s).toString("ascii"),h=/^Content-Length:\s*(\d+)/i.exec(o);if(!h)throw new Error(`lsp stdio: invalid header: ${o.slice(0,120)}`);if(this.contentLength=parseInt(h[1],10),this.contentLength<0||this.contentLength>512*1024*1024)throw new Error(`lsp stdio: implausible content length ${this.contentLength}`);this.buffer=this.buffer.subarray(s+4),this.headerDone=!0}if(this.buffer.length<this.contentLength)break;const r=this.buffer.subarray(0,this.contentLength).toString("utf8");this.buffer=this.buffer.subarray(this.contentLength),this.headerDone=!1,this.contentLength=0,t.push(r)}return t}reset(){this.buffer=Buffer.alloc(0),this.headerDone=!1,this.contentLength=0}}a.MAX_HEADER_BYTES=64*1024;function b(){const n=l.resolve("typescript-language-server/package.json"),e=JSON.parse(d.readFileSync(n,"utf8"));if(!e.bin)throw new Error("typescript-language-server package.json has no bin entry");const t=typeof e.bin=="string"?e.bin:Object.values(e.bin)[0];return c.join(c.dirname(n),t)}function m(){return l.resolve("typescript/lib/tsserver.js")}class L{constructor(e,t){this.callbacks=t,this.child=null,this.reader=new a,this.disposed=!1,this.workspacePath=e.workspacePath,this.language=e.language}get running(){return this.child!==null&&this.child.exitCode===null&&this.child.signalCode===null}start(){if(this.disposed)return;let e;try{e=b()}catch(t){i.warn(`[lsp-stdio] \u89E3\u6790 typescript-language-server \u5165\u53E3\u5931\u8D25: ${t.message}`),this.callbacks.onError?.(t);return}try{const t=f(process.execPath,[e,"--stdio"],{cwd:this.workspacePath,env:{...process.env,TSSERVER_PATH:m()},stdio:["pipe","pipe","pipe"]});this.child=t,t.on("error",r=>{i.warn(`[lsp-stdio] \u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B error: ${r.message}`),this.callbacks.onError?.(r)}),t.on("exit",(r,s)=>{this.child===t&&(this.child=null),i.info(`[lsp-stdio] \u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B\u9000\u51FA: code=${r}, signal=${String(s)}`),this.callbacks.onExit?.(r,s)}),t.stdout?.on("data",r=>{if(!this.disposed)try{for(const s of this.reader.push(r))try{this.callbacks.onMessage(JSON.parse(s))}catch(o){i.warn(`[lsp-stdio] \u65E0\u6CD5\u89E3\u6790 server \u6D88\u606F: ${s.slice(0,200)} err=${o.message}`)}}catch(s){i.warn(`[lsp-stdio] \u89E3\u6790 server \u8F93\u51FA\u5931\u8D25: ${s.message}`),this.callbacks.onError?.(s)}}),t.stderr?.on("data",r=>{this.disposed||this.callbacks.onStderr?.(r.toString("utf8"))}),i.info(`[lsp-stdio] \u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B\u5DF2\u542F\u52A8 workspace=${this.workspacePath} pid=${t.pid}`)}catch(t){i.warn(`[lsp-stdio] \u542F\u52A8\u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B\u5931\u8D25: ${t.message}`),this.callbacks.onError?.(t)}}send(e){const t=this.child;if(!t||!t.stdin?.writable){i.warn("[lsp-stdio] \u8FDB\u7A0B\u672A\u5C31\u7EEA\uFF0C\u4E22\u5F03\u6D88\u606F");return}t.stdin.write(g(JSON.stringify(e)))}dispose(){if(this.disposed)return;this.disposed=!0;const e=this.child;this.child=null,this.reader.reset(),e&&e.exitCode===null&&e.signalCode===null&&(i.info(`[lsp-stdio] \u7EC8\u6B62\u8BED\u8A00\u670D\u52A1\u8FDB\u7A0B pid=${e.pid}`),e.kill())}}export{L as LspProcessBridge,a as LspStdioFrameReader,g as encodeLspStdioFrame,m as resolveTsserverPath,b as resolveTypeScriptLanguageServerEntry};
6
+
@@ -176,6 +176,8 @@ export declare class BackgroundCommandManager {
176
176
  private spawnAndAttach;
177
177
 
178
178
  disableInactivityTimeout(agentId: string, taskId: string): boolean;
179
+
180
+ markBackgrounded(agentId: string, taskId: string): Promise<boolean>;
179
181
  private onOutput;
180
182
 
181
183
  private scheduleInactivity;
@@ -209,6 +211,12 @@ export declare class BackgroundCommandManager {
209
211
 
210
212
  disposeAll(): Promise<void>;
211
213
 
214
+ reconcileDiskRunning(agentId: string): Promise<number>;
215
+
216
+ listTasksWithReconcile(agentId: string, statusFilter?: string): Promise<TaskSummary[]>;
217
+
218
+ reconcileAllAgents(): Promise<number>;
219
+
212
220
  reconcileOrphans(agentId: string): Promise<void>;
213
221
 
214
222
  readSessionState(agentId: string): Promise<import("./background-command-store.js").BackgroundSessionState | null>;
@@ -218,6 +226,8 @@ export declare class BackgroundCommandManager {
218
226
  private getTask;
219
227
  private generateTaskId;
220
228
  private persistTask;
229
+
230
+ private persistTaskSafe;
221
231
  }
222
232
 
223
233
  export declare class BackgroundTaskError extends Error {
@@ -1,7 +1,7 @@
1
- import{spawn as E}from"node:child_process";import{promises as A}from"node:fs";import{createInterface as I}from"node:readline";import{randomBytes as S}from"node:crypto";import{prepareShellCommand as O,shellExecutable as R}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as M,decodeShellOutputChunk as w}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as D}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as F}from"./background-command-store.js";const z=1e3,G=30*60*1e3,N=24*60*60*1e3,B=50*1024*1024,C=16,_=256,W=256*1024,X=100,$=5e3,q=256,b=20,v=5*60*1e3,L=1024*1024;class K{constructor(){this.tasksByAgent=new Map,this.store=new F}globalCount(){let t=0;for(const i of this.tasksByAgent.values())t+=i.size;return t}startCommand(t){const i=t.agentId,e=this.getOrCreateBucket(i);if(e.size>=C)throw new x("too_many_tasks",`Agent ${i} has ${e.size} background tasks (limit ${C}). Kill or wait for completion first.`);if(this.globalCount()>=_)throw new x("global_limit_reached",`Global background task limit ${_} reached.`);const n=this.generateTaskId(e),o=t.timeoutMs===void 0?G:t.timeoutMs>0?Math.min(t.timeoutMs,N):0,s=typeof t.inactivityTimeoutMs=="number"&&Number.isFinite(t.inactivityTimeoutMs)&&t.inactivityTimeoutMs>0?t.inactivityTimeoutMs:0,r=this.store.outputPathFor(i,n),u=t.commandText.length>80?`${t.commandText.slice(0,77)}...`:t.commandText,a={taskId:n,agentId:i,command:t.commandText,commandPreview:u,description:t.description,cwd:t.cwd,status:"running",startedAt:Date.now(),finishedAt:null,exitCode:null,signal:null,killedReason:null,timeoutMs:o,inactivityTimeoutMs:s,child:null,ringBuffer:new D,outputPath:r,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,outputSeq:0,outputSubscribers:new Set,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,inactivityTimer:null,inactivityTimedOut:!1,finalized:!1,stdoutDecoder:M(),stderrDecoder:M(),stdoutText:"",stderrText:"",commandLineCount:0},c=new Promise(d=>{a.completionResolve=d});return e.set(n,a),this.spawnAndAttach(a,t).catch(d=>{a.ringBuffer.push(`[ACode] spawn failed: ${d?.message??String(d)}
2
- `),this.finalizeTask(a,-1,null,"spawn_error")}),this.persistTask(a),{taskId:n,completion:c}}async spawnAndAttach(t,i){const e=R(),n=await O(i.commandText,i.workspaceRoot,e),o=process.platform==="win32",s=E(e.command,[...e.argsPrefix,n.commandText],{cwd:i.cwd,env:process.env,detached:!o,stdio:["ignore","pipe","pipe"],windowsVerbatimArguments:o,windowsHide:!0});t.child=s,t.commandFilePath=n.commandFilePath,t.commandLineCount=n.commandLineCount,t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),s.stdout?.on("data",r=>{const u=w(t.stdoutDecoder,r);this.onOutput(t,"stdout",u)}),s.stderr?.on("data",r=>{const u=w(t.stderrDecoder,r);this.onOutput(t,"stderr",u)}),s.on("error",r=>{this.onOutput(t,"stderr",`[ACode] child error: ${r.message}
3
- `),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),s.on("close",(r,u)=>{const a=w(t.stdoutDecoder,void 0,!0),c=w(t.stderrDecoder,void 0,!0);if(a&&this.onOutput(t,"stdout",a),c&&this.onOutput(t,"stderr",c),t.ringBuffer.flush(),!t.finalized){const d=t.killedReason??null;this.finalizeTask(t,r,u,d??void 0)}}),t.timeoutMs>0&&(t.timeoutTimer=setTimeout(()=>{t.finalized||(t.killedReason="timeout",this.killProcessGroup(s))},t.timeoutMs),t.timeoutTimer.unref?.())}disableInactivityTimeout(t,i){const e=this.getTask(t,i);return e?(e.inactivityTimeoutMs=0,e.inactivityTimer&&(clearTimeout(e.inactivityTimer),e.inactivityTimer=null),!0):!1}onOutput(t,i,e){if(!e)return;t.outputSeq++;const n=t.outputSeq;if(t.outputSubscribers.size>0)for(const r of t.outputSubscribers)try{r.onLine({stream:i,text:e,seq:n})}catch{}const o=i==="stdout"?"stdoutText":"stderrText";if(Buffer.byteLength(t[o],"utf-8")<L){const r=L-Buffer.byteLength(t[o],"utf-8");t[o]+=r>0?e.slice(0,r):""}if(t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),t.outputBytes>=B){if(!t.outputTruncated){t.outputTruncated=!0;const r=`
1
+ import{spawn as E}from"node:child_process";import{promises as S}from"node:fs";import{createInterface as R}from"node:readline";import{randomBytes as M}from"node:crypto";import{prepareShellCommand as O,shellExecutable as D}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as b,decodeShellOutputChunk as A}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as G}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as z}from"./background-command-store.js";const F=1e3,N=30*60*1e3,W=24*60*60*1e3,$=5*1e3,k=64*1024,X=2e3,C=50*1024*1024,_=16,P=256,q=256*1024,K=100,U=5e3,Y=256,v=20,B=5*60*1e3,L=1024*1024,x=`pid-${process.pid}-${Date.now().toString(36)}`;class H{constructor(){this.tasksByAgent=new Map,this.store=new z}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>=_)throw new I("too_many_tasks",`Agent ${i} has ${e.size} background tasks (limit ${_}). Kill or wait for completion first.`);if(this.globalCount()>=P)throw new I("global_limit_reached",`Global background task limit ${P} reached.`);const n=this.generateTaskId(e),r=t.timeoutMs===void 0?N:t.timeoutMs>0?Math.min(t.timeoutMs,W):0,s=typeof t.inactivityTimeoutMs=="number"&&Number.isFinite(t.inactivityTimeoutMs)&&t.inactivityTimeoutMs>0?t.inactivityTimeoutMs:0,o=this.store.outputPathFor(i,n),a=t.commandText.length>80?`${t.commandText.slice(0,77)}...`:t.commandText,u={taskId:n,agentId:i,command:t.commandText,commandPreview:a,description:t.description,cwd:t.cwd,status:"running",startedAt:Date.now(),finishedAt:null,exitCode:null,signal:null,killedReason:null,timeoutMs:r,inactivityTimeoutMs:s,child:null,ringBuffer:new G,outputPath:o,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,outputSeq:0,outputSubscribers:new Set,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,inactivityTimer:null,exitGraceTimer:null,inactivityTimedOut:!1,finalized:!1,stdoutDecoder:b(),stderrDecoder:b(),stdoutText:"",stderrText:"",commandLineCount:0,bootId:x,lastPulseAt:Date.now(),lastPulseBytes:0,backgrounded:!1},l=new Promise(c=>{u.completionResolve=c});return e.set(n,u),r>0&&(u.timeoutTimer=setTimeout(()=>{u.finalized||(u.killedReason="timeout",u.child?this.killProcessGroup(u.child):this.finalizeTask(u,null,null,"timeout"))},r),u.timeoutTimer.unref?.()),this.spawnAndAttach(u,t).catch(c=>{u.ringBuffer.push(`[ACode] spawn failed: ${c?.message??String(c)}
2
+ `),this.finalizeTask(u,-1,null,"spawn_error")}),this.persistTaskSafe(u),{taskId:n,completion:l}}async spawnAndAttach(t,i){const e=D(),n=await O(i.commandText,i.workspaceRoot,e),r=process.platform==="win32",s=E(e.command,[...e.argsPrefix,n.commandText],{cwd:i.cwd,env:process.env,detached:!r,stdio:["ignore","pipe","pipe"],windowsVerbatimArguments:r,windowsHide:!0});t.child=s,t.commandFilePath=n.commandFilePath,t.commandLineCount=n.commandLineCount,t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),s.stdout?.on("data",o=>{const a=A(t.stdoutDecoder,o);this.onOutput(t,"stdout",a)}),s.stderr?.on("data",o=>{const a=A(t.stderrDecoder,o);this.onOutput(t,"stderr",a)}),s.on("error",o=>{this.onOutput(t,"stderr",`[ACode] child error: ${o.message}
3
+ `),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),s.on("exit",(o,a)=>{if(t.finalized)return;const u=()=>{if(t.finalized||!t.child)return;const l=A(t.stdoutDecoder,void 0,!0),c=A(t.stderrDecoder,void 0,!0);l&&this.onOutput(t,"stdout",l),c&&this.onOutput(t,"stderr",c),t.ringBuffer.flush();const p=t.killedReason??null;this.finalizeTask(t,o,a,p??void 0)};t.exitGraceTimer=setTimeout(u,X),t.exitGraceTimer.unref?.()}),s.on("close",(o,a)=>{t.exitGraceTimer&&(clearTimeout(t.exitGraceTimer),t.exitGraceTimer=null);const u=A(t.stdoutDecoder,void 0,!0),l=A(t.stderrDecoder,void 0,!0);if(u&&this.onOutput(t,"stdout",u),l&&this.onOutput(t,"stderr",l),t.ringBuffer.flush(),!t.finalized){const c=t.killedReason??null;this.finalizeTask(t,o,a,c??void 0)}})}disableInactivityTimeout(t,i){const e=this.getTask(t,i);return e?(e.inactivityTimeoutMs=0,e.inactivityTimer&&(clearTimeout(e.inactivityTimer),e.inactivityTimer=null),!0):!1}async markBackgrounded(t,i){const e=this.getTask(t,i);return e?(e.backgrounded=!0,await this.persistTask(e),!0):!1}onOutput(t,i,e){if(!e)return;t.outputSeq++;const n=t.outputSeq;if(t.outputSubscribers.size>0)for(const o of t.outputSubscribers)try{o.onLine({stream:i,text:e,seq:n})}catch{}const r=i==="stdout"?"stdoutText":"stderrText";if(Buffer.byteLength(t[r],"utf-8")<L){const o=L-Buffer.byteLength(t[r],"utf-8");t[r]+=o>0?e.slice(0,o):""}if(t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),t.status==="running"){const o=Date.now(),a=t.outputBytes-t.lastPulseBytes;(o-t.lastPulseAt>=$||a>=k)&&(t.lastPulseAt=o,t.lastPulseBytes=t.outputBytes,this.persistTaskSafe(t))}if(t.outputBytes>=C){if(!t.outputTruncated){t.outputTruncated=!0;const o=`
4
4
  [ACode] output truncated at 50MB
5
- `;t.ringBuffer.push(r),this.appendToFile(t,r)}t.ringBuffer.push(e);return}t.ringBuffer.push(e),t.onOutput?.({stream:i,text:e});const s=(e.match(/\n/g)??[]).length;t.outputLineCount+=s,t.outputBytes+=Buffer.byteLength(e,"utf-8"),this.appendToFile(t,e)}scheduleInactivity(t){t.finalized||t.inactivityTimeoutMs<=0||(t.inactivityTimer&&clearTimeout(t.inactivityTimer),t.inactivityTimer=setTimeout(()=>{t.finalized||!t.child||(t.inactivityTimedOut=!0,t.killedReason="inactivity",this.killProcessGroup(t.child))},t.inactivityTimeoutMs),t.inactivityTimer.unref?.())}appendToFile(t,i){return t.outputBytes>B?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>A.appendFile(t.outputPath,i,"utf-8")).catch(e=>{t.ringBuffer.push(`[ACode] output write failed: ${e?.message??e}
6
- `)}),t.outputWriteChain)}finalizeTask(t,i,e,n){if(t.finalized)return;t.finalized=!0,t.timeoutTimer&&(clearTimeout(t.timeoutTimer),t.timeoutTimer=null),t.killEscalationTimer&&(clearTimeout(t.killEscalationTimer),t.killEscalationTimer=null),t.inactivityTimer&&(clearTimeout(t.inactivityTimer),t.inactivityTimer=null),t.child=null,t.finishedAt=Date.now();let o;n?(o="killed",t.killedReason=n):i===0?o="completed":o="failed",t.status=o,t.exitCode=i,t.signal=e;const s=n==="inactivity"||t.inactivityTimedOut,r={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||s,inactivityTimedOut:s,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(r);const u={taskId:t.taskId,status:o,exitCode:i,signal:e,timedOut:n==="timeout"||s,aborted:!1,durationMs:t.finishedAt-t.startedAt},a=t.completionWaiters;t.completionWaiters=[];for(const c of a)c(u);if(this.persistTask(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:o,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const c=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const d of c)try{d.onDone({status:o,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},v),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(A.unlink(n.outputPath).catch(()=>{}),e.delete(i),e.size===0&&this.tasksByAgent.delete(t))}listTasks(t,i){const e=this.tasksByAgent.get(t);if(!e)return[];const n=[];for(const o of e.values())i&&i!=="all"&&o.status!==i||n.push(this.toSummary(o));return n.sort((o,s)=>s.startedAt-o.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const o=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:o,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let o=!0;return()=>{o&&(o=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),o=i.mode==="lines"||i.mode==="grep"?i.mode:"tail",s=Math.min(Math.max(1,i.line_size??100),X),r=Number.isFinite(i.start_line)&&(i.start_line??0)>0?Math.floor(i.start_line??0):0;if(!n)return{taskId:e,status:"killed",mode:o,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,outputTruncated:!1,hasMore:!1,nextStartLine:null,outputFile:""}};let u=[],a=!1,c=null;if(o==="tail"){const m=n.ringBuffer.tail(s),f=Math.max(0,n.outputLineCount-m.length);u=m.map((l,g)=>({line:f+g,text:l})),c=null}else if(o==="lines"){const m=n.ringBuffer.firstLineIndex(),f=m+n.ringBuffer.getLineCount();if(r>=m&&r<f)u=n.ringBuffer.offset(r,s).map((g,h)=>({line:r+h,text:g})),a=r+u.length<f,c=a?r+u.length:null;else if(r<f){const l=await this.readFromFile(n,"lines",r,s,0,0,void 0);u=l.rows,a=l.hasMore,c=l.nextStartLine}}else{const m=String(i.pattern??"");if(!m||m.length>q)return this._emptyResult(e,n,o);let f;try{f=new RegExp(m,"i")}catch{return this._emptyResult(e,n,o)}const l=Math.min(Math.max(0,i.context_before??0),b),g=Math.min(Math.max(0,i.context_after??0),b),h=await this.readFromFile(n,"grep",r,s,l,g,f);u=h.rows,a=h.hasMore,c=h.nextStartLine}const d=[];let p=0,T=!1;for(const m of u){const f=Buffer.byteLength(m.text,"utf-8")+1;if(d.length>=s||p+f>W){T=!0,a=!0;break}d.push(m),p+=f}const y=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:o,lines:d,lineCount:d.length,metadata:y?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:p,truncated:T,outputTruncated:n.outputTruncated,hasMore:a,nextStartLine:a?c:null,outputFile:n.outputPath}:void 0}}_emptyResult(t,i,e){return{taskId:t,status:i.status,mode:e,lines:[],lineCount:0,metadata:{totalLines:i.outputLineCount,totalBytes:i.outputBytes,returnedBytes:0,truncated:!1,outputTruncated:i.outputTruncated,hasMore:!1,nextStartLine:null,outputFile:i.outputPath}}}async readFromFile(t,i,e,n,o,s,r){const u=[],a=new Set;let c=0,d=!1;const p=[];let T=-1/0;try{const y=await A.open(t.outputPath,"r"),m=I({input:y.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const f of m){const l=c;if(c++,i==="grep"&&l-e>=$&&l>=e){d=!0;break}if(l>=e){if(i==="lines"){if(u.length>=n){d=!0;break}a.has(l)||(a.add(l),u.push({line:l,text:f}))}else if(r.test(f)){for(const h of p)h.line>=e&&!a.has(h.line)&&(a.add(h.line),u.push(h));if(a.has(l)||(a.add(l),u.push({line:l,text:f})),T=l,u.length>=n){d=!0;break}}else if(o>0&&(p.push({line:l,text:f}),p.length>o&&p.shift()),T>=e&&l-T<=s&&!a.has(l)&&(a.add(l),u.push({line:l,text:f}),u.length>=n)){d=!0;break}}}m.close(),await y.close()}catch{return{rows:[],hasMore:!1,nextStartLine:null}}return d?{rows:u,hasMore:d,nextStartLine:c}:{rows:u,hasMore:d,nextStartLine:null}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(r=>{let u=null;const a=c=>{u&&clearTimeout(u),r(c)};n.completionWaiters.push(a),e&&e>0&&(u=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(c=>c!==a),r({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),u.unref?.())});const s=(await this.store.readSession(t))?.tasks.find(r=>r.taskId===i);return s?s.status!=="running"?{taskId:i,status:s.status,exitCode:s.exitCode,signal:s.signal,timedOut:s.killedReason==="timeout",aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt,...s.outputPath?{outputFilePath:s.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const o=e.get(n);if(!o)continue;const s=o.completionWaiters;if(s.length===0)continue;o.completionWaiters=[];const r={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-o.startedAt};for(const u of s)u(r)}}async killTask(t,i){const e=this.getTask(t,i);return e?e.status!=="running"?{taskId:i,status:e.status,exitCode:e.exitCode??void 0}:e.child?(e.killedReason="user_kill",this.killProcessGroup(e.child)?(e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.killEscalationTimer=setTimeout(()=>{e.child&&!e.finalized&&this.killProcessGroup(e.child,!0)},z),e.killEscalationTimer.unref?.(),{taskId:i,status:"killed",signal:"SIGTERM"}):{taskId:i,status:e.status,error:"kill_failed",reason:"permission_denied"}):{taskId:i,status:e.status,error:"kill_failed",reason:"no_child"}:{taskId:i,status:"killed",error:"task_not_found"}}killProcessGroup(t,i=!1){const e=t.pid;if(!e)return!1;try{if(process.platform==="win32"){const n="/F",{spawnSync:o}=require("node:child_process");return o("taskkill",["/PID",String(e),"/T",n],{windowsHide:!0}),!0}try{process.kill(-e,i?"SIGKILL":"SIGTERM")}catch{process.kill(e,i?"SIGKILL":"SIGTERM")}return!0}catch{return!1}}async disposeAgent(t){const i=this.tasksByAgent.get(t);if(i){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="session_disposed",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}await this.store.deleteAgent(t)}async disposeAll(){for(const[t,i]of this.tasksByAgent){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="process_exit",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer);this.tasksByAgent.delete(t)}}async reconcileOrphans(t){const i=await this.store.readSession(t);if(!i||i.tasks.length===0)return;let e=!1;for(const n of i.tasks)n.status==="running"&&(n.status="killed",n.killedReason="runtime_crashed",n.finishedAt=Date.now(),n.notified=!0,e=!0);e&&await this.store.persistSession(t,i.tasks)}async readSessionState(t){return this.store.readSession(t)}async markNotified(t,i){await this.store.markNotified(t,i)}getOrCreateBucket(t){let i=this.tasksByAgent.get(t);return i||(i=new Map,this.tasksByAgent.set(t,i)),i}getTask(t,i){const e=this.tasksByAgent.get(t);if(e&&/^[A-Za-z0-9]{8}$/.test(i))return e.get(i)}generateTaskId(t){for(let i=0;i<3;i++){const e=S(4).toString("hex").slice(0,8);if(!t.has(e))return e}return S(6).toString("hex").slice(0,8)}async persistTask(t){const i=await this.store.readSession(t.agentId)??{agentId:t.agentId,updatedAt:0,tasks:[]},e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,description:t.description,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<v),await this.store.persistSession(t.agentId,i.tasks)}}class x extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}const Q=new K;export{K as BackgroundCommandManager,x as BackgroundTaskError,Q 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>C?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>S.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.exitGraceTimer&&(clearTimeout(t.exitGraceTimer),t.exitGraceTimer=null),t.child=null,t.finishedAt=Date.now();let r;n?(r="killed",t.killedReason=n):i===0?r="completed":r="failed",t.status=r,t.exitCode=i,t.signal=e;const s=n==="inactivity"||t.inactivityTimedOut,o={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||s,inactivityTimedOut:s,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(o);const a={taskId:t.taskId,status:r,exitCode:i,signal:e,timedOut:n==="timeout"||s,aborted:!1,durationMs:t.finishedAt-t.startedAt},u=t.completionWaiters;t.completionWaiters=[];for(const l of u)l(a);if(this.persistTaskSafe(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:r,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const l=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const c of l)try{c.onDone({status:r,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},B),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(S.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 r of e.values())i&&i!=="all"&&r.status!==i||n.push(this.toSummary(r));return n.sort((r,s)=>s.startedAt-r.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const r=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:r,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let r=!0;return()=>{r&&(r=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),r=i.mode==="lines"||i.mode==="grep"?i.mode:"tail",s=Math.min(Math.max(1,i.line_size??100),K),o=Number.isFinite(i.start_line)&&(i.start_line??0)>0?Math.floor(i.start_line??0):0;if(!n)return{taskId:e,status:"killed",mode:r,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,outputTruncated:!1,hasMore:!1,nextStartLine:null,outputFile:""}};let a=[],u=!1,l=null;if(r==="tail"){const m=n.ringBuffer.tail(s),f=Math.max(0,n.outputLineCount-m.length);a=m.map((d,y)=>({line:f+y,text:d})),l=null}else if(r==="lines"){const m=n.ringBuffer.firstLineIndex(),f=m+n.ringBuffer.getLineCount();if(o>=m&&o<f)a=n.ringBuffer.offset(o,s).map((y,T)=>({line:o+T,text:y})),u=o+a.length<f,l=u?o+a.length:null;else if(o<f){const d=await this.readFromFile(n,"lines",o,s,0,0,void 0);a=d.rows,u=d.hasMore,l=d.nextStartLine}}else{const m=String(i.pattern??"");if(!m||m.length>Y)return this._emptyResult(e,n,r);let f;try{f=new RegExp(m,"i")}catch{return this._emptyResult(e,n,r)}const d=Math.min(Math.max(0,i.context_before??0),v),y=Math.min(Math.max(0,i.context_after??0),v),T=await this.readFromFile(n,"grep",o,s,d,y,f);a=T.rows,u=T.hasMore,l=T.nextStartLine}const c=[];let p=0,g=!1;for(const m of a){const f=Buffer.byteLength(m.text,"utf-8")+1;if(c.length>=s||p+f>q){g=!0,u=!0;break}c.push(m),p+=f}const w=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:r,lines:c,lineCount:c.length,metadata:w?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:p,truncated:g,outputTruncated:n.outputTruncated,hasMore:u,nextStartLine:u?l:null,outputFile:n.outputPath}:void 0}}_emptyResult(t,i,e){return{taskId:t,status:i.status,mode:e,lines:[],lineCount:0,metadata:{totalLines:i.outputLineCount,totalBytes:i.outputBytes,returnedBytes:0,truncated:!1,outputTruncated:i.outputTruncated,hasMore:!1,nextStartLine:null,outputFile:i.outputPath}}}async readFromFile(t,i,e,n,r,s,o){const a=[],u=new Set;let l=0,c=!1;const p=[];let g=-1/0;try{const w=await S.open(t.outputPath,"r"),m=R({input:w.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const f of m){const d=l;if(l++,i==="grep"&&d-e>=U&&d>=e){c=!0;break}if(d>=e){if(i==="lines"){if(a.length>=n){c=!0;break}u.has(d)||(u.add(d),a.push({line:d,text:f}))}else if(o.test(f)){for(const T of p)T.line>=e&&!u.has(T.line)&&(u.add(T.line),a.push(T));if(u.has(d)||(u.add(d),a.push({line:d,text:f})),g=d,a.length>=n){c=!0;break}}else if(r>0&&(p.push({line:d,text:f}),p.length>r&&p.shift()),g>=e&&d-g<=s&&!u.has(d)&&(u.add(d),a.push({line:d,text:f}),a.length>=n)){c=!0;break}}}m.close(),await w.close()}catch{return{rows:[],hasMore:!1,nextStartLine:null}}return c?{rows:a,hasMore:c,nextStartLine:l}:{rows:a,hasMore:c,nextStartLine:null}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(o=>{let a=null;const u=l=>{a&&clearTimeout(a),o(l)};n.completionWaiters.push(u),e&&e>0&&(a=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(l=>l!==u),o({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),a.unref?.())});const s=(await this.store.readSession(t))?.tasks.find(o=>o.taskId===i);return s?s.status!=="running"?{taskId:i,status:s.status,exitCode:s.exitCode,signal:s.signal,timedOut:s.killedReason==="timeout",aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt,...s.outputPath?{outputFilePath:s.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const r=e.get(n);if(!r)continue;const s=r.completionWaiters;if(s.length===0)continue;r.completionWaiters=[];const o={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-r.startedAt};for(const a of s)a(o)}}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)},F),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:r}=require("node:child_process");return r("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),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);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.killedReason="process_exit",e.child&&this.killProcessGroup(e.child,!0),e.finalized||this.finalizeTask(e,null,null,"process_exit")),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);await this.store.updateSession(t,()=>null),this.tasksByAgent.delete(t)}}async reconcileDiskRunning(t){const i=this.tasksByAgent.get(t);let e=0;return await this.store.updateSession(t,n=>{let r=!1;for(const s of n.tasks){if(s.status!=="running")continue;const o=i?.get(s.taskId)?.status==="running",a=s.ownerBootId===x;if(o){a||(s.ownerBootId=x,r=!0);continue}s.status="killed",s.killedReason="runtime_crashed",s.finishedAt=Date.now(),s.notified=!0,e++,r=!0}return r?n.tasks:null}),e}async listTasksWithReconcile(t,i){try{await this.reconcileDiskRunning(t)}catch{}const e=this.tasksByAgent.get(t),n=e?[...e.values()]:[],r=n.filter(o=>o.backgrounded).map(o=>this.toSummary(o)),s=new Set(n.map(o=>o.taskId));try{const o=await this.store.readSession(t);if(o){const a=Date.now();for(const u of o.tasks)s.has(u.taskId)||u.backgrounded===!0&&u.status!=="running"&&(u.finishedAt&&a-u.finishedAt>=B||i&&i!=="all"&&u.status!==i||r.push(V(u)))}}catch{}return i&&i!=="all"?r.filter(o=>o.status===i).sort((o,a)=>a.startedAt-o.startedAt):r.sort((o,a)=>a.startedAt-o.startedAt)}async reconcileAllAgents(){const t=await this.store.listAgents();let i=0;for(const e of t)try{i+=await this.reconcileDiskRunning(e)}catch{}return i}async reconcileOrphans(t){await this.reconcileDiskRunning(t)}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=M(4).toString("hex").slice(0,8);if(!t.has(e))return e}return M(6).toString("hex").slice(0,8)}async persistTask(t){await this.store.updateSession(t.agentId,i=>{const e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,description:t.description,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,ownerBootId:t.bootId,lastPulseAt:t.status==="running"?t.lastPulseAt:void 0,backgrounded:t.backgrounded},n=i.tasks.findIndex(s=>s.taskId===t.taskId);n>=0?i.tasks[n]=e:i.tasks.push(e);const r=Date.now();return i.tasks.filter(s=>s.status==="running"||!s.finishedAt||r-s.finishedAt<B)})}persistTaskSafe(t){this.persistTask(t).catch(i=>{console.error(`[BackgroundCommandManager] persist ${t.agentId}/${t.taskId} failed:`,i)})}}class I extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}function V(h){return{taskId:h.taskId,status:h.status,command:h.command,commandPreview:h.commandPreview,description:h.description,cwd:h.cwd,startedAt:h.startedAt,finishedAt:h.finishedAt,exitCode:h.exitCode,signal:h.signal,outputLineCount:h.outputLineCount,outputBytes:h.outputBytes}}const ot=new H;export{H as BackgroundCommandManager,I as BackgroundTaskError,ot as backgroundCommandManager};
7
7
 
@@ -21,6 +21,12 @@ export interface BackgroundTaskRecord {
21
21
  notified: boolean;
22
22
 
23
23
  killedReason?: string | null;
24
+
25
+ ownerBootId?: string;
26
+
27
+ lastPulseAt?: number;
28
+
29
+ backgrounded?: boolean;
24
30
  }
25
31
  export interface BackgroundSessionState {
26
32
  agentId: string;
@@ -37,6 +43,8 @@ export declare class BackgroundCommandStore {
37
43
 
38
44
  persistSession(agentId: string, tasks: BackgroundTaskRecord[]): Promise<void>;
39
45
 
46
+ updateSession(agentId: string, mutator: (state: BackgroundSessionState) => BackgroundTaskRecord[] | null | undefined): Promise<BackgroundSessionState | null>;
47
+
40
48
  markNotified(agentId: string, taskIds: string[]): Promise<void>;
41
49
 
42
50
  outputPathFor(agentId: string, taskId: string): string;
@@ -1,2 +1,2 @@
1
- import{promises as a}from"node:fs";import c from"node:path";import{renameWithRetry as f}from"./fs-utils.js";import{resolveAcodeHome as p}from"./paths.js";function w(s){return String(s).replace(/[^a-zA-Z0-9_-]/g,"_")}function u(s){return c.join(p(),"bg-tasks",w(s))}function h(s){return c.join(u(s),"state.json")}class k{constructor(){this.writeChains=new Map}chain(t){return this.writeChains.get(t)??Promise.resolve()}setChain(t,r){this.writeChains.set(t,r.catch(()=>{}))}async readSession(t){try{const r=await a.readFile(h(t),"utf-8"),e=JSON.parse(r);return{agentId:t,updatedAt:typeof e.updatedAt=="number"?e.updatedAt:0,tasks:Array.isArray(e.tasks)?e.tasks:[]}}catch(r){if(r?.code==="ENOENT")return null;if(r instanceof SyntaxError)return await this.backupCorrupt(t).catch(()=>{}),null;throw r}}async persistSession(t,r){const e=h(t),n=this.chain(t).then(async()=>{await a.mkdir(u(t),{recursive:!0});const o={agentId:t,updatedAt:Date.now(),tasks:r},i=`${e}.tmp`;await a.writeFile(i,JSON.stringify(o,null,2),"utf-8"),await f(i,e)});this.setChain(t,n),await n}async markNotified(t,r){const e=await this.readSession(t);if(!e)return;const n=new Set(r);let o=!1;for(const i of e.tasks)n.has(i.taskId)&&!i.notified&&(i.notified=!0,o=!0);o&&await this.persistSession(t,e.tasks)}outputPathFor(t,r){return c.join(u(t),`${r}.out`)}async deleteAgent(t){const r=this.chain(t).then(()=>a.rm(u(t),{recursive:!0,force:!0}));this.setChain(t,r),await r.catch(()=>{})}async listAgents(){try{return(await a.readdir(c.join(p(),"bg-tasks"),{withFileTypes:!0})).filter(r=>r.isDirectory()).map(r=>r.name)}catch(t){if(t?.code==="ENOENT")return[];throw t}}async backupCorrupt(t){const r=h(t),e=`${r}.corrupt-${Date.now()}`;await a.copyFile(r,e).catch(()=>{})}}export{k as BackgroundCommandStore};
1
+ import{promises as a}from"node:fs";import u from"node:path";import{renameWithRetry as l}from"./fs-utils.js";import{resolveAcodeHome as m}from"./paths.js";function d(o){return String(o).replace(/[^a-zA-Z0-9_-]/g,"_")}function c(o){return u.join(m(),"bg-tasks",d(o))}function h(o){return u.join(c(o),"state.json")}class C{constructor(){this.writeChains=new Map}chain(t){return this.writeChains.get(t)??Promise.resolve()}setChain(t,r){this.writeChains.set(t,r.catch(()=>{}))}async readSession(t){try{const r=await a.readFile(h(t),"utf-8"),s=JSON.parse(r);return{agentId:t,updatedAt:typeof s.updatedAt=="number"?s.updatedAt:0,tasks:Array.isArray(s.tasks)?s.tasks:[]}}catch(r){if(r?.code==="ENOENT")return null;if(r instanceof SyntaxError)return await this.backupCorrupt(t).catch(()=>{}),null;throw r}}async persistSession(t,r){const s=h(t),i=this.chain(t).then(async()=>{await a.mkdir(c(t),{recursive:!0});const n={agentId:t,updatedAt:Date.now(),tasks:r},e=`${s}.tmp`;await a.writeFile(e,JSON.stringify(n,null,2),"utf-8"),await l(e,s)});this.setChain(t,i),await i}async updateSession(t,r){const s=h(t);let i=null;const n=this.chain(t).then(async()=>{const e=await this.readSession(t)??{agentId:t,updatedAt:0,tasks:[]},p=r(e);if(!p){i=e;return}const f={agentId:t,updatedAt:Date.now(),tasks:p};await a.mkdir(c(t),{recursive:!0});const w=`${s}.tmp`;await a.writeFile(w,JSON.stringify(f,null,2),"utf-8"),await l(w,s),i=f});return this.setChain(t,n),await n,i}async markNotified(t,r){const s=await this.readSession(t);if(!s)return;const i=new Set(r);let n=!1;for(const e of s.tasks)i.has(e.taskId)&&!e.notified&&(e.notified=!0,n=!0);n&&await this.persistSession(t,s.tasks)}outputPathFor(t,r){return u.join(c(t),`${r}.out`)}async deleteAgent(t){const r=this.chain(t).then(()=>a.rm(c(t),{recursive:!0,force:!0}));this.setChain(t,r),await r.catch(()=>{})}async listAgents(){try{return(await a.readdir(u.join(m(),"bg-tasks"),{withFileTypes:!0})).filter(r=>r.isDirectory()).map(r=>r.name)}catch(t){if(t?.code==="ENOENT")return[];throw t}}async backupCorrupt(t){const r=h(t),s=`${r}.corrupt-${Date.now()}`;await a.copyFile(r,s).catch(()=>{})}}export{C as BackgroundCommandStore};
2
2