aws-runtime-bridge 1.9.185 → 1.9.186

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,39 @@ files 白名单),供调度中心「查看本次更新内容」弹窗按 `##
5
5
  展示。发布新版本时须在仓库中先写好 `## v<version>` 段(prepack-guard 校验缺段
6
6
  即阻断发布);Bridge 自更新成功后的运行时追加仅作为包内缺段时的兜底。
7
7
 
8
+ ## v1.9.186
9
+
10
+ _发布日期:2026-11-04_
11
+
12
+ ### 新增:语音消息发送状态查询/等待 get_send_status(私信与群聊通用)
13
+
14
+ send_private / send_group 带语音不再让工具调用阻塞到合成结束:受理即落库、
15
+ 最多同步等待 20s——终态(已送达/已按文字兜底/失败)直接返回完整结果,
16
+ 超时返回 pending + ticket,任务留在后台继续。新增 `get_send_status` 工具凭
17
+ tickets 查询或阻塞等待终态:
18
+
19
+ - 可传 `wait_timeout_ms` 阻塞等待(全部票据到终态立即提前返回;到点未完返回
20
+ 当前快照,不算错误),多条语音一次传齐统一核销;
21
+ - 结果带 state/msgId/fallbackMsgId/error,群聊票据附 roomId/projectId、私信附
22
+ targetId,连发多条也能把结果对回具体消息;
23
+ - 客户端等待上限封顶 270s:Node fetch(undici) 默认 headersTimeout=300s,
24
+ 与服务端 300s 预算贴边必然网络层失败(与 create_meeting 同一道坑);
25
+ - acode 侧该工具归入轮询类(免 60s MCP 超时),适配层归为 idle(等待语音送达),
26
+ 豁免 tool_using 5min 慢超时看门狗——否则 270s 等待会被强制恢复打断。
27
+
28
+ ### 修复:Windows 11 26200 上启动项端口徽标一直为空
29
+
30
+ 部分 Windows 构建对监听 socket 的 State 上报不稳定(node 绑定 ::/127.0.0.1 的
31
+ 监听项 State 为 null),`Get-NetTCPConnection -State Listen` 直接漏掉,vite 等
32
+ 端口徽标永远空。改为全量枚举后按「无对端(RemotePort=0)」客户端过滤监听项。
33
+
34
+ ### 修复:启动项端口徽标错过晚绑定的端口
35
+
36
+ vite/spring-boot 的端口常在 start 返回后数秒才监听,原先只在建连时补探一次
37
+ (固定 2s),错过即永久空。改为 5s 间隔续探(探到 / 进程退出 / 2 分钟窗口超时
38
+ 即停);端口一经探到即缓存,后续状态帧不再把已显示的徽标清空;无订阅者时不再
39
+ 做后台续探。相关:工作区文件搜索/替换支持限定在当前显示目录(rootPath)。
40
+
8
41
  ## v1.9.185
9
42
 
10
43
  _发布日期:2026-11-04_
@@ -1,2 +1,2 @@
1
- import{EventEmitter as S}from"node:events";const m="[temp clipboard paste file]",y=/[A-Z]:(?:\\\\|\\)[^\r\n"'`]*?(?:\\\\|\\)mini-term-clipboard(?:\\\\|\\)paste-\d+\.txt/gi,P=/\/[^\r\n"'`]*?\/mini-term-clipboard\/paste-\d+\.txt/gi,w=/mini-term-clipboard[\\/]paste-\d+\.txt/gi;function d(t){return t.replace(y,m).replace(P,m).replace(w,m)}function $(t){const e=t.trim().replace(/^(["'])(.*)\1$/,"$2");return[y,P,w].some(c=>{c.lastIndex=0;const i=c.exec(e);return!!(i&&i[0]===e)})}function u(t){return typeof t=="string"?d(t):Array.isArray(t)?t.map(e=>u(e)):t&&typeof t=="object"?Object.fromEntries(Object.entries(t).map(([e,c])=>[e,u(c)])):t}const h={get_colleague:"aws-mcp",get_group_rooms:"aws-mcp",list_project_members:"aws-mcp",get_profile:"aws-mcp",hire_colleague:"aws-mcp",send_group:"aws-mcp",send_private:"aws-mcp",poll_message:"aws-mcp",get_message:"aws-mcp",get_dm_history:"aws-mcp",get_group_history:"aws-mcp",my_task:"aws-mcp",submit_task_result:"aws-mcp"},C=new Set(["aws-mcp","aws-mcp-dev","agents-work-studio"]);function g(t,e){const c=h[e.toLowerCase()];return c&&C.has(t.toLowerCase())?c:t}function L(t){const e=t.trim();if(!e)return e;if(e.includes("__")){const i=e.split("__").filter(Boolean);if(i.length>=3&&i[0].toLowerCase()==="mcp"){const f=i[1],o=i.slice(2).join("__");return`${g(f,o)}/${o}`}if(i.length>=2){const f=i[0],o=i.slice(1).join("__");return`${g(f,o)}/${o}`}}const c=h[e.toLowerCase()];return c?`${c}/${e}`:e}function k(t,e){return t?e?`${g(t,e)}/${e}`:t:e}const E=500;function p(t,e=E){return!t||t.length<=e?t:`${t.slice(0,e)}...\uFF08\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`}function j(t,e,c){if(!t)return{actionType:"other",actionLabel:"\u8C03\u7528\u5DE5\u5177"};const i=t.toLowerCase(),f=i.replace(/[_-]/g,""),A=(i.startsWith("mcp__")?i.split("__").slice(2).join("__"):i.startsWith("builtin__")?i.split("__").slice(1).join("__"):i.includes("-mcp__")?i.slice(i.indexOf("-mcp__")+6):i.includes("/")?i.split("/").filter(Boolean).at(-1)||i:i.includes("__")&&i.split("__").pop()||i).replace(/[_-]/g,""),D=a=>Array.isArray(a)?a.map(r=>String(r??"").trim()).filter(r=>r.length>0):[],n=(...a)=>{for(const r of a){if(typeof r=="string"&&r.trim())return d(r);if(Array.isArray(r)&&r.length>0){const l=r[0];if(typeof l=="string"&&l.trim())return d(l)}}},s=(...a)=>{const r=a.map(l=>l.replace(/[_-]/g,""));return r.includes(f)||r.includes(A)},T=D(e?.load_skills);if((i==="task"||i==="task()")&&T.length>0)return{actionType:"skill",actionLabel:"\u52A0\u8F7D\u6280\u80FD",actionDetail:T.join(", ")};if(i==="skill"||i==="load_skill"||i==="disclosecontext"||i.includes("skill")&&!i.startsWith("mcp__")&&!i.includes("mcp_")){let a="";if(Array.isArray(e?.load_skills)&&e.load_skills.length>0)a=e.load_skills.join(", ");else if(typeof e?.skill=="string")a=e.skill;else if(typeof e?.name=="string")a=e.name;else if(e?.args&&typeof e.args=="object"){const r=e.args;typeof r.name=="string"?a=r.name:typeof r.skill=="string"&&(a=r.skill)}return{actionType:"skill",actionLabel:"\u4F7F\u7528\u6280\u80FD",actionDetail:a||void 0}}if(s("edit","editfile","strreplace","editcode","apply_patch","multiedit","replace"))return{actionType:"edit_file",actionLabel:"\u7F16\u8F91\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("write","writefile","fswrite"))return{actionType:"write_file",actionLabel:"\u5199\u5165\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("read","readfile","readcode","readmultiplefiles","read_file"))return Array.isArray(e?.paths)?{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:`${e.paths.length}\u4E2A\u6587\u4EF6`}:{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.paths,e?.target_file,e?.targetFile)};if(s("bash","shell","command","executepwsh","controlpwshprocess","execute","run_command","execute_command")){const a=n(e?.command,e?.cmd,e?.argv);return{actionType:"bash",actionLabel:"\u6267\u884C\u547D\u4EE4",actionDetail:p(a)}}if(s("poll_message","get_message"))return{actionType:"idle",actionLabel:"\u76D1\u542C\u6D88\u606F",actionDetail:L(t)};if(s("create_meeting"))return{actionType:"idle",actionLabel:"\u7B49\u5F85\u4F1A\u8BAE\u5F00\u59CB",actionDetail:L(t)};if(s("ask_user_question","askuserquestion"))return{actionType:"other",actionLabel:"\u7B49\u5F85\u56DE\u7B54"};if(c?.mcpServer){const a=c.mcpServer;return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:k(a,t||"")}}const b=h[t];if(b)return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:`${b}/${t}`};if(i.startsWith("mcp__")||i.includes("-mcp__")||i.includes("mcp_")&&!i.includes("skill")||i==="kiropowers"){let a="",r="";if(i.startsWith("mcp__")){const _=t.split("__");a=_[1]||"",r=_.slice(2).join("__")||""}else if(i.includes("-mcp__")){const _=i.indexOf("-mcp__");a=i.substring(0,_+4),r=i.substring(_+6)}else i==="kiropowers"?(a="kiro",r=e?.powerName||e?.action||""):(a="mcp",r=t);return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:k(a,r)||t}}if(s("edit","editfile","strreplace","editcode","apply_patch","multiedit","replace"))return{actionType:"edit_file",actionLabel:"\u7F16\u8F91\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("write","writefile","fswrite"))return{actionType:"write_file",actionLabel:"\u5199\u5165\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("read","readfile","readcode","readmultiplefiles","read_file"))return Array.isArray(e?.paths)?{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:`${e.paths.length}\u4E2A\u6587\u4EF6`}:{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.paths,e?.target_file,e?.targetFile)};if(s("bash","shell","command","executepwsh","controlpwshprocess","execute","run_command","execute_command")){const a=n(e?.command,e?.cmd,e?.argv);return{actionType:"bash",actionLabel:"\u6267\u884C\u547D\u4EE4",actionDetail:p(a)}}if(i==="glob"||i==="grep"||i==="find"||i==="search"||i==="grepsearch"||i==="filesearch"){const a=e?.query||e?.pattern;return{actionType:"search",actionLabel:"\u641C\u7D22\u6587\u4EF6",actionDetail:p(a)}}if(i==="webfetch"||i==="websearch"||i==="fetch"||i==="http"||i==="remote_web_search"){const a=e?.url||e?.query;return{actionType:"web",actionLabel:"\u7F51\u7EDC\u8BF7\u6C42",actionDetail:p(a)}}return i.startsWith("lsp")||i.includes("language_server")?{actionType:"lsp",actionLabel:"LSP\u64CD\u4F5C"}:i==="notebookedit"||i.includes("notebook")?{actionType:"notebook",actionLabel:"\u7F16\u8F91Notebook"}:{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:t}}class R extends S{}export{R as BaseProviderAdapter,L as getCanonicalMcpDetail,j as getToolActionInfo,$ as isTransientClipboardPasteFilePath,d as redactTransientClipboardPastePaths,u as redactTransientClipboardPastePathsInValue};
1
+ import{EventEmitter as S}from"node:events";const m="[temp clipboard paste file]",P=/[A-Z]:(?:\\\\|\\)[^\r\n"'`]*?(?:\\\\|\\)mini-term-clipboard(?:\\\\|\\)paste-\d+\.txt/gi,w=/\/[^\r\n"'`]*?\/mini-term-clipboard\/paste-\d+\.txt/gi,u=/mini-term-clipboard[\\/]paste-\d+\.txt/gi;function d(t){return t.replace(P,m).replace(w,m).replace(u,m)}function $(t){const e=t.trim().replace(/^(["'])(.*)\1$/,"$2");return[P,w,u].some(c=>{c.lastIndex=0;const i=c.exec(e);return!!(i&&i[0]===e)})}function L(t){return typeof t=="string"?d(t):Array.isArray(t)?t.map(e=>L(e)):t&&typeof t=="object"?Object.fromEntries(Object.entries(t).map(([e,c])=>[e,L(c)])):t}const h={get_colleague:"aws-mcp",get_group_rooms:"aws-mcp",list_project_members:"aws-mcp",get_profile:"aws-mcp",hire_colleague:"aws-mcp",send_group:"aws-mcp",send_private:"aws-mcp",poll_message:"aws-mcp",get_message:"aws-mcp",get_send_status:"aws-mcp",get_dm_history:"aws-mcp",get_group_history:"aws-mcp",my_task:"aws-mcp",submit_task_result:"aws-mcp"},C=new Set(["aws-mcp","aws-mcp-dev","agents-work-studio"]);function g(t,e){const c=h[e.toLowerCase()];return c&&C.has(t.toLowerCase())?c:t}function T(t){const e=t.trim();if(!e)return e;if(e.includes("__")){const i=e.split("__").filter(Boolean);if(i.length>=3&&i[0].toLowerCase()==="mcp"){const f=i[1],o=i.slice(2).join("__");return`${g(f,o)}/${o}`}if(i.length>=2){const f=i[0],o=i.slice(1).join("__");return`${g(f,o)}/${o}`}}const c=h[e.toLowerCase()];return c?`${c}/${e}`:e}function k(t,e){return t?e?`${g(t,e)}/${e}`:t:e}const E=500;function p(t,e=E){return!t||t.length<=e?t:`${t.slice(0,e)}...\uFF08\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09`}function j(t,e,c){if(!t)return{actionType:"other",actionLabel:"\u8C03\u7528\u5DE5\u5177"};const i=t.toLowerCase(),f=i.replace(/[_-]/g,""),A=(i.startsWith("mcp__")?i.split("__").slice(2).join("__"):i.startsWith("builtin__")?i.split("__").slice(1).join("__"):i.includes("-mcp__")?i.slice(i.indexOf("-mcp__")+6):i.includes("/")?i.split("/").filter(Boolean).at(-1)||i:i.includes("__")&&i.split("__").pop()||i).replace(/[_-]/g,""),D=a=>Array.isArray(a)?a.map(r=>String(r??"").trim()).filter(r=>r.length>0):[],n=(...a)=>{for(const r of a){if(typeof r=="string"&&r.trim())return d(r);if(Array.isArray(r)&&r.length>0){const l=r[0];if(typeof l=="string"&&l.trim())return d(l)}}},s=(...a)=>{const r=a.map(l=>l.replace(/[_-]/g,""));return r.includes(f)||r.includes(A)},b=D(e?.load_skills);if((i==="task"||i==="task()")&&b.length>0)return{actionType:"skill",actionLabel:"\u52A0\u8F7D\u6280\u80FD",actionDetail:b.join(", ")};if(i==="skill"||i==="load_skill"||i==="disclosecontext"||i.includes("skill")&&!i.startsWith("mcp__")&&!i.includes("mcp_")){let a="";if(Array.isArray(e?.load_skills)&&e.load_skills.length>0)a=e.load_skills.join(", ");else if(typeof e?.skill=="string")a=e.skill;else if(typeof e?.name=="string")a=e.name;else if(e?.args&&typeof e.args=="object"){const r=e.args;typeof r.name=="string"?a=r.name:typeof r.skill=="string"&&(a=r.skill)}return{actionType:"skill",actionLabel:"\u4F7F\u7528\u6280\u80FD",actionDetail:a||void 0}}if(s("edit","editfile","strreplace","editcode","apply_patch","multiedit","replace"))return{actionType:"edit_file",actionLabel:"\u7F16\u8F91\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("write","writefile","fswrite"))return{actionType:"write_file",actionLabel:"\u5199\u5165\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("read","readfile","readcode","readmultiplefiles","read_file"))return Array.isArray(e?.paths)?{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:`${e.paths.length}\u4E2A\u6587\u4EF6`}:{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.paths,e?.target_file,e?.targetFile)};if(s("bash","shell","command","executepwsh","controlpwshprocess","execute","run_command","execute_command")){const a=n(e?.command,e?.cmd,e?.argv);return{actionType:"bash",actionLabel:"\u6267\u884C\u547D\u4EE4",actionDetail:p(a)}}if(s("poll_message","get_message"))return{actionType:"idle",actionLabel:"\u76D1\u542C\u6D88\u606F",actionDetail:T(t)};if(s("get_send_status"))return{actionType:"idle",actionLabel:"\u7B49\u5F85\u8BED\u97F3\u9001\u8FBE",actionDetail:T(t)};if(s("create_meeting"))return{actionType:"idle",actionLabel:"\u7B49\u5F85\u4F1A\u8BAE\u5F00\u59CB",actionDetail:T(t)};if(s("ask_user_question","askuserquestion"))return{actionType:"other",actionLabel:"\u7B49\u5F85\u56DE\u7B54"};if(c?.mcpServer){const a=c.mcpServer;return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:k(a,t||"")}}const y=h[t];if(y)return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:`${y}/${t}`};if(i.startsWith("mcp__")||i.includes("-mcp__")||i.includes("mcp_")&&!i.includes("skill")||i==="kiropowers"){let a="",r="";if(i.startsWith("mcp__")){const _=t.split("__");a=_[1]||"",r=_.slice(2).join("__")||""}else if(i.includes("-mcp__")){const _=i.indexOf("-mcp__");a=i.substring(0,_+4),r=i.substring(_+6)}else i==="kiropowers"?(a="kiro",r=e?.powerName||e?.action||""):(a="mcp",r=t);return{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:k(a,r)||t}}if(s("edit","editfile","strreplace","editcode","apply_patch","multiedit","replace"))return{actionType:"edit_file",actionLabel:"\u7F16\u8F91\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("write","writefile","fswrite"))return{actionType:"write_file",actionLabel:"\u5199\u5165\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.target_file,e?.targetFile)};if(s("read","readfile","readcode","readmultiplefiles","read_file"))return Array.isArray(e?.paths)?{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:`${e.paths.length}\u4E2A\u6587\u4EF6`}:{actionType:"read_file",actionLabel:"\u8BFB\u53D6\u6587\u4EF6",actionDetail:n(e?.file_path,e?.filePath,e?.path,e?.paths,e?.target_file,e?.targetFile)};if(s("bash","shell","command","executepwsh","controlpwshprocess","execute","run_command","execute_command")){const a=n(e?.command,e?.cmd,e?.argv);return{actionType:"bash",actionLabel:"\u6267\u884C\u547D\u4EE4",actionDetail:p(a)}}if(i==="glob"||i==="grep"||i==="find"||i==="search"||i==="grepsearch"||i==="filesearch"){const a=e?.query||e?.pattern;return{actionType:"search",actionLabel:"\u641C\u7D22\u6587\u4EF6",actionDetail:p(a)}}if(i==="webfetch"||i==="websearch"||i==="fetch"||i==="http"||i==="remote_web_search"){const a=e?.url||e?.query;return{actionType:"web",actionLabel:"\u7F51\u7EDC\u8BF7\u6C42",actionDetail:p(a)}}return i.startsWith("lsp")||i.includes("language_server")?{actionType:"lsp",actionLabel:"LSP\u64CD\u4F5C"}:i==="notebookedit"||i.includes("notebook")?{actionType:"notebook",actionLabel:"\u7F16\u8F91Notebook"}:{actionType:"mcp",actionLabel:"\u8C03\u7528MCP",actionDetail:t}}class R extends S{}export{R as BaseProviderAdapter,T as getCanonicalMcpDetail,j as getToolActionInfo,$ as isTransientClipboardPasteFilePath,d as redactTransientClipboardPastePaths,L as redactTransientClipboardPastePathsInValue};
2
2
 
@@ -1,2 +1,2 @@
1
- import{createReadStream as S,promises as u}from"node:fs";import T from"node:os";import w from"node:path";import{pipeline as W}from"node:stream/promises";import{Router as R}from"express";import j from"multer";import{allowHostFileBrowser as v}from"../config.js";import{validateToken as c}from"../middleware/auth.js";import{isIgnoredByGitIgnore as A,loadGitIgnoreRules as N}from"../services/gitignore.js";import{chmodWorkspaceEntry as C,createWorkspaceEntry as x,deleteWorkspaceEntry as F,extractWorkspaceArchive as B,getWorkspaceEntryProperties as z,listWorkspaceDirectory as L,moveWorkspaceEntry as M,previewWorkspaceDocument as O,readWorkspaceFile as H,renameWorkspaceEntry as U,replaceWorkspaceFiles as G,resolveWorkspaceDownloadTarget as X,searchWorkspaceFiles as Y,searchWorkspacePaths as q,uploadWorkspaceFiles as J,writeWorkspaceFile as $,zipWorkspaceDirectoryToTemp as K}from"../services/workspace-files.js";import{unwatchWorkspaceFile as V,watchWorkspaceFile as Z}from"../services/workspace-watch.js";import{createLogger as Q}from"../utils/logger.js";const h=Q("file-browser"),p=R(),m=new Map,tt=6e4,b=2e3,et=Number.POSITIVE_INFINITY,rt=j({dest:w.join(T.tmpdir(),"agentswork-runtime-bridge-uploads"),limits:{files:b,fileSize:et}}),f=Number.MAX_SAFE_INTEGER,g=Number.MAX_SAFE_INTEGER;function P(o){return o===!0||o==="true"||o==="1"}function ot(o){return`attachment; filename="${o.replace(/[\\"\r\n]/g,"_").replace(/[^\x20-\x7E]/g,"_")}"; filename*=UTF-8''${encodeURIComponent(o)}`}async function at(o=[]){await Promise.all(o.map(e=>u.rm(e.path,{force:!0}).catch(()=>{})))}function st(o){return{status:400,body:{error:o.code==="LIMIT_FILE_COUNT"?`\u4E00\u6B21\u6700\u591A\u4E0A\u4F20 ${b} \u4E2A\u6587\u4EF6\uFF0C\u8BF7\u62C6\u5206\u6587\u4EF6\u5939\u540E\u91CD\u8BD5\u3002`:o.message,code:o.code}}}function nt(o){const e=o.relativePathsJson;if(typeof e=="string"&&e.trim()){const t=JSON.parse(e);if(!Array.isArray(t))throw new Error("relativePathsJson must be an array");return t.map(a=>String(a||""))}const r=o.relativePaths;return Array.isArray(r)?r.map(t=>String(t||"")):typeof r=="string"?[r]:[]}function it(o,e,r){rt.array("files")(o,e,t=>{if(!t){r();return}if(t instanceof j.MulterError){const a=st(t);e.status(a.status).json(a.body);return}r(t)})}async function k(o=""){const r=process.platform==="win32";if(!o||o===""){if(r){const d=Array.from({length:24},(y,_)=>String.fromCharCode(67+_)+":\\");return{isWindows:!0,currentPath:"",items:(await Promise.all(d.map(async y=>{try{return await u.access(y),{name:y,path:y,isDirectory:!0}}catch{return null}}))).filter(y=>y!==null)}}return{isWindows:!1,currentPath:"/",items:(await u.readdir("/",{withFileTypes:!0})).filter(d=>d.isDirectory()).map(d=>({name:d.name,path:"/"+d.name,isDirectory:!0}))}}const t=w.normalize(o);let a;try{a=await u.stat(t)}catch{throw new Error("Path not found: "+t)}if(!a.isDirectory())throw new Error("Path is not a directory: "+t);const i=(await u.readdir(t,{withFileTypes:!0})).filter(n=>n.isDirectory()).map(n=>({name:n.name,path:w.join(t,n.name),isDirectory:!0}));return{isWindows:r,currentPath:t,items:i}}async function ct(o=""){const e=await k(o),r=e.currentPath?await N(e.currentPath):[];return e.currentPath?{...e,maxDepth:f,maxItemsPerDirectory:g,tree:await D(e.currentPath,e.currentPath,1,r)}:{...e,maxDepth:f,maxItemsPerDirectory:g,tree:e.items.slice(0,g)}}async function D(o,e,r,t){if(r>f)return[];const s=(await u.readdir(e,{withFileTypes:!0})).filter(n=>n.isDirectory()).filter(n=>!pt(o,w.join(e,n.name),t)).sort((n,l)=>n.name.localeCompare(l.name)).slice(0,g),i=[];for(const n of s){const l=w.join(e,n.name),d={name:n.name,path:l.replace(/\\/g,"/"),isDirectory:!0};if(r<f)try{d.children=await D(o,l,r+1,t)}catch{d.children=[]}else d.truncated=!0;i.push(d)}return i}import{isIgnoredByGitIgnore as Tt,loadGitIgnoreRules as Wt,parseGitIgnoreRules as Rt}from"../services/gitignore.js";function pt(o,e,r){if(r.length===0)return!1;const t=w.relative(o,e).replace(/\\/g,"/");return A(t,!0,r)}async function E(o){const e=w.normalize(String(o||""));if(!(await u.stat(e)).isDirectory())throw new Error("Path is not a directory: "+e);return e}function I(o){const e=String(o||"").trim();if(!e)throw new Error("Folder name is required");if(e.includes("/")||e.includes("\\")||e==="."||e==="..")throw new Error("Folder name must not contain path separators");return e}async function lt(o,e){const r=await E(o),t=I(e),a=w.join(r,t);return await u.mkdir(a),{ok:!0,parentPath:o,targetPath:a.replace(/\\/g,"/"),entryName:t}}async function dt(o,e){const r=await E(o),t=I(e),a=w.join(w.dirname(r),t);return await u.rename(r,a),{ok:!0,sourcePath:r.replace(/\\/g,"/"),targetPath:a.replace(/\\/g,"/"),newName:t}}p.post("/list",c,async(o,e)=>{try{if(!v)return e.status(403).json({error:"Host filesystem browsing is disabled. Use /api/file-browser/workspace/list with a registered workspace, or set AWS_ALLOW_HOST_FILE_BROWSER=true for isolated local debugging."});const{path:r=""}=o.body||{};e.json(await k(String(r||"")))}catch(r){const t=r;e.status(500).json({error:t.message})}}),p.post("/directory-picker/list",c,async(o,e)=>{try{const{path:r=""}=o.body||{};e.json(await k(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/directory-picker/tree",c,async(o,e)=>{try{const{path:r=""}=o.body||{};e.json(await ct(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/workspace/list",c,async(o,e)=>{try{const{workspacePath:r,path:t=""}=o.body||{},a=await L({workspacePath:String(r||""),targetPath:String(t||"")});e.json({isWindows:process.platform==="win32",currentPath:a.currentPath,workspacePath:a.workspacePath,items:a.items})}catch(r){const t=r;console.error("[file-browser] failed to list workspace directory:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/read",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{},a=await H({workspacePath:String(r||""),filePath:String(t||"")});e.json(a)}catch(r){const t=r;console.error("[file-browser] failed to read workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/properties",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t}=o.body||{};e.json(await z({workspacePath:String(r||""),targetPath:String(t||"")}))}catch(r){const t=r;h.error("failed to get workspace entry properties:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/chmod",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,mode:a,recursive:s}=o.body||{};e.json(await C({workspacePath:String(r||""),targetPath:String(t||""),mode:String(a||""),recursive:!!s}))}catch(r){const t=r;h.error("failed to chmod workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/create",c,async(o,e)=>{try{const{path:r="",entryName:t}=o.body||{};e.json(await lt(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to create directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/rename",c,async(o,e)=>{try{const{targetPath:r,newName:t}=o.body||{};e.json(await dt(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to rename directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/preview",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{},a=await O({workspacePath:String(r||""),filePath:String(t||"")});e.json(a)}catch(r){const t=r;h.error("failed to preview workspace document:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/watch",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{};e.json(await Z({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to watch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/unwatch",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{};e.json(await V({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to unwatch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/write",c,async(o,e)=>{try{const{workspacePath:r,filePath:t,content:a}=o.body||{},s=await $({workspacePath:String(r||""),filePath:String(t||""),content:String(a??"")});e.json(s)}catch(r){const t=r;console.error("[file-browser] failed to write workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/create",c,async(o,e)=>{try{const{workspacePath:r,path:t="",entryName:a,isDirectory:s}=o.body||{},i=await x({workspacePath:String(r||""),targetPath:String(t||""),entryName:String(a||""),isDirectory:!!s});e.json(i)}catch(r){const t=r;h.error("failed to create workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/rename",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,newName:a}=o.body||{},s=await U({workspacePath:String(r||""),targetPath:String(t||""),newName:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to rename workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/move",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,destinationPath:a}=o.body||{},s=await M({workspacePath:String(r||""),targetPath:String(t||""),destinationPath:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to move workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/delete",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t}=o.body||{},a=await F({workspacePath:String(r||""),targetPath:String(t||"")});e.json(a)}catch(r){const t=r;h.error("failed to delete workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/upload",c,it,async(o,e)=>{const r=o.files||[];try{const{workspacePath:t,path:a="",extractArchives:s}=o.body||{},i=nt(o.body||{}),n=await J({workspacePath:String(t||""),targetPath:String(a||""),extractArchives:P(s),files:r.map((l,d)=>({originalname:l.originalname,relativePath:i[d],path:l.path,size:l.size}))});e.json(n)}catch(t){const a=t;h.error("failed to upload workspace files:",a),e.status(400).json({error:a.message})}finally{await at(r)}}),p.post("/workspace/download",c,async(o,e)=>{let r;const t=String((o.body||{}).progressId||"").trim();try{const{workspacePath:a,targetPath:s}=o.body||{},i=await X({workspacePath:String(a||""),targetPath:String(s||"")});if(e.setHeader("Content-Type",i.contentType),e.setHeader("Content-Disposition",ot(i.fileName)),e.setHeader("X-Workspace-Path",encodeURIComponent(i.workspacePath)),e.setHeader("X-Workspace-Target-Path",encodeURIComponent(i.targetPath)),i.isDirectory){t&&m.set(t,{zippedBytes:0,totalBytes:null,done:!1,cancelled:!1,updatedAt:Date.now()}),r=await K(i.resolvedTargetPath,i.fileName,l=>{if(!t)return;const d=m.get(t);d&&(d.zippedBytes=l,d.updatedAt=Date.now())},()=>t?!!m.get(t)?.cancelled:!1);const n=await u.stat(r);if(t){const l=m.get(t);l&&(l.zippedBytes=n.size,l.totalBytes=n.size,l.done=!0,l.updatedAt=Date.now())}e.setHeader("Content-Length",String(n.size)),await W(S(r),e);return}i.size!==void 0&&e.setHeader("Content-Length",String(i.size)),S(i.resolvedTargetPath).pipe(e)}catch(a){const s=a;h.error("failed to download workspace entry:",s),e.headersSent?e.destroy(s):e.status(400).json({error:s.message})}finally{if(r&&await u.rm(r,{force:!0}).catch(()=>{}),t){const a=m.get(t);a&&(a.done=!0,a.totalBytes===null&&(a.totalBytes=0)),setTimeout(()=>m.delete(t),tt)}}}),p.post("/workspace/download-progress",c,(o,e)=>{const r=String((o.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=m.get(r);if(!t){e.status(404).json({error:"download progress not found"});return}e.json({zippedBytes:t.zippedBytes,totalBytes:t.totalBytes,done:t.done,cancelled:t.cancelled})}),p.post("/workspace/download-cancel",c,(o,e)=>{const r=String((o.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=m.get(r);t&&(t.cancelled=!0,t.done=!0,t.updatedAt=Date.now()),e.json({ok:!0})}),p.post("/workspace/extract",c,async(o,e)=>{try{const{workspacePath:r,archivePath:t,outputPath:a=""}=o.body||{},s=await B({workspacePath:String(r||""),archivePath:String(t||""),outputPath:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to extract workspace archive:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/path-search",c,async(o,e)=>{try{const{workspacePath:r,rootPath:t,keyword:a,ignoreCase:s,maxResults:i,maxDirectories:n}=o.body||{},l=await q({workspacePath:String(r||""),rootPath:t!==void 0?String(t):void 0,keyword:String(a||""),ignoreCase:P(s),maxResults:typeof i=="number"?i:void 0,maxDirectories:typeof n=="number"?n:void 0});e.json(l)}catch(r){const t=r;h.error("failed to search workspace paths:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/search",c,async(o,e)=>{try{const{workspacePath:r,keyword:t,fileMask:a,ignoreCase:s,maxResults:i,mode:n}=o.body||{},l=await Y({workspacePath:String(r||""),keyword:String(t||""),fileMask:a!==void 0?String(a):void 0,ignoreCase:P(s),maxResults:typeof i=="number"?i:void 0,mode:n==="filename"?"filename":"content"});e.json(l)}catch(r){const t=r;h.error("failed to search workspace files:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/replace",c,async(o,e)=>{try{const{workspacePath:r,keyword:t,replacement:a,fileMask:s,ignoreCase:i,filePath:n}=o.body||{},l=await G({workspacePath:String(r||""),keyword:String(t||""),replacement:String(a??""),fileMask:s!==void 0?String(s):void 0,ignoreCase:P(i),filePath:n!==void 0?String(n):void 0});e.json(l)}catch(r){const t=r;h.error("failed to replace workspace files:",t),e.status(400).json({error:t.message})}});export{b as WORKSPACE_UPLOAD_FILE_LIMIT,et as WORKSPACE_UPLOAD_MAX_FILE_SIZE_BYTES,st as createWorkspaceUploadLimitResponse,p as fileBrowserRouter,Tt as isIgnoredByGitIgnore,ct as listHostDirectoryTree,Wt as loadGitIgnoreRules,Rt as parseGitIgnoreRules,nt as parseWorkspaceUploadRelativePaths};
1
+ import{createReadStream as S,promises as u}from"node:fs";import T from"node:os";import w from"node:path";import{pipeline as W}from"node:stream/promises";import{Router as R}from"express";import j from"multer";import{allowHostFileBrowser as v}from"../config.js";import{validateToken as c}from"../middleware/auth.js";import{isIgnoredByGitIgnore as A,loadGitIgnoreRules as N}from"../services/gitignore.js";import{chmodWorkspaceEntry as C,createWorkspaceEntry as x,deleteWorkspaceEntry as F,extractWorkspaceArchive as B,getWorkspaceEntryProperties as z,listWorkspaceDirectory as L,moveWorkspaceEntry as M,previewWorkspaceDocument as O,readWorkspaceFile as H,renameWorkspaceEntry as U,replaceWorkspaceFiles as G,resolveWorkspaceDownloadTarget as X,searchWorkspaceFiles as Y,searchWorkspacePaths as q,uploadWorkspaceFiles as J,writeWorkspaceFile as $,zipWorkspaceDirectoryToTemp as K}from"../services/workspace-files.js";import{unwatchWorkspaceFile as V,watchWorkspaceFile as Z}from"../services/workspace-watch.js";import{createLogger as Q}from"../utils/logger.js";const h=Q("file-browser"),p=R(),m=new Map,tt=6e4,b=2e3,et=Number.POSITIVE_INFINITY,rt=j({dest:w.join(T.tmpdir(),"agentswork-runtime-bridge-uploads"),limits:{files:b,fileSize:et}}),f=Number.MAX_SAFE_INTEGER,g=Number.MAX_SAFE_INTEGER;function P(o){return o===!0||o==="true"||o==="1"}function ot(o){return`attachment; filename="${o.replace(/[\\"\r\n]/g,"_").replace(/[^\x20-\x7E]/g,"_")}"; filename*=UTF-8''${encodeURIComponent(o)}`}async function at(o=[]){await Promise.all(o.map(e=>u.rm(e.path,{force:!0}).catch(()=>{})))}function st(o){return{status:400,body:{error:o.code==="LIMIT_FILE_COUNT"?`\u4E00\u6B21\u6700\u591A\u4E0A\u4F20 ${b} \u4E2A\u6587\u4EF6\uFF0C\u8BF7\u62C6\u5206\u6587\u4EF6\u5939\u540E\u91CD\u8BD5\u3002`:o.message,code:o.code}}}function nt(o){const e=o.relativePathsJson;if(typeof e=="string"&&e.trim()){const t=JSON.parse(e);if(!Array.isArray(t))throw new Error("relativePathsJson must be an array");return t.map(a=>String(a||""))}const r=o.relativePaths;return Array.isArray(r)?r.map(t=>String(t||"")):typeof r=="string"?[r]:[]}function it(o,e,r){rt.array("files")(o,e,t=>{if(!t){r();return}if(t instanceof j.MulterError){const a=st(t);e.status(a.status).json(a.body);return}r(t)})}async function k(o=""){const r=process.platform==="win32";if(!o||o===""){if(r){const d=Array.from({length:24},(y,_)=>String.fromCharCode(67+_)+":\\");return{isWindows:!0,currentPath:"",items:(await Promise.all(d.map(async y=>{try{return await u.access(y),{name:y,path:y,isDirectory:!0}}catch{return null}}))).filter(y=>y!==null)}}return{isWindows:!1,currentPath:"/",items:(await u.readdir("/",{withFileTypes:!0})).filter(d=>d.isDirectory()).map(d=>({name:d.name,path:"/"+d.name,isDirectory:!0}))}}const t=w.normalize(o);let a;try{a=await u.stat(t)}catch{throw new Error("Path not found: "+t)}if(!a.isDirectory())throw new Error("Path is not a directory: "+t);const i=(await u.readdir(t,{withFileTypes:!0})).filter(n=>n.isDirectory()).map(n=>({name:n.name,path:w.join(t,n.name),isDirectory:!0}));return{isWindows:r,currentPath:t,items:i}}async function ct(o=""){const e=await k(o),r=e.currentPath?await N(e.currentPath):[];return e.currentPath?{...e,maxDepth:f,maxItemsPerDirectory:g,tree:await D(e.currentPath,e.currentPath,1,r)}:{...e,maxDepth:f,maxItemsPerDirectory:g,tree:e.items.slice(0,g)}}async function D(o,e,r,t){if(r>f)return[];const s=(await u.readdir(e,{withFileTypes:!0})).filter(n=>n.isDirectory()).filter(n=>!pt(o,w.join(e,n.name),t)).sort((n,l)=>n.name.localeCompare(l.name)).slice(0,g),i=[];for(const n of s){const l=w.join(e,n.name),d={name:n.name,path:l.replace(/\\/g,"/"),isDirectory:!0};if(r<f)try{d.children=await D(o,l,r+1,t)}catch{d.children=[]}else d.truncated=!0;i.push(d)}return i}import{isIgnoredByGitIgnore as Tt,loadGitIgnoreRules as Wt,parseGitIgnoreRules as Rt}from"../services/gitignore.js";function pt(o,e,r){if(r.length===0)return!1;const t=w.relative(o,e).replace(/\\/g,"/");return A(t,!0,r)}async function E(o){const e=w.normalize(String(o||""));if(!(await u.stat(e)).isDirectory())throw new Error("Path is not a directory: "+e);return e}function I(o){const e=String(o||"").trim();if(!e)throw new Error("Folder name is required");if(e.includes("/")||e.includes("\\")||e==="."||e==="..")throw new Error("Folder name must not contain path separators");return e}async function lt(o,e){const r=await E(o),t=I(e),a=w.join(r,t);return await u.mkdir(a),{ok:!0,parentPath:o,targetPath:a.replace(/\\/g,"/"),entryName:t}}async function dt(o,e){const r=await E(o),t=I(e),a=w.join(w.dirname(r),t);return await u.rename(r,a),{ok:!0,sourcePath:r.replace(/\\/g,"/"),targetPath:a.replace(/\\/g,"/"),newName:t}}p.post("/list",c,async(o,e)=>{try{if(!v)return e.status(403).json({error:"Host filesystem browsing is disabled. Use /api/file-browser/workspace/list with a registered workspace, or set AWS_ALLOW_HOST_FILE_BROWSER=true for isolated local debugging."});const{path:r=""}=o.body||{};e.json(await k(String(r||"")))}catch(r){const t=r;e.status(500).json({error:t.message})}}),p.post("/directory-picker/list",c,async(o,e)=>{try{const{path:r=""}=o.body||{};e.json(await k(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/directory-picker/tree",c,async(o,e)=>{try{const{path:r=""}=o.body||{};e.json(await ct(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/workspace/list",c,async(o,e)=>{try{const{workspacePath:r,path:t=""}=o.body||{},a=await L({workspacePath:String(r||""),targetPath:String(t||"")});e.json({isWindows:process.platform==="win32",currentPath:a.currentPath,workspacePath:a.workspacePath,items:a.items})}catch(r){const t=r;console.error("[file-browser] failed to list workspace directory:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/read",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{},a=await H({workspacePath:String(r||""),filePath:String(t||"")});e.json(a)}catch(r){const t=r;console.error("[file-browser] failed to read workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/properties",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t}=o.body||{};e.json(await z({workspacePath:String(r||""),targetPath:String(t||"")}))}catch(r){const t=r;h.error("failed to get workspace entry properties:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/chmod",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,mode:a,recursive:s}=o.body||{};e.json(await C({workspacePath:String(r||""),targetPath:String(t||""),mode:String(a||""),recursive:!!s}))}catch(r){const t=r;h.error("failed to chmod workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/create",c,async(o,e)=>{try{const{path:r="",entryName:t}=o.body||{};e.json(await lt(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to create directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/rename",c,async(o,e)=>{try{const{targetPath:r,newName:t}=o.body||{};e.json(await dt(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to rename directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/preview",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{},a=await O({workspacePath:String(r||""),filePath:String(t||"")});e.json(a)}catch(r){const t=r;h.error("failed to preview workspace document:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/watch",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{};e.json(await Z({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to watch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/unwatch",c,async(o,e)=>{try{const{workspacePath:r,filePath:t}=o.body||{};e.json(await V({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to unwatch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/write",c,async(o,e)=>{try{const{workspacePath:r,filePath:t,content:a}=o.body||{},s=await $({workspacePath:String(r||""),filePath:String(t||""),content:String(a??"")});e.json(s)}catch(r){const t=r;console.error("[file-browser] failed to write workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/create",c,async(o,e)=>{try{const{workspacePath:r,path:t="",entryName:a,isDirectory:s}=o.body||{},i=await x({workspacePath:String(r||""),targetPath:String(t||""),entryName:String(a||""),isDirectory:!!s});e.json(i)}catch(r){const t=r;h.error("failed to create workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/rename",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,newName:a}=o.body||{},s=await U({workspacePath:String(r||""),targetPath:String(t||""),newName:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to rename workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/move",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t,destinationPath:a}=o.body||{},s=await M({workspacePath:String(r||""),targetPath:String(t||""),destinationPath:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to move workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/delete",c,async(o,e)=>{try{const{workspacePath:r,targetPath:t}=o.body||{},a=await F({workspacePath:String(r||""),targetPath:String(t||"")});e.json(a)}catch(r){const t=r;h.error("failed to delete workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/upload",c,it,async(o,e)=>{const r=o.files||[];try{const{workspacePath:t,path:a="",extractArchives:s}=o.body||{},i=nt(o.body||{}),n=await J({workspacePath:String(t||""),targetPath:String(a||""),extractArchives:P(s),files:r.map((l,d)=>({originalname:l.originalname,relativePath:i[d],path:l.path,size:l.size}))});e.json(n)}catch(t){const a=t;h.error("failed to upload workspace files:",a),e.status(400).json({error:a.message})}finally{await at(r)}}),p.post("/workspace/download",c,async(o,e)=>{let r;const t=String((o.body||{}).progressId||"").trim();try{const{workspacePath:a,targetPath:s}=o.body||{},i=await X({workspacePath:String(a||""),targetPath:String(s||"")});if(e.setHeader("Content-Type",i.contentType),e.setHeader("Content-Disposition",ot(i.fileName)),e.setHeader("X-Workspace-Path",encodeURIComponent(i.workspacePath)),e.setHeader("X-Workspace-Target-Path",encodeURIComponent(i.targetPath)),i.isDirectory){t&&m.set(t,{zippedBytes:0,totalBytes:null,done:!1,cancelled:!1,updatedAt:Date.now()}),r=await K(i.resolvedTargetPath,i.fileName,l=>{if(!t)return;const d=m.get(t);d&&(d.zippedBytes=l,d.updatedAt=Date.now())},()=>t?!!m.get(t)?.cancelled:!1);const n=await u.stat(r);if(t){const l=m.get(t);l&&(l.zippedBytes=n.size,l.totalBytes=n.size,l.done=!0,l.updatedAt=Date.now())}e.setHeader("Content-Length",String(n.size)),await W(S(r),e);return}i.size!==void 0&&e.setHeader("Content-Length",String(i.size)),S(i.resolvedTargetPath).pipe(e)}catch(a){const s=a;h.error("failed to download workspace entry:",s),e.headersSent?e.destroy(s):e.status(400).json({error:s.message})}finally{if(r&&await u.rm(r,{force:!0}).catch(()=>{}),t){const a=m.get(t);a&&(a.done=!0,a.totalBytes===null&&(a.totalBytes=0)),setTimeout(()=>m.delete(t),tt)}}}),p.post("/workspace/download-progress",c,(o,e)=>{const r=String((o.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=m.get(r);if(!t){e.status(404).json({error:"download progress not found"});return}e.json({zippedBytes:t.zippedBytes,totalBytes:t.totalBytes,done:t.done,cancelled:t.cancelled})}),p.post("/workspace/download-cancel",c,(o,e)=>{const r=String((o.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=m.get(r);t&&(t.cancelled=!0,t.done=!0,t.updatedAt=Date.now()),e.json({ok:!0})}),p.post("/workspace/extract",c,async(o,e)=>{try{const{workspacePath:r,archivePath:t,outputPath:a=""}=o.body||{},s=await B({workspacePath:String(r||""),archivePath:String(t||""),outputPath:String(a||"")});e.json(s)}catch(r){const t=r;h.error("failed to extract workspace archive:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/path-search",c,async(o,e)=>{try{const{workspacePath:r,rootPath:t,keyword:a,ignoreCase:s,maxResults:i,maxDirectories:n}=o.body||{},l=await q({workspacePath:String(r||""),rootPath:t!==void 0?String(t):void 0,keyword:String(a||""),ignoreCase:P(s),maxResults:typeof i=="number"?i:void 0,maxDirectories:typeof n=="number"?n:void 0});e.json(l)}catch(r){const t=r;h.error("failed to search workspace paths:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/search",c,async(o,e)=>{try{const{workspacePath:r,rootPath:t,keyword:a,fileMask:s,ignoreCase:i,maxResults:n,mode:l}=o.body||{},d=await Y({workspacePath:String(r||""),rootPath:t!=null&&String(t).trim()?String(t):void 0,keyword:String(a||""),fileMask:s!==void 0?String(s):void 0,ignoreCase:P(i),maxResults:typeof n=="number"?n:void 0,mode:l==="filename"?"filename":"content"});e.json(d)}catch(r){const t=r;h.error("failed to search workspace files:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/replace",c,async(o,e)=>{try{const{workspacePath:r,rootPath:t,keyword:a,replacement:s,fileMask:i,ignoreCase:n,filePath:l}=o.body||{},d=await G({workspacePath:String(r||""),rootPath:t!=null&&String(t).trim()?String(t):void 0,keyword:String(a||""),replacement:String(s??""),fileMask:i!==void 0?String(i):void 0,ignoreCase:P(n),filePath:l!==void 0?String(l):void 0});e.json(d)}catch(r){const t=r;h.error("failed to replace workspace files:",t),e.status(400).json({error:t.message})}});export{b as WORKSPACE_UPLOAD_FILE_LIMIT,et as WORKSPACE_UPLOAD_MAX_FILE_SIZE_BYTES,st as createWorkspaceUploadLimitResponse,p as fileBrowserRouter,Tt as isIgnoredByGitIgnore,ct as listHostDirectoryTree,Wt as loadGitIgnoreRules,Rt as parseGitIgnoreRules,nt as parseWorkspaceUploadRelativePaths};
2
2
 
@@ -199,6 +199,25 @@ export declare class LaunchManager {
199
199
  private restored;
200
200
 
201
201
  private readonly portsProbeInflight;
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+ private readonly knownPorts;
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+ private readonly portReprobeTimers;
218
+
219
+ private readonly portReprobeTokens;
220
+ private portReprobeSeq;
202
221
  private constructor();
203
222
  static getInstance(): LaunchManager;
204
223
 
@@ -277,6 +296,7 @@ export declare class LaunchManager {
277
296
 
278
297
 
279
298
 
299
+
280
300
  listRuns(options?: {
281
301
  withPorts?: boolean;
282
302
  }): Promise<LaunchRunSummary[]>;
@@ -293,6 +313,7 @@ export declare class LaunchManager {
293
313
 
294
314
 
295
315
 
316
+
296
317
  publishRunningPorts(): Promise<void>;
297
318
 
298
319
 
@@ -327,8 +348,20 @@ export declare class LaunchManager {
327
348
 
328
349
 
329
350
 
351
+
352
+
353
+
330
354
  private publishRunStatus;
331
355
 
356
+ private buildStatusFrame;
357
+
358
+
359
+
360
+
361
+ private schedulePortReprobe;
362
+
363
+ private clearPortReprobe;
364
+
332
365
 
333
366
 
334
367
  private persist;
@@ -1,12 +1,12 @@
1
- import{exec as _,spawn as z}from"node:child_process";import{promises as F}from"node:fs";import A from"node:os";import M from"node:path";import{promisify as J}from"node:util";import{atomicWriteJsonFile as U,withFileLock as G}from"../utils/file-utils.js";import{createLogger as V}from"../utils/logger.js";import{LaunchMetricsMonitor as X}from"./launch-metrics.js";import{getLaunchStreamHub as P}from"./launch-streams.js";import{getListeningPortsForProcessTrees as k}from"./port-detector.js";import{isProcessRunning as L,terminateProcessTree as E}from"./process-detector.js";import{RingBuffer as O}from"./ring-buffer.js";const C=J(_),m=V("launch-manager"),D=5e3,B=4096,K=1500,Y=20;class H extends Error{constructor(t){super(`\u542F\u52A8\u9879\u5DF2\u5728\u8FD0\u884C: pid=${t.pid}`),this.name="LaunchAlreadyRunningError",this.existing=t}}function N(){return M.join(A.tmpdir(),"agentswork-runtime-bridge","launch-runs.json")}function Z(l,t){return l.replace(/\$\{([^}]+)\}/g,(e,s)=>{const r=s.trim(),i=r.toUpperCase();if(i==="WORKSPACE")return t.workspacePath||"";if(i==="JAVA_HOME")return t.jdkPath||process.env.JAVA_HOME||"";if(i==="HOME")return A.homedir();if(t.env&&t.env[r]!==void 0)return t.env[r];if(t.env){for(const[n,a]of Object.entries(t.env))if(n.toUpperCase()===i)return a}return e})}function Q(l){const t={...process.env};l.env&&Object.assign(t,l.env);for(const e of Object.keys(t))if(e.toUpperCase()==="PATH"&&e!=="PATH"){const s=t[e];delete t[e],t.PATH===void 0&&typeof s=="string"&&(t.PATH=s)}if(l.jdkPath){t.JAVA_HOME=l.jdkPath;const e=M.join(l.jdkPath,"bin"),s=M.delimiter;t.PATH=t.PATH?`${e}${s}${t.PATH}`:e}return t}const q=256,tt=120;class W{constructor(t=()=>{}){this.utf8Probe=new TextDecoder("utf-8",{fatal:!0}),this.utf8=new TextDecoder("utf-8"),this.gbk=new TextDecoder("gbk"),this.mode=null,this.buffered=[],this.bufferedSize=0,this.flushTimer=null,this.onFlush=t}decode(t){return this.mode!==null?(this.mode==="gbk"?this.gbk:this.utf8).decode(t,{stream:!0}):(this.buffered.push(t),this.bufferedSize+=t.length,this.bufferedSize>=q?this.commit():(this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.onFlush(this.commit())},tt)),""))}finalize(){return this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode===null?this.commit():(this.mode==="gbk"?this.gbk:this.utf8).decode()}commit(){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode!==null)return"";const t=Buffer.concat(this.buffered,this.bufferedSize);this.buffered=[],this.bufferedSize=0;try{return this.utf8Probe.decode(t),this.mode="utf8",this.utf8.decode(t)}catch{return this.mode="gbk",this.gbk.decode(t)}}}function et(l){const t=l.length>0&&!l.endsWith(`
2
- `)?l+`
3
- `:l;return t.length>B?t.slice(-B):t}async function st(l){const t=A.platform();for(let e=0;e<6;e+=1){try{if(t==="win32"){const s=`Get-CimInstance Win32_Process -Filter "ParentProcessId = ${l} AND Name <> 'conhost.exe'" | Select-Object -First 1 ProcessId | ConvertTo-Json -Compress`,r=Buffer.from(s,"utf16le").toString("base64"),{stdout:i}=await C(`powershell -NoProfile -NonInteractive -EncodedCommand ${r}`,{encoding:"utf8",timeout:5e3,windowsHide:!0}),n=i.replace(/#\s*<\s*CLIXML[\s\S]*?(?=\{|\[|$)/g,"").trim(),a=JSON.parse(n||"null"),h=Number(a?.ProcessId);if(Number.isFinite(h)&&h>0)return h}else{const{stdout:s}=await C(`pgrep -P ${l} | head -n 1`,{encoding:"utf8",timeout:3e3}),r=Number(s.trim());if(Number.isFinite(r)&&r>0)return r}}catch{}await new Promise(s=>setTimeout(s,300))}return null}class S{constructor(){this.runs=new Map,this.logs=new Map,this.startupWaiters=new Map,this.readinessMatchers=new Map,this.logListeners=new Map,this.metricsMonitor=new X(()=>this.runningRefs()),this.restored=!1,this.portsProbeInflight=new Map}static getInstance(){return S.instance===null&&(S.instance=new S),S.instance}runningRefs(){const t=[];for(const e of this.runs.values())e.state==="running"&&e.pid>0&&t.push({configId:e.configId,pid:e.pid});return t}async start(t){const e=t.configId;if(!e||!e.trim())throw new Error("configId is required");if(!t.command||!t.command.trim())throw new Error("command is required");let s=null;if(t.readinessPattern&&t.readinessPattern.trim())try{s=new RegExp(t.readinessPattern.trim())}catch(o){throw new Error(`readinessPattern \u6B63\u5219\u8BED\u6CD5\u65E0\u6548: ${o.message}`)}const r=t.startedBy?.trim()||void 0;await this.ensureRestored();const i=this.runs.get(e);if(i&&i.state==="running"&&await L(i.pid))throw new H(i);const n=Z(t.command,{workspacePath:t.workspacePath,jdkPath:t.jdkPath,env:t.env}),a=t.cwd||t.workspacePath||process.cwd(),h=Q(t);m.info(`[start] \u542F\u52A8\u547D\u4EE4: configId=${e}, cwd=${a}, command=${n}`);let g;try{g=z(n,{cwd:a,env:h,shell:!0,windowsHide:!0})}catch(o){const c=`\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${o.message}`;throw m.error(`[start] spawn \u5931\u8D25: configId=${e}, error=${c}`),new Error(c)}const w=g.pid??0,T={configId:e,pid:w,state:"running",startedAt:new Date().toISOString(),command:n,cwd:a,...r?{startedBy:r}:{}};this.runs.set(e,T),this.publishRunStatus(e,!0),this.metricsMonitor.clear(e),this.metricsMonitor.ensureRunning();let p=this.logs.get(e);p||(p=new O(D),this.logs.set(e,p));const I=p.getSinceIndex(-1).nextIndex;s?this.readinessMatchers.set(e,{pattern:s,fromIndex:I+1}):this.readinessMatchers.delete(e);const b=p.append({stream:"stdout",text:`> ${n}
4
- `});P().publish(e,"logs",b.index,b.data);let R=null;(async()=>{const o=await st(w),c=this.runs.get(e);o&&c?.pid===w&&(R=o,this.runs.set(e,{...c,pid:o}),await this.persist(),this.publishRunStatus(e))})();const y=o=>o!==void 0&&(o.pid===w||o.pid===R),u=new W(o=>{f("stdout",o)}),d=new W(o=>{f("stderr",o)}),f=(o,c)=>{const x=et(c);if(x){const $=p.append({stream:o,text:x});P().publish(e,"logs",$.index,$.data);const v=this.logListeners.get(e);if(v&&v.size>0)for(const j of v)try{j($)}catch{}}};return g.stdout.on("data",o=>{f("stdout",u.decode(o))}),g.stderr.on("data",o=>{f("stderr",d.decode(o))}),g.on("error",o=>{f("stderr",`[\u542F\u52A8\u5931\u8D25] ${o.message}
5
- `);const c=this.runs.get(e);c&&y(c)&&c.state==="running"&&(this.runs.set(e,{...c,state:"exited",exitedAt:new Date().toISOString(),exitCode:-1}),P().publishState(e,{configId:e,state:"exited",exitCode:-1}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",-1))}),g.on("exit",(o,c)=>{f("stdout",u.finalize()),f("stderr",d.finalize()),f("stderr",`
1
+ import{exec as H,spawn as j}from"node:child_process";import{promises as z}from"node:fs";import A from"node:os";import $ from"node:path";import{promisify as F}from"node:util";import{atomicWriteJsonFile as J,withFileLock as U}from"../utils/file-utils.js";import{createLogger as V}from"../utils/logger.js";import{LaunchMetricsMonitor as G}from"./launch-metrics.js";import{getLaunchStreamHub as w}from"./launch-streams.js";import{getListeningPortsForProcessTrees as X}from"./port-detector.js";import{isProcessRunning as E,terminateProcessTree as v}from"./process-detector.js";import{RingBuffer as L}from"./ring-buffer.js";const O=F(H),P=V("launch-manager"),_=5e3,D=4096,Y=1500,K=2e3,Z=5e3,Q=12e4,q=20;class C extends Error{constructor(t){super(`\u542F\u52A8\u9879\u5DF2\u5728\u8FD0\u884C: pid=${t.pid}`),this.name="LaunchAlreadyRunningError",this.existing=t}}function B(){return $.join(A.tmpdir(),"agentswork-runtime-bridge","launch-runs.json")}function tt(p,t){return p.replace(/\$\{([^}]+)\}/g,(e,s)=>{const r=s.trim(),n=r.toUpperCase();if(n==="WORKSPACE")return t.workspacePath||"";if(n==="JAVA_HOME")return t.jdkPath||process.env.JAVA_HOME||"";if(n==="HOME")return A.homedir();if(t.env&&t.env[r]!==void 0)return t.env[r];if(t.env){for(const[i,a]of Object.entries(t.env))if(i.toUpperCase()===n)return a}return e})}function et(p){const t={...process.env};p.env&&Object.assign(t,p.env);for(const e of Object.keys(t))if(e.toUpperCase()==="PATH"&&e!=="PATH"){const s=t[e];delete t[e],t.PATH===void 0&&typeof s=="string"&&(t.PATH=s)}if(p.jdkPath){t.JAVA_HOME=p.jdkPath;const e=$.join(p.jdkPath,"bin"),s=$.delimiter;t.PATH=t.PATH?`${e}${s}${t.PATH}`:e}return t}const st=256,rt=120;class N{constructor(t=()=>{}){this.utf8Probe=new TextDecoder("utf-8",{fatal:!0}),this.utf8=new TextDecoder("utf-8"),this.gbk=new TextDecoder("gbk"),this.mode=null,this.buffered=[],this.bufferedSize=0,this.flushTimer=null,this.onFlush=t}decode(t){return this.mode!==null?(this.mode==="gbk"?this.gbk:this.utf8).decode(t,{stream:!0}):(this.buffered.push(t),this.bufferedSize+=t.length,this.bufferedSize>=st?this.commit():(this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.onFlush(this.commit())},rt)),""))}finalize(){return this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode===null?this.commit():(this.mode==="gbk"?this.gbk:this.utf8).decode()}commit(){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode!==null)return"";const t=Buffer.concat(this.buffered,this.bufferedSize);this.buffered=[],this.bufferedSize=0;try{return this.utf8Probe.decode(t),this.mode="utf8",this.utf8.decode(t)}catch{return this.mode="gbk",this.gbk.decode(t)}}}function nt(p){const t=p.length>0&&!p.endsWith(`
2
+ `)?p+`
3
+ `:p;return t.length>D?t.slice(-D):t}async function it(p){const t=A.platform();for(let e=0;e<6;e+=1){try{if(t==="win32"){const s=`Get-CimInstance Win32_Process -Filter "ParentProcessId = ${p} AND Name <> 'conhost.exe'" | Select-Object -First 1 ProcessId | ConvertTo-Json -Compress`,r=Buffer.from(s,"utf16le").toString("base64"),{stdout:n}=await O(`powershell -NoProfile -NonInteractive -EncodedCommand ${r}`,{encoding:"utf8",timeout:5e3,windowsHide:!0}),i=n.replace(/#\s*<\s*CLIXML[\s\S]*?(?=\{|\[|$)/g,"").trim(),a=JSON.parse(i||"null"),u=Number(a?.ProcessId);if(Number.isFinite(u)&&u>0)return u}else{const{stdout:s}=await O(`pgrep -P ${p} | head -n 1`,{encoding:"utf8",timeout:3e3}),r=Number(s.trim());if(Number.isFinite(r)&&r>0)return r}}catch{}await new Promise(s=>setTimeout(s,300))}return null}class y{constructor(){this.runs=new Map,this.logs=new Map,this.startupWaiters=new Map,this.readinessMatchers=new Map,this.logListeners=new Map,this.metricsMonitor=new G(()=>this.runningRefs()),this.restored=!1,this.portsProbeInflight=new Map,this.knownPorts=new Map,this.portReprobeTimers=new Map,this.portReprobeTokens=new Map,this.portReprobeSeq=0}static getInstance(){return y.instance===null&&(y.instance=new y),y.instance}runningRefs(){const t=[];for(const e of this.runs.values())e.state==="running"&&e.pid>0&&t.push({configId:e.configId,pid:e.pid});return t}async start(t){const e=t.configId;if(!e||!e.trim())throw new Error("configId is required");if(!t.command||!t.command.trim())throw new Error("command is required");let s=null;if(t.readinessPattern&&t.readinessPattern.trim())try{s=new RegExp(t.readinessPattern.trim())}catch(o){throw new Error(`readinessPattern \u6B63\u5219\u8BED\u6CD5\u65E0\u6548: ${o.message}`)}const r=t.startedBy?.trim()||void 0;await this.ensureRestored();const n=this.runs.get(e);if(n&&n.state==="running"&&await E(n.pid))throw new C(n);const i=tt(t.command,{workspacePath:t.workspacePath,jdkPath:t.jdkPath,env:t.env}),a=t.cwd||t.workspacePath||process.cwd(),u=et(t);P.info(`[start] \u542F\u52A8\u547D\u4EE4: configId=${e}, cwd=${a}, command=${i}`);let l;try{l=j(i,{cwd:a,env:u,shell:!0,windowsHide:!0})}catch(o){const c=`\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${o.message}`;throw P.error(`[start] spawn \u5931\u8D25: configId=${e}, error=${c}`),new Error(c)}const f=l.pid??0,b={configId:e,pid:f,state:"running",startedAt:new Date().toISOString(),command:i,cwd:a,...r?{startedBy:r}:{}};this.runs.set(e,b),this.knownPorts.delete(e),this.clearPortReprobe(e),this.publishRunStatus(e,!0),this.metricsMonitor.clear(e),this.metricsMonitor.ensureRunning();let m=this.logs.get(e);m||(m=new L(_),this.logs.set(e,m));const k=m.getSinceIndex(-1).nextIndex;s?this.readinessMatchers.set(e,{pattern:s,fromIndex:k+1}):this.readinessMatchers.delete(e);const R=m.append({stream:"stdout",text:`> ${i}
4
+ `});w().publish(e,"logs",R.index,R.data);let x=null;(async()=>{const o=await it(f),c=this.runs.get(e);o&&c?.pid===f&&(x=o,this.runs.set(e,{...c,pid:o}),await this.persist(),this.publishRunStatus(e))})();const T=o=>o!==void 0&&(o.pid===f||o.pid===x),d=new N(o=>{g("stdout",o)}),h=new N(o=>{g("stderr",o)}),g=(o,c)=>{const S=nt(c);if(S){const I=m.append({stream:o,text:S});w().publish(e,"logs",I.index,I.data);const M=this.logListeners.get(e);if(M&&M.size>0)for(const W of M)try{W(I)}catch{}}};return l.stdout.on("data",o=>{g("stdout",d.decode(o))}),l.stderr.on("data",o=>{g("stderr",h.decode(o))}),l.on("error",o=>{g("stderr",`[\u542F\u52A8\u5931\u8D25] ${o.message}
5
+ `);const c=this.runs.get(e);c&&T(c)&&c.state==="running"&&(this.runs.set(e,{...c,state:"exited",exitedAt:new Date().toISOString(),exitCode:-1}),this.knownPorts.delete(e),this.clearPortReprobe(e),w().publishState(e,{configId:e,state:"exited",exitCode:-1}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",-1))}),l.on("exit",(o,c)=>{g("stdout",d.finalize()),g("stderr",h.finalize()),g("stderr",`
6
6
  [\u8FDB\u7A0B\u9000\u51FA] code=${o??"-"}${c?` signal=${c}`:""}
7
- `);const x=this.runs.get(e);x&&y(x)&&x.state==="running"&&(this.runs.set(e,{...x,state:"exited",exitedAt:new Date().toISOString(),exitCode:o}),P().publishState(e,{configId:e,state:"exited",exitCode:o}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",o))}),await this.persist(),T}async stop(t,e="manual",s){await this.ensureRestored();const r=this.runs.get(t);if(!r||r.state!=="running")return{success:!1,message:"\u542F\u52A8\u9879\u672A\u5728\u8FD0\u884C",pid:r?.pid};const i=s?.trim()||void 0;m.info(`[stop] \u7EC8\u6B62\u8FDB\u7A0B\u6811: configId=${t}, pid=${r.pid}, reason=${e}${i?`, actor=${i}`:""}`),this.runs.set(t,{...r,state:"stopped",exitedAt:new Date().toISOString(),...i?{stoppedBy:i}:{}});const n=await E(r.pid),a=e==="shutdown"?`
7
+ `);const S=this.runs.get(e);S&&T(S)&&S.state==="running"&&(this.runs.set(e,{...S,state:"exited",exitedAt:new Date().toISOString(),exitCode:o}),this.knownPorts.delete(e),this.clearPortReprobe(e),w().publishState(e,{configId:e,state:"exited",exitCode:o}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",o))}),await this.persist(),b}async stop(t,e="manual",s){await this.ensureRestored();const r=this.runs.get(t);if(!r||r.state!=="running")return{success:!1,message:"\u542F\u52A8\u9879\u672A\u5728\u8FD0\u884C",pid:r?.pid};const n=s?.trim()||void 0;P.info(`[stop] \u7EC8\u6B62\u8FDB\u7A0B\u6811: configId=${t}, pid=${r.pid}, reason=${e}${n?`, actor=${n}`:""}`),this.runs.set(t,{...r,state:"stopped",exitedAt:new Date().toISOString(),...n?{stoppedBy:n}:{}}),this.knownPorts.delete(t),this.clearPortReprobe(t);const i=await v(r.pid),a=e==="shutdown"?`
8
8
  [bridge \u505C\u6B62\uFF0C\u8FDB\u7A0B\u5DF2\u8054\u52A8\u7EC8\u6B62]
9
9
  `:`
10
10
  [\u5DF2\u624B\u52A8\u505C\u6B62]
11
- `,h=this.logs.get(t)?.append({stream:"stderr",text:a});return h&&P().publish(t,"logs",h.index,h.data),P().publishState(t,{configId:t,state:"stopped"}),this.publishRunStatus(t),await this.persist(),this.resolveStartupWaiters(t,"stopped",void 0),{success:n.failed===0,message:n.failed===0?"\u8FDB\u7A0B\u5DF2\u505C\u6B62":`\u90E8\u5206\u8FDB\u7A0B\u672A\u80FD\u7EC8\u6B62\uFF08terminated=${n.terminated}, failed=${n.failed}\uFF09`,pid:r.pid}}async restart(t){await this.ensureRestored();const e=this.runs.get(t.configId);return e&&e.state==="running"&&await this.stop(t.configId,"manual",t.startedBy),await this.start(t)}async startBatch(t,e){await this.ensureRestored();const s=await Promise.all(t.map(async i=>{try{const n=await this.start(i);return{configId:i.configId,ok:!0,run:n}}catch(n){return n instanceof H?{configId:i.configId,ok:!0,skipped:!0,message:"\u5F53\u524D\u5DF2\u5904\u4E8E\u542F\u52A8/\u6B63\u5728\u542F\u52A8\u72B6\u6001\uFF0C\u8DF3\u8FC7",run:n.existing}:{configId:i.configId,ok:!1,error:n.message}}}));if(e>0){const i=await Promise.all(s.map(async n=>{if(!n.ok||n.skipped)return n;try{const a=await this.waitStartupResult(n.configId,e);return{...n,wait:a}}catch(a){return{...n,error:a.message}}}));s.splice(0,s.length,...i)}const r={total:s.length,started:0,skipped:0,failed:0,ready:0,exited:0,stopped:0,timeout:0};for(const i of s){if(!i.ok){r.failed+=1;continue}if(i.skipped){r.skipped+=1;continue}r.started+=1,i.wait&&(r[i.wait.outcome]+=1)}return{items:s,summary:r}}async stopAll(){await this.ensureRestored();const t=Array.from(this.runs.values()).filter(s=>s.state==="running");let e=0;for(const s of t)try{const r=await this.stop(s.configId,"shutdown");r.success?e++:m.warn(`[stopAll] \u542F\u52A8\u9879\u672A\u80FD\u5B8C\u5168\u7EC8\u6B62: configId=${s.configId}, message=${r.message}`)}catch(r){m.warn(`[stopAll] \u505C\u6B62\u542F\u52A8\u9879\u5931\u8D25: configId=${s.configId}, error=${r.message}`)}return{running:t.length,stopped:e}}async waitStartupResult(t,e){const s=this.runs.get(t);if(!s)throw new Error("launch run not found");const r=Date.now(),i=()=>(this.logs.get(t)?.getLastN(Y)??[]).map(a=>a.data);if(s.state!=="running")return{outcome:s.state==="stopped"?"stopped":"exited",waitedMs:0,...s.state==="exited"?{exitCode:s.exitCode??null}:{},logTail:i()};const n=this.readinessMatchers.get(t);if(n){const a=this.scanReadinessMatch(t,n);if(a!==null)return{outcome:"ready",waitedMs:0,matchedLine:a,logTail:i()}}return await new Promise(a=>{let h=!1,g=null,w=null,T=!1,p=null;const I=()=>{g!==null&&(clearInterval(g),g=null),w!==null&&(clearTimeout(w),w=null);const u=this.startupWaiters.get(t);if(u&&(u.delete(R),u.size===0&&this.startupWaiters.delete(t)),p!==null){const d=this.logListeners.get(t);d&&(d.delete(p),d.size===0&&this.logListeners.delete(t)),p=null}},b=u=>{h||(h=!0,I(),a(u))},R=(u,d)=>{b({outcome:u==="stopped"?"stopped":"exited",waitedMs:Date.now()-r,...u==="exited"?{exitCode:d??null}:{},logTail:i()})};let y=this.startupWaiters.get(t);if(y||(y=new Set,this.startupWaiters.set(t,y)),y.add(R),n){p=d=>{if(!(h||d.index<n.fromIndex)){for(const f of d.data.text.split(/\r?\n/))if(f&&n.pattern.test(f)){b({outcome:"ready",waitedMs:Date.now()-r,matchedLine:f.trim(),logTail:i()});return}}};let u=this.logListeners.get(t);u||(u=new Set,this.logListeners.set(t,u)),u.add(p)}else{const u=async()=>{if(h||T)return;const d=this.runs.get(t);if(!(!d||d.state!=="running"||d.pid<=0)){T=!0;try{const o=(await k([d.pid])).get(d.pid)??[];if(o.length>0&&!h){const c=this.runs.get(t);c?.state==="running"&&c.pid===d.pid&&b({outcome:"ready",waitedMs:Date.now()-r,ports:o,logTail:i()})}}catch{}finally{T=!1}}};g=setInterval(()=>{u()},K),u()}w=setTimeout(()=>{b({outcome:"timeout",waitedMs:Date.now()-r,logTail:i()})},e)})}scanReadinessMatch(t,e){const s=this.logs.get(t);if(!s)return null;for(const r of s.getSinceIndex(e.fromIndex-1).entries)for(const i of r.data.text.split(/\r?\n/))if(i&&e.pattern.test(i))return i.trim();return null}resolveStartupWaiters(t,e,s){const r=this.startupWaiters.get(t);if(!(!r||r.size===0)){this.startupWaiters.delete(t);for(const i of r)try{i(e,s)}catch{}}}async listRuns(t){await this.ensureRestored();const s=t?.withPorts??!0?Array.from(this.runs.values()).filter(n=>n.state==="running"&&n.pid>0).map(n=>n.pid):[];let r=new Map;if(s.length>0)try{r=await this.probePorts(s)}catch(n){m.debug(`[listRuns] \u7AEF\u53E3\u63A2\u6D4B\u5931\u8D25: ${n.message}`)}const i=[];for(const n of this.runs.values()){const a=this.logs.get(n.configId);i.push({...n,logTail:a?a.getLastN(20).map(h=>h.data):[],nextLogIndex:a?a.getSinceIndex(-1).nextIndex:0,ports:n.state==="running"?r.get(n.pid)??[]:[],metrics:this.metricsMonitor.getLatest(n.configId)??void 0})}return i}async probePorts(t){const e=[...t].sort((i,n)=>i-n).join(","),s=this.portsProbeInflight.get(e);if(s)return s;const r=k(t).finally(()=>{this.portsProbeInflight.get(e)===r&&this.portsProbeInflight.delete(e)});return this.portsProbeInflight.set(e,r),r}async publishRunningPorts(){const t=this.runningRefs();if(t.length===0)return;let e=new Map;try{e=await this.probePorts(t.map(s=>s.pid))}catch(s){m.debug(`[publishRunningPorts] \u7AEF\u53E3\u63A2\u6D4B\u5931\u8D25: ${s.message}`);return}for(const s of t){const r=this.runs.get(s.configId);!r||r.state!=="running"||r.pid!==s.pid||P().publishStatusRun({...r,ports:e.get(s.pid)??[]})}}async getLogs(t,e){await this.ensureRestored();const s=this.runs.get(t);if(!s)return null;const r=this.logs.get(t);if(!r)return{configId:t,state:s.state,entries:[],nextIndex:0,oldestIndex:0};const{entries:i,nextIndex:n,oldestIndex:a}=r.getSinceIndex(e);return{configId:t,state:s.state,entries:i,nextIndex:n,oldestIndex:a}}getRun(t){return this.runs.get(t)}getMetrics(t,e){return this.metricsMonitor.getHistory(t,e)}getMetricsSince(t,e,s){return this.metricsMonitor.getHistoryEntriesSince(t,e,s)}async ensureRestored(){if(!this.restored){this.restored=!0;try{const t=await F.readFile(N(),"utf-8"),e=JSON.parse(t);for(const s of e){const r=s.pid>0&&await L(s.pid);if(r){m.warn(`[restore] \u68C0\u6D4B\u5230\u4E0A\u6B21 bridge \u975E\u4F18\u96C5\u9000\u51FA\u540E\u7684\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u8054\u52A8\u7EC8\u6B62: configId=${s.configId}, pid=${s.pid}`);try{await E(s.pid)}catch(i){m.warn(`[restore] \u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u5931\u8D25: configId=${s.configId}, pid=${s.pid}, error=${i.message}`)}}this.runs.set(s.configId,{...s,state:"exited",...r?{exitedAt:new Date().toISOString()}:{}}),this.logs.set(s.configId,new O(D))}m.info(`[restore] \u5DF2\u6062\u590D ${e.length} \u6761\u542F\u52A8\u9879\u8FD0\u884C\u8BB0\u5F55`)}catch{}}}publishRunStatus(t,e=!1){const s=this.runs.get(t);s&&(P().publishStatusRun({...s}),!(!e||s.state!=="running"||s.pid<=0)&&setTimeout(()=>{const r=this.runs.get(t);!r||r.state!=="running"||r.pid<=0||(async()=>{let i=[];try{i=(await this.probePorts([r.pid])).get(r.pid)??[]}catch{}const n=this.runs.get(t);n&&n.state==="running"&&n.pid===r.pid&&P().publishStatusRun({...n,ports:i})})()},2e3))}async persist(){const t=N();try{const e=Array.from(this.runs.values());await G(t,async()=>{await U(t,e)})}catch(e){m.warn(`[persist] \u6301\u4E45\u5316\u5931\u8D25: ${e.message}`)}}}S.instance=null;function gt(){return S.getInstance()}export{H as LaunchAlreadyRunningError,S as LaunchManager,W as StreamDecoder,gt as getLaunchManager,Z as resolvePlaceholders};
11
+ `,u=this.logs.get(t)?.append({stream:"stderr",text:a});return u&&w().publish(t,"logs",u.index,u.data),w().publishState(t,{configId:t,state:"stopped"}),this.publishRunStatus(t),await this.persist(),this.resolveStartupWaiters(t,"stopped",void 0),{success:i.failed===0,message:i.failed===0?"\u8FDB\u7A0B\u5DF2\u505C\u6B62":`\u90E8\u5206\u8FDB\u7A0B\u672A\u80FD\u7EC8\u6B62\uFF08terminated=${i.terminated}, failed=${i.failed}\uFF09`,pid:r.pid}}async restart(t){await this.ensureRestored();const e=this.runs.get(t.configId);return e&&e.state==="running"&&await this.stop(t.configId,"manual",t.startedBy),await this.start(t)}async startBatch(t,e){await this.ensureRestored();const s=await Promise.all(t.map(async n=>{try{const i=await this.start(n);return{configId:n.configId,ok:!0,run:i}}catch(i){return i instanceof C?{configId:n.configId,ok:!0,skipped:!0,message:"\u5F53\u524D\u5DF2\u5904\u4E8E\u542F\u52A8/\u6B63\u5728\u542F\u52A8\u72B6\u6001\uFF0C\u8DF3\u8FC7",run:i.existing}:{configId:n.configId,ok:!1,error:i.message}}}));if(e>0){const n=await Promise.all(s.map(async i=>{if(!i.ok||i.skipped)return i;try{const a=await this.waitStartupResult(i.configId,e);return{...i,wait:a}}catch(a){return{...i,error:a.message}}}));s.splice(0,s.length,...n)}const r={total:s.length,started:0,skipped:0,failed:0,ready:0,exited:0,stopped:0,timeout:0};for(const n of s){if(!n.ok){r.failed+=1;continue}if(n.skipped){r.skipped+=1;continue}r.started+=1,n.wait&&(r[n.wait.outcome]+=1)}return{items:s,summary:r}}async stopAll(){await this.ensureRestored();const t=Array.from(this.runs.values()).filter(s=>s.state==="running");let e=0;for(const s of t)try{const r=await this.stop(s.configId,"shutdown");r.success?e++:P.warn(`[stopAll] \u542F\u52A8\u9879\u672A\u80FD\u5B8C\u5168\u7EC8\u6B62: configId=${s.configId}, message=${r.message}`)}catch(r){P.warn(`[stopAll] \u505C\u6B62\u542F\u52A8\u9879\u5931\u8D25: configId=${s.configId}, error=${r.message}`)}return{running:t.length,stopped:e}}async waitStartupResult(t,e){const s=this.runs.get(t);if(!s)throw new Error("launch run not found");const r=Date.now(),n=()=>(this.logs.get(t)?.getLastN(q)??[]).map(a=>a.data);if(s.state!=="running")return{outcome:s.state==="stopped"?"stopped":"exited",waitedMs:0,...s.state==="exited"?{exitCode:s.exitCode??null}:{},logTail:n()};const i=this.readinessMatchers.get(t);if(i){const a=this.scanReadinessMatch(t,i);if(a!==null)return{outcome:"ready",waitedMs:0,matchedLine:a,logTail:n()}}return await new Promise(a=>{let u=!1,l=null,f=null,b=!1,m=null;const k=()=>{l!==null&&(clearInterval(l),l=null),f!==null&&(clearTimeout(f),f=null);const d=this.startupWaiters.get(t);if(d&&(d.delete(x),d.size===0&&this.startupWaiters.delete(t)),m!==null){const h=this.logListeners.get(t);h&&(h.delete(m),h.size===0&&this.logListeners.delete(t)),m=null}},R=d=>{u||(u=!0,k(),a(d))},x=(d,h)=>{R({outcome:d==="stopped"?"stopped":"exited",waitedMs:Date.now()-r,...d==="exited"?{exitCode:h??null}:{},logTail:n()})};let T=this.startupWaiters.get(t);if(T||(T=new Set,this.startupWaiters.set(t,T)),T.add(x),i){m=h=>{if(!(u||h.index<i.fromIndex)){for(const g of h.data.text.split(/\r?\n/))if(g&&i.pattern.test(g)){R({outcome:"ready",waitedMs:Date.now()-r,matchedLine:g.trim(),logTail:n()});return}}};let d=this.logListeners.get(t);d||(d=new Set,this.logListeners.set(t,d)),d.add(m)}else{const d=async()=>{if(u||b)return;const h=this.runs.get(t);if(!(!h||h.state!=="running"||h.pid<=0)){b=!0;try{const o=(await this.probePorts([h.pid])).get(h.pid)??[];if(o.length>0&&!u){const c=this.runs.get(t);c?.state==="running"&&c.pid===h.pid&&(this.knownPorts.set(t,o),w().publishStatusRun({...c,ports:o}),R({outcome:"ready",waitedMs:Date.now()-r,ports:o,logTail:n()}))}}catch{}finally{b=!1}}};l=setInterval(()=>{d()},Y),d()}f=setTimeout(()=>{R({outcome:"timeout",waitedMs:Date.now()-r,logTail:n()})},e)})}scanReadinessMatch(t,e){const s=this.logs.get(t);if(!s)return null;for(const r of s.getSinceIndex(e.fromIndex-1).entries)for(const n of r.data.text.split(/\r?\n/))if(n&&e.pattern.test(n))return n.trim();return null}resolveStartupWaiters(t,e,s){const r=this.startupWaiters.get(t);if(!(!r||r.size===0)){this.startupWaiters.delete(t);for(const n of r)try{n(e,s)}catch{}}}async listRuns(t){await this.ensureRestored();const s=t?.withPorts??!0?Array.from(this.runs.values()).filter(i=>i.state==="running"&&i.pid>0).map(i=>i.pid):[];let r=new Map;if(s.length>0)try{r=await this.probePorts(s)}catch(i){P.debug(`[listRuns] \u7AEF\u53E3\u63A2\u6D4B\u5931\u8D25: ${i.message}`)}const n=[];for(const i of this.runs.values()){const a=this.logs.get(i.configId),u=i.state==="running"?r.get(i.pid)??[]:[];u.length>0&&this.knownPorts.set(i.configId,u),n.push({...i,logTail:a?a.getLastN(20).map(l=>l.data):[],nextLogIndex:a?a.getSinceIndex(-1).nextIndex:0,ports:i.state==="running"?u.length>0?u:this.knownPorts.get(i.configId)??[]:[],metrics:this.metricsMonitor.getLatest(i.configId)??void 0})}return n}async probePorts(t){const e=[...t].sort((n,i)=>n-i).join(","),s=this.portsProbeInflight.get(e);if(s)return s;const r=X(t).finally(()=>{this.portsProbeInflight.get(e)===r&&this.portsProbeInflight.delete(e)});return this.portsProbeInflight.set(e,r),r}async publishRunningPorts(){const t=this.runningRefs();if(t.length===0)return;let e=new Map;try{e=await this.probePorts(t.map(s=>s.pid))}catch(s){P.debug(`[publishRunningPorts] \u7AEF\u53E3\u63A2\u6D4B\u5931\u8D25: ${s.message}`);return}for(const s of t){const r=this.runs.get(s.configId);if(!r||r.state!=="running"||r.pid!==s.pid)continue;const n=e.get(s.pid)??[];n.length>0?this.knownPorts.set(s.configId,n):this.portReprobeTimers.has(s.configId)||this.schedulePortReprobe(s.configId),w().publishStatusRun({...r,ports:n.length>0?n:this.knownPorts.get(s.configId)??[]})}}async getLogs(t,e){await this.ensureRestored();const s=this.runs.get(t);if(!s)return null;const r=this.logs.get(t);if(!r)return{configId:t,state:s.state,entries:[],nextIndex:0,oldestIndex:0};const{entries:n,nextIndex:i,oldestIndex:a}=r.getSinceIndex(e);return{configId:t,state:s.state,entries:n,nextIndex:i,oldestIndex:a}}getRun(t){return this.runs.get(t)}getMetrics(t,e){return this.metricsMonitor.getHistory(t,e)}getMetricsSince(t,e,s){return this.metricsMonitor.getHistoryEntriesSince(t,e,s)}async ensureRestored(){if(!this.restored){this.restored=!0;try{const t=await z.readFile(B(),"utf-8"),e=JSON.parse(t);for(const s of e){const r=s.pid>0&&await E(s.pid);if(r){P.warn(`[restore] \u68C0\u6D4B\u5230\u4E0A\u6B21 bridge \u975E\u4F18\u96C5\u9000\u51FA\u540E\u7684\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u8054\u52A8\u7EC8\u6B62: configId=${s.configId}, pid=${s.pid}`);try{await v(s.pid)}catch(n){P.warn(`[restore] \u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u5931\u8D25: configId=${s.configId}, pid=${s.pid}, error=${n.message}`)}}this.runs.set(s.configId,{...s,state:"exited",...r?{exitedAt:new Date().toISOString()}:{}}),this.logs.set(s.configId,new L(_))}P.info(`[restore] \u5DF2\u6062\u590D ${e.length} \u6761\u542F\u52A8\u9879\u8FD0\u884C\u8BB0\u5F55`)}catch{}}}publishRunStatus(t,e=!1){const s=this.runs.get(t);s&&(w().publishStatusRun(this.buildStatusFrame(s)),!(!e||s.state!=="running"||s.pid<=0)&&this.schedulePortReprobe(t))}buildStatusFrame(t){return{...t,ports:t.state==="running"?this.knownPorts.get(t.configId)??[]:[]}}schedulePortReprobe(t){this.clearPortReprobe(t);const e=Date.now(),s=++this.portReprobeSeq;this.portReprobeTokens.set(t,s);const r=(n,i)=>{const a=setTimeout(()=>{if(this.portReprobeTokens.get(t)!==s||(this.portReprobeTimers.delete(t),i&&!w().hasStatusSubscribers()))return;const u=this.runs.get(t);!u||u.state!=="running"||u.pid<=0||(this.knownPorts.get(t)?.length??0)>0||(async()=>{let l=[];try{l=(await this.probePorts([u.pid])).get(u.pid)??[]}catch{}if(this.portReprobeTokens.get(t)!==s)return;const f=this.runs.get(t);if(!(!f||f.state!=="running"||f.pid!==u.pid)){if(l.length>0){this.knownPorts.set(t,l),w().publishStatusRun({...f,ports:l});return}Date.now()-e<Q&&r(Z,!0)}})()},n);a.unref?.(),this.portReprobeTimers.set(t,a)};r(K,!1)}clearPortReprobe(t){const e=this.portReprobeTimers.get(t);e!==void 0&&(clearTimeout(e),this.portReprobeTimers.delete(t)),this.portReprobeTokens.delete(t)}async persist(){const t=B();try{const e=Array.from(this.runs.values());await U(t,async()=>{await J(t,e)})}catch(e){P.warn(`[persist] \u6301\u4E45\u5316\u5931\u8D25: ${e.message}`)}}}y.instance=null;function Pt(){return y.getInstance()}export{C as LaunchAlreadyRunningError,y as LaunchManager,N as StreamDecoder,Pt as getLaunchManager,tt as resolvePlaceholders};
12
12
 
@@ -33,6 +33,8 @@ export declare class LaunchStreamHub {
33
33
 
34
34
  subscribeStatus(send: (frame: string) => void): () => void;
35
35
 
36
+ hasStatusSubscribers(): boolean;
37
+
36
38
  publishStatusRun(run: unknown): void;
37
39
 
38
40
  publish(configId: string, kind: LaunchStreamKind, id: number, data: unknown): void;
@@ -1,11 +1,11 @@
1
- class i{constructor(){this.subscribers=new Map,this.statusSubscribers=new Set}static getInstance(){return i.instance===null&&(i.instance=new i),i.instance}subscribe(s,t,n){const r=b(s,t);let e=this.subscribers.get(r);e||(e=new Set,this.subscribers.set(r,e));const c={configId:s,kind:t,send:n};return e.add(c),()=>{const u=this.subscribers.get(r);u&&(u.delete(c),u.size===0&&this.subscribers.delete(r))}}subscriberCount(s,t){return this.subscribers.get(b(s,t))?.size??0}subscribeStatus(s){const t={send:s};return this.statusSubscribers.add(t),()=>{this.statusSubscribers.delete(t)}}publishStatusRun(s){if(this.statusSubscribers.size===0)return;const t=`event: run_upsert
1
+ class n{constructor(){this.subscribers=new Map,this.statusSubscribers=new Set}static getInstance(){return n.instance===null&&(n.instance=new n),n.instance}subscribe(s,t,i){const r=b(s,t);let e=this.subscribers.get(r);e||(e=new Set,this.subscribers.set(r,e));const c={configId:s,kind:t,send:i};return e.add(c),()=>{const u=this.subscribers.get(r);u&&(u.delete(c),u.size===0&&this.subscribers.delete(r))}}subscriberCount(s,t){return this.subscribers.get(b(s,t))?.size??0}subscribeStatus(s){const t={send:s};return this.statusSubscribers.add(t),()=>{this.statusSubscribers.delete(t)}}hasStatusSubscribers(){return this.statusSubscribers.size>0}publishStatusRun(s){if(this.statusSubscribers.size===0)return;const t=`event: run_upsert
2
2
  data: ${JSON.stringify({run:s})}
3
3
 
4
- `;for(const n of[...this.statusSubscribers])try{n.send(t)}catch{}}publish(s,t,n,r){const e=this.subscribers.get(b(s,t));if(!e||e.size===0)return;const c=`id: ${n}
4
+ `;for(const i of[...this.statusSubscribers])try{i.send(t)}catch{}}publish(s,t,i,r){const e=this.subscribers.get(b(s,t));if(!e||e.size===0)return;const c=`id: ${i}
5
5
  data: ${JSON.stringify(r)}
6
6
 
7
- `;for(const u of[...e])try{u.send(c)}catch{}}publishState(s,t){const n=`event: run_state
7
+ `;for(const u of[...e])try{u.send(c)}catch{}}publishState(s,t){const i=`event: run_state
8
8
  data: ${JSON.stringify(t)}
9
9
 
10
- `;for(const r of["logs","metrics"]){const e=this.subscribers.get(b(s,r));if(!(!e||e.size===0))for(const c of[...e])try{c.send(n)}catch{}}}}i.instance=null;function b(a,s){return`${a}:${s}`}function o(){return i.getInstance()}export{i as LaunchStreamHub,o as getLaunchStreamHub};
10
+ `;for(const r of["logs","metrics"]){const e=this.subscribers.get(b(s,r));if(!(!e||e.size===0))for(const c of[...e])try{c.send(i)}catch{}}}}n.instance=null;function b(a,s){return`${a}:${s}`}function o(){return n.getInstance()}export{n as LaunchStreamHub,o as getLaunchStreamHub};
11
11
 
@@ -1,6 +1,6 @@
1
- import{exec as N}from"node:child_process";import g from"node:os";import{promisify as P}from"node:util";import{createLogger as w}from"../utils/logger.js";const l=P(N),m=w("port-detector");function I(s){return s.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g,"").trim()}function b(s){return s==null?[]:Array.isArray(s)?s:[s]}function y(s,o){const i=new Set([s]),t=[s];for(;t.length>0;){const n=t.shift();if(n===void 0)break;for(const e of o.get(n)??[])i.has(e)||(i.add(e),t.push(e))}return i}function M(s){const o=new Map;for(const i of s.split(`
2
- `)){const t=i.trim().split(/\s+/);if(t.length<2)continue;const n=Number.parseInt(t[0],10),e=Number.parseInt(t[1],10);if(!Number.isFinite(n)||!Number.isFinite(e))continue;const r=o.get(e);r?r.push(n):o.set(e,[n])}return o}async function S(){const i=Buffer.from("$conns = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalPort, OwningProcess; $procs = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId; @{Connections = $conns; Processes = $procs} | ConvertTo-Json -Compress -Depth 3","utf16le").toString("base64"),{stdout:t}=await l(`powershell -NoProfile -NonInteractive -EncodedCommand ${i}`,{encoding:"utf8",timeout:15e3,windowsHide:!0,maxBuffer:10*1024*1024}),n=new Map,e=new Map,r=I(t),d=r.search(/[[{]/);if(d<0)return{portsByPid:n,childrenMap:e};const u=JSON.parse(r.slice(d));for(const c of b(u.Connections)){const a=typeof c?.LocalPort=="number"?c.LocalPort:Number.NaN,p=typeof c?.OwningProcess=="number"?c.OwningProcess:Number.NaN;if(!Number.isFinite(a)||!Number.isFinite(p)||a<=0||p<=0)continue;const f=n.get(p);f?f.push(a):n.set(p,[a])}for(const c of b(u.Processes)){const a=typeof c?.ProcessId=="number"?c.ProcessId:Number.NaN,p=typeof c?.ParentProcessId=="number"?c.ParentProcessId:Number.NaN;if(!Number.isFinite(a)||!Number.isFinite(p))continue;const f=e.get(p);f?f.push(a):e.set(p,[a])}return{portsByPid:n,childrenMap:e}}function h(s,o,i){const t=s.get(o);t?t.push(i):s.set(o,[i])}function L(s,o){for(const i of s.split(`
3
- `)){if(!i.includes("LISTEN"))continue;const t=i.trim().split(/\s+/);if(t.length<5)continue;const n=t[3].match(/:(\d+)$/);if(!n)continue;const e=Number.parseInt(n[1],10);for(const r of t[t.length-1].matchAll(/pid=(\d+)/g))h(o,Number.parseInt(r[1],10),e)}}function C(s,o){for(const i of s.split(`
4
- `)){const t=i.trim().split(/\s+/);if(t.length<7||!t[0].startsWith("tcp")||t[5]!=="LISTEN")continue;const n=t[3].match(/:(\d+)$/);if(!n)continue;const e=t[6].match(/^(\d+)\//);e&&h(o,Number.parseInt(e[1],10),Number.parseInt(n[1],10))}}function T(s,o){for(const i of s.split(`
5
- `)){if(!i.includes("(LISTEN)"))continue;const t=i.trim().split(/\s+/);if(t.length<9)continue;const n=Number.parseInt(t[1],10),e=i.match(/:(\d+)\s+\(LISTEN\)/);!Number.isFinite(n)||!e||h(o,n,Number.parseInt(e[1],10))}}async function $(s){const o=new Map,{stdout:i}=await l("ps -axo pid=,ppid=",{encoding:"utf8",timeout:5e3}),t=M(i);if(s){try{const{stdout:n}=await l("lsof -nP -iTCP -sTCP:LISTEN",{encoding:"utf8",timeout:8e3});T(n,o)}catch(n){m.debug(`[collectUnix] lsof failed: ${n.message}`)}return{portsByPid:o,childrenMap:t}}try{const{stdout:n}=await l("ss -ltnp",{encoding:"utf8",timeout:5e3});L(n,o)}catch(n){m.debug(`[collectUnix] ss failed, fallback to netstat: ${n.message}`);try{const{stdout:e}=await l("netstat -ltnp",{encoding:"utf8",timeout:5e3});C(e,o)}catch(e){m.debug(`[collectUnix] netstat failed: ${e.message}`)}}return{portsByPid:o,childrenMap:t}}async function A(s){const o=new Map,i=s.filter(r=>Number.isFinite(r)&&r>0);if(i.length===0)return o;const t=g.platform();let n,e;if(t==="win32"){const r=await S();n=r.portsByPid,e=r.childrenMap}else{const r=await $(t==="darwin");n=r.portsByPid,e=r.childrenMap}for(const r of i){const d=y(r,e),u=new Set;for(const c of d)for(const a of n.get(c)??[])u.add(a);u.size>0&&o.set(r,[...u].sort((c,a)=>c-a))}return o}export{A as getListeningPortsForProcessTrees};
1
+ import{exec as P}from"node:child_process";import g from"node:os";import{promisify as w}from"node:util";import{createLogger as I}from"../utils/logger.js";const l=w(P),m=I("port-detector");function y(o){return o.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g,"").trim()}function b(o){return o==null?[]:Array.isArray(o)?o:[o]}function M(o,s){const i=new Set([o]),t=[o];for(;t.length>0;){const e=t.shift();if(e===void 0)break;for(const n of s.get(e)??[])i.has(n)||(i.add(n),t.push(n))}return i}function S(o){const s=new Map;for(const i of o.split(`
2
+ `)){const t=i.trim().split(/\s+/);if(t.length<2)continue;const e=Number.parseInt(t[0],10),n=Number.parseInt(t[1],10);if(!Number.isFinite(e)||!Number.isFinite(n))continue;const r=s.get(n);r?r.push(e):s.set(n,[e])}return s}async function C(){const i=Buffer.from("$conns = Get-NetTCPConnection -ErrorAction SilentlyContinue | Select-Object LocalPort, RemotePort, OwningProcess; $procs = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId; @{Connections = $conns; Processes = $procs} | ConvertTo-Json -Compress -Depth 3","utf16le").toString("base64"),{stdout:t}=await l(`powershell -NoProfile -NonInteractive -EncodedCommand ${i}`,{encoding:"utf8",timeout:15e3,windowsHide:!0,maxBuffer:20*1024*1024}),e=new Map,n=new Map,r=y(t),d=r.search(/[[{]/);if(d<0)return{portsByPid:e,childrenMap:n};const p=JSON.parse(r.slice(d));for(const c of b(p.Connections)){const u=typeof c?.LocalPort=="number"?c.LocalPort:Number.NaN,f=typeof c?.RemotePort=="number"?c.RemotePort:Number.NaN,a=typeof c?.OwningProcess=="number"?c.OwningProcess:Number.NaN;if(!Number.isFinite(u)||f!==0||!Number.isFinite(a)||u<=0||a<=0)continue;const N=e.get(a);N?N.push(u):e.set(a,[u])}for(const c of b(p.Processes)){const u=typeof c?.ProcessId=="number"?c.ProcessId:Number.NaN,f=typeof c?.ParentProcessId=="number"?c.ParentProcessId:Number.NaN;if(!Number.isFinite(u)||!Number.isFinite(f))continue;const a=n.get(f);a?a.push(u):n.set(f,[u])}return{portsByPid:e,childrenMap:n}}function h(o,s,i){const t=o.get(s);t?t.push(i):o.set(s,[i])}function L(o,s){for(const i of o.split(`
3
+ `)){if(!i.includes("LISTEN"))continue;const t=i.trim().split(/\s+/);if(t.length<5)continue;const e=t[3].match(/:(\d+)$/);if(!e)continue;const n=Number.parseInt(e[1],10);for(const r of t[t.length-1].matchAll(/pid=(\d+)/g))h(s,Number.parseInt(r[1],10),n)}}function T(o,s){for(const i of o.split(`
4
+ `)){const t=i.trim().split(/\s+/);if(t.length<7||!t[0].startsWith("tcp")||t[5]!=="LISTEN")continue;const e=t[3].match(/:(\d+)$/);if(!e)continue;const n=t[6].match(/^(\d+)\//);n&&h(s,Number.parseInt(n[1],10),Number.parseInt(e[1],10))}}function $(o,s){for(const i of o.split(`
5
+ `)){if(!i.includes("(LISTEN)"))continue;const t=i.trim().split(/\s+/);if(t.length<9)continue;const e=Number.parseInt(t[1],10),n=i.match(/:(\d+)\s+\(LISTEN\)/);!Number.isFinite(e)||!n||h(s,e,Number.parseInt(n[1],10))}}async function x(o){const s=new Map,{stdout:i}=await l("ps -axo pid=,ppid=",{encoding:"utf8",timeout:5e3}),t=S(i);if(o){try{const{stdout:e}=await l("lsof -nP -iTCP -sTCP:LISTEN",{encoding:"utf8",timeout:8e3});$(e,s)}catch(e){m.debug(`[collectUnix] lsof failed: ${e.message}`)}return{portsByPid:s,childrenMap:t}}try{const{stdout:e}=await l("ss -ltnp",{encoding:"utf8",timeout:5e3});L(e,s)}catch(e){m.debug(`[collectUnix] ss failed, fallback to netstat: ${e.message}`);try{const{stdout:n}=await l("netstat -ltnp",{encoding:"utf8",timeout:5e3});T(n,s)}catch(n){m.debug(`[collectUnix] netstat failed: ${n.message}`)}}return{portsByPid:s,childrenMap:t}}async function B(o){const s=new Map,i=o.filter(r=>Number.isFinite(r)&&r>0);if(i.length===0)return s;const t=g.platform();let e,n;if(t==="win32"){const r=await C();e=r.portsByPid,n=r.childrenMap}else{const r=await x(t==="darwin");e=r.portsByPid,n=r.childrenMap}for(const r of i){const d=M(r,n),p=new Set;for(const c of d)for(const u of e.get(c)??[])p.add(u);p.size>0&&s.set(r,[...p].sort((c,u)=>c-u))}return s}export{B as getListeningPortsForProcessTrees};
6
6
 
@@ -96,6 +96,8 @@ interface ChmodWorkspaceEntryParams extends WorkspacePathParams {
96
96
  }
97
97
  export interface SearchWorkspaceFilesParams {
98
98
  workspacePath: string;
99
+
100
+ rootPath?: string;
99
101
  keyword: string;
100
102
  fileMask?: string;
101
103
  ignoreCase?: boolean;
@@ -141,6 +143,8 @@ export interface SearchWorkspaceFilesResult {
141
143
  }
142
144
  export interface ReplaceWorkspaceFilesParams {
143
145
  workspacePath: string;
146
+
147
+ rootPath?: string;
144
148
  keyword: string;
145
149
  replacement: string;
146
150
  fileMask?: string;
@@ -1,2 +1,2 @@
1
- import{createReadStream as q,createWriteStream as O,promises as s}from"node:fs";import lt from"node:os";import w from"node:path";import{Readable as ht}from"node:stream";import{pipeline as ut}from"node:stream/promises";import{TextDecoder as wt}from"node:util";import{createGunzip as dt}from"node:zlib";import*as ft from"tar";import pt from"unzipper";const{ZipArchive:_}=await import("archiver"),x={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},mt=new wt("gbk");function u(t){return String(t||"").replace(/\\/g,"/")}function Z(t){const e=w.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function yt(t){const e=Z(t),r=await s.realpath(e);if(!(await s.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function v(t){return{requestedRoot:Z(t),workspaceRoot:await yt(t)}}function C(t,e){const r=w.resolve(e),i=w.relative(t,r);if(i.startsWith("..")||w.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function D(t){return s.realpath(t)}async function F(t){await s.realpath(w.dirname(t));try{if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${t}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}function L(t,e,r){const i=w.resolve(t,r),o=w.relative(t,i);return o.startsWith("..")||w.isAbsolute(o)?i:C(e,w.join(e,o))}async function b({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim(),a=o?L(r,i,o):i,c=await D(a);return{workspaceRoot:i,resolvedTargetPath:c}}function $(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||w.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function G(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function B(t){const e=G(t);return $(w.basename(e.replace(/\\/g,"/")))}function gt(t,e){if(!t)return B(e);const i=G(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(o=>$(o));return i.length===0?B(e):w.join(...i)}function H(t){return(t&4095).toString(8).padStart(4,"0")}function Pt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function I(t,e,r,i,o,a,c){const n=t&e?"r":"-",h=t&r?"w":"-",l=(t&i)!==0,d=(t&o)!==0?l?a:c:l?"x":"-";return`${n}${h}${d}`}function kt(t){return[I(t,256,128,64,2048,"s","S"),I(t,32,16,8,1024,"s","S"),I(t,4,2,1,512,"t","T")].join("")}function xt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function vt(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function Et(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function St(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function K(t,e){if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);const i=await s.stat(t);if(!e||!i.isDirectory())return[t];const o=await s.readdir(t),a=await Promise.all(o.map(c=>{const n=w.join(t,c);return K(n,!0)}));return[t,...a.flat()]}async function Rt(t){if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);await D(t)}async function Dt(t){const e=await s.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),o=r.subarray(0,i);return xt(o)||vt(o)||Et(o)||St(o)}finally{await e.close()}}async function T({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:o}=await v(t),a=String(e||"").trim();if(!a)throw new Error("targetPath is required");const c=L(i,o,a),n=await D(c);if(!r.allowWorkspaceRoot&&n===o)throw new Error("Workspace root cannot be modified");return{workspaceRoot:o,resolvedTargetPath:n}}async function X(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const o=L(e,r,i);return await et(o)?{workspaceRoot:r,resolvedTargetPath:await s.realpath(o)}:(await s.realpath(w.dirname(o)),{workspaceRoot:r,resolvedTargetPath:o})}function V(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function Y(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function Ft(t,e){const r=$(Y(e)||"extracted");return w.join(t,r)}function M(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>x.maxPathLength||i.length>x.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return C(t,w.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=mt.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function J(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function N(t){if(t.entries+=1,t.entries>x.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${x.maxEntries}`)}function Q(t,e){if(t.totalBytes+=e,e>x.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${x.maxFileBytes} bytes`);if(t.totalBytes>x.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${x.maxTotalBytes} bytes`)}async function tt(t,e,r){let i=0;t.on("data",o=>{const a=Buffer.isBuffer(o)?o.length:Buffer.byteLength(o);i+=a,(i>x.maxFileBytes||r.totalBytes+i>x.maxTotalBytes)&&t instanceof ht&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await ut(t,O(e)),Q(r,i)}async function et(t){try{return await s.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function W({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim();if(!o)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:L(r,i,o)}}async function Gt(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.targetPath||"").trim();if(!r){const{resolvedTargetPath:h}=await b(t);return rt(e,h)}const i=w.resolve(r);let o;try{o=await s.realpath(i)}catch{throw new Error(`Path not found: ${i}`)}if(!(await s.stat(o)).isDirectory())throw new Error(`Path is not a directory: ${i}`);const c=w.relative(e,o),n=c===""||!c.startsWith("..")&&!w.isAbsolute(c);return rt(e,n?o:i)}async function rt(t,e){if(!(await s.stat(e)).isDirectory())throw new Error(`Path is not a directory: ${e}`);const i=await s.readdir(e,{withFileTypes:!0}),o=await Promise.all(i.map(async a=>{const c=w.join(e,a.name),n=a.isFile()?(await s.stat(c)).size:void 0;return{name:a.name,path:u(c),isDirectory:a.isDirectory(),size:n}}));return{currentPath:u(e),workspacePath:u(t),items:o}}async function Ht(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await D(r),o=await s.stat(i);if(!o.isFile())throw new Error(`Path is not a file: ${i}`);const a=await s.readFile(i,"utf-8");return{workspacePath:u(e),filePath:u(i),content:a,mtimeMs:o.mtimeMs}}const it={maxEntries:1e5,maxDepth:32};async function zt(t){let e=0,r=0;async function i(o,a){if(a>=it.maxDepth)return;let c;try{c=await s.readdir(o,{withFileTypes:!0})}catch{return}for(const n of c){if(r+=1,r>it.maxEntries)return;if(n.isSymbolicLink())continue;const h=w.join(o,n.name);try{if(n.isDirectory())await i(h,a+1);else if(n.isFile()){const l=await s.stat(h);e+=l.size}}catch{}}}return await i(t,0),e}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await T(t,{allowWorkspaceRoot:!0}),[i,o]=await Promise.all([s.stat(r),s.lstat(r)]),a=i.isDirectory(),c=i.isFile()?await Dt(r):void 0,n=a?await zt(r):i.size;return{workspacePath:u(e),targetPath:u(r),name:w.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:o.isSymbolicLink(),size:n,mode:i.mode,modeOctal:H(i.mode),permissions:kt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...c?{image:c}:{}}}async function Xt(t){const e=Pt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:o}=await T(t),a=await K(o,r);for(const c of a)await Rt(c),await s.chmod(c,e);return{ok:!0,workspacePath:u(i),targetPath:u(o),mode:e,modeOctal:H(e),recursive:r,changedCount:a.length}}async function Vt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await D(r),o=await s.stat(i);if(!o.isFile())throw new Error(`Path is not a file: ${i}`);const a=w.extname(i).toLowerCase();if(a===".doc"||a===".xls"||a===".ppt")return{workspacePath:u(e),filePath:u(i),kind:"unsupported",content:"",message:`\u65E7\u7248 ${a} \u683C\u5F0F\u5C06\u7531\u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A\u73B0\u4EE3\u683C\u5F0F\u540E\u6253\u5F00\uFF08\u539F\u6587\u4EF6\u4E0D\u53D8\uFF09\u3002`,size:o.size,mtimeMs:o.mtimeMs};if(!/\.(docx|docm|xlsx|xlsm|pptx|pptm)$/.test(a))throw new Error("Document preview only supports docx/xlsx/pptx files");return{workspacePath:u(e),filePath:u(i),kind:"html",content:"",size:o.size,mtimeMs:o.mtimeMs}}async function Yt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);await s.mkdir(w.dirname(r),{recursive:!0}),await F(r),await s.writeFile(r,String(t.content??""),"utf-8");const i=await s.stat(r);return{ok:!0,workspacePath:u(e),filePath:u(r),mtimeMs:i.mtimeMs}}async function Jt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await b(t),i=$(t.entryName),o=w.join(r,i);if(!(await s.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await s.access(o),new Error(`Path already exists: ${o}`)}catch(c){if((typeof c=="object"&&c!==null?Reflect.get(c,"code"):void 0)!=="ENOENT")throw c}return t.isDirectory?await s.mkdir(o,{recursive:!1}):(await s.mkdir(w.dirname(o),{recursive:!0}),await F(o),await s.writeFile(o,"","utf-8")),{ok:!0,workspacePath:u(e),targetPath:u(o),isDirectory:t.isDirectory}}async function Qt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await T(t),i=$(t.newName),o=w.join(w.dirname(r),i);if(o===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)};await s.stat(r);try{throw await s.access(o),new Error(`Path already exists: ${o}`)}catch(a){if((typeof a=="object"&&a!==null?Reflect.get(a,"code"):void 0)!=="ENOENT")throw a}return await s.rename(r,o),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)}}async function te(t){const{workspaceRoot:e,resolvedTargetPath:r}=await T(t),i=await b({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await s.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await s.stat(r)).isDirectory()){const n=w.relative(r,i.resolvedTargetPath);if(n===""||!n.startsWith("..")&&!w.isAbsolute(n))throw new Error("Cannot move a directory into itself or its descendant")}const c=w.join(i.resolvedTargetPath,w.basename(r));if(c===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(c),destinationPath:u(i.resolvedTargetPath)};try{throw await s.access(c),new Error(`Path already exists: ${c}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await s.rename(r,c),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(c),destinationPath:u(i.resolvedTargetPath)}}async function ee(t){const{workspaceRoot:e,resolvedTargetPath:r}=await T(t),i=await s.stat(r),o=w.parse(r).root;if(r===o)throw new Error("Filesystem root cannot be deleted");return i.isDirectory()?await s.rm(r,{recursive:!0,force:!1}):await s.unlink(r),{ok:!0,workspacePath:u(e),targetPath:u(r)}}async function re(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await X(t);if(!(await s.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const o=[];for(const a of t.files){const c=B(a.originalname),n=gt(a.relativePath,a.originalname),h=w.join(r,n);await s.mkdir(w.dirname(h),{recursive:!0}),await F(h),await s.copyFile(a.path,h);let l;t.extractArchives&&V(h)&&(l=await Wt({workspacePath:e,archivePath:h,outputPath:Ft(r,c)})),o.push({fileName:c,targetPath:u(h),size:a.size,extracted:l})}return{ok:!0,workspacePath:u(e),targetPath:u(r),files:o}}async function ie(t){const{workspaceRoot:e,resolvedTargetPath:r}=await T(t),i=await s.stat(r),o=w.basename(r);return{workspacePath:u(e),targetPath:u(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${o||"workspace"}.zip`:o,contentType:i.isDirectory()?"application/zip":Lt(r),isDirectory:i.isDirectory(),size:i.isDirectory()?void 0:i.size}}function Lt(t){const e=w.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function oe(t,e){const r=new _({zlib:{level:9}}),i=new Promise((o,a)=>{e.on("close",o),e.on("finish",o),r.on("error",a),e.on("error",a)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function ae(t,e,r,i){const o=w.join(lt.tmpdir(),"agentswork-bridge-downloads");await s.mkdir(o,{recursive:!0});const a=(e||"workspace.zip").replace(/[^\w.-]/g,"_").replace(/\.zip$/i,"")||"workspace",c=w.join(o,`${a}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),n=new _({zlib:{level:9}}),h=()=>n.pointer(),l=()=>n.abort(),f=O(c),d=new Promise((m,g)=>{f.on("close",m),f.on("finish",m),n.on("error",g),f.on("error",g)});d.catch(()=>{}),n.pipe(f);const y=setInterval(()=>{try{if(i?.()){l();return}r?.(h())}catch{}},200);try{return n.on("progress",()=>{i?.()&&l()}),n.directory(t,!1),i?.()&&l(),await n.finalize(),await d,r?.(h()),c}catch(m){throw await s.rm(c,{force:!0}).catch(()=>{}),m}finally{clearInterval(y)}}async function $t(t,e,r){let i=0,o;const a=q(t),c=a.pipe(pt.Parse({forceStream:!0}));try{for await(const n of c){if(o){await J(n);continue}const h=Tt(n);try{const l=M(e,h);if(N(r),n.type==="Directory"){await s.mkdir(l,{recursive:!0});continue}if(n.type!=="File")throw new Error(`Unsupported archive entry type: ${h}`);await s.mkdir(w.dirname(l),{recursive:!0}),await F(l),await tt(n,l,r),i+=1}catch(l){o=l,await J(n)}}}finally{a.destroy(),c.destroy()}if(o)throw o;return i}async function At(t,e,r,i){let o=0,a;const c=(n,h)=>{if(a)return!1;try{M(e,n),N(i);const l=String(h.type||"").toLowerCase();if(l==="symboliclink"||l==="link")throw new Error(`Archive links are not supported: ${n}`);if(l!=="file"&&l!=="oldfile"&&l!=="contiguousfile"&&l!=="directory")throw new Error(`Unsupported archive entry type: ${n}`);return typeof h.size=="number"&&Q(i,h.size),l!=="directory"&&(o+=1),!0}catch(l){return a=l,!1}};if(await ft.x({file:t,cwd:e,gzip:r,strict:!0,filter:(n,h)=>c(n,h)}),a)throw a;return o}async function Ct(t,e,r){const i=Y(w.basename(t)),o=M(e,i||"decompressed");return N(r),await s.mkdir(w.dirname(o),{recursive:!0}),await F(o),await tt(q(t).pipe(dt()),o,r),1}async function Wt(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),{resolvedTargetPath:i}=await X({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),o=L(e,r,String(t.archivePath||"").trim()),a=await D(o);if(!(await s.stat(a)).isFile())throw new Error(`Archive path is not a file: ${a}`);if(await et(i)||await s.mkdir(i,{recursive:!0}),!(await s.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const h=V(a);if(!h)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let l=0;const f={entries:0,totalBytes:0};return h==="zip"?l=await $t(a,i,f):h==="tar"||h==="tgz"?l=await At(a,i,h==="tgz",f):l=await Ct(a,i,f),{ok:!0,workspacePath:u(r),archivePath:u(a),targetPath:u(i),extractedEntries:l}}function ot(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,o]=e;return i.split(",").flatMap(a=>ot(r+a.trim()+o))}function at(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return ot(r.toLowerCase()).some(a=>{if(a==="*")return!0;const c=a.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${c}$`).test(i)})}function j(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const nt=10*1024*1024,bt=1e3;async function ne(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,o=String(t.fileMask||"").trim(),a=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:bt,c=i?r.toLowerCase():r,h=(t.mode==="filename"?"filename":"content")==="filename",l=[];let f=0,d=!1;async function y(m){if(d)return;let g;try{g=await s.readdir(m,{withFileTypes:!0})}catch{return}for(const p of g){if(d)return;const P=C(e,w.join(m,p.name));if(p.isSymbolicLink())continue;if(p.isDirectory()){if(j(p.name))continue;await y(P);continue}if(!p.isFile()||o&&!at(p.name,o))continue;f+=1;let k;try{k=await s.stat(P)}catch{continue}if(k.size>nt)continue;if(h){const E=(i?p.name.toLowerCase():p.name).indexOf(c);if(E!==-1&&(l.push({filePath:u(P),lineNumber:0,lineContent:p.name,matchStart:E,matchEnd:E+r.length}),l.length>=a)){d=!0;return}continue}let S;try{S=await s.readFile(P,"utf-8")}catch{continue}const z=S.split(/\r?\n/);for(let R=0;R<z.length;R+=1){const E=z[R],ct=i?E.toLowerCase():E;let U=0;for(;;){const A=ct.indexOf(c,U);if(A===-1)break;if(l.push({filePath:u(P),lineNumber:R+1,lineContent:E,matchStart:A,matchEnd:A+r.length}),l.length>=a){d=!0;return}U=A+1}}}}return await y(e),{workspacePath:u(e),keyword:r,totalFiles:f,totalMatches:l.length,matches:l,truncated:d}}const Bt=100,It=120;async function se(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,o=i?r.toLowerCase():r,a=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:Bt,c=typeof t.maxDirectories=="number"&&t.maxDirectories>0?t.maxDirectories:It,n=String(t.rootPath||"").trim(),h=n?w.resolve(/^[A-Za-z]:$/.test(n)?`${n}/`:n):e;let l;try{l=await s.realpath(h)}catch{throw new Error(`Path not found: ${h}`)}if(!(await s.stat(l)).isDirectory())throw new Error(`Path is not a directory: ${h}`);const d=[],y=[l];let m=0,g=!1;for(;y.length>0&&d.length<a&&m<c;){const p=y.shift();m+=1;let P;try{P=await s.readdir(p,{withFileTypes:!0})}catch{continue}for(const k of P){if(d.length>=a){g=!0;break}if(k.isSymbolicLink())continue;const S=w.join(p,k.name);if(k.isDirectory()&&j(k.name))continue;const z=w.relative(l,S).replace(/\\/g,"/");(i?z.toLowerCase():z).includes(o)&&d.push({name:k.name,path:u(S),isDirectory:k.isDirectory()}),k.isDirectory()&&m<c&&y.push(S)}}return(m>=c||d.length>=a)&&(g=!0),d.sort((p,P)=>p.isDirectory===P.isDirectory?p.name.localeCompare(P.name):p.isDirectory?-1:1),{workspacePath:u(e),rootPath:u(l),keyword:r,items:d,truncated:g}}async function st(t,e,r,i){let o;try{o=await s.readFile(t,"utf-8")}catch{return{changed:!1,count:0}}const a=o;let c=0;if(i){const n=e.toLowerCase();let h=0;for(;;){const f=o.toLowerCase().indexOf(n,h);if(f===-1)break;o=o.slice(0,f)+r+o.slice(f+e.length),c+=1;const d=r.length||1;h=f+d}}else{let n=0;for(;;){const h=o.indexOf(e,n);if(h===-1)break;o=o.slice(0,h)+r+o.slice(h+e.length),c+=1;const l=r.length||1;n=h+l}}return o===a?{changed:!1,count:0}:(await F(t),await s.writeFile(t,o,"utf-8"),{changed:!0,count:c})}async function ce(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),o=!!t.ignoreCase,a=String(t.fileMask||"").trim(),c=String(t.filePath||"").trim();let n=0,h=0;if(c){const{resolvedFilePath:f}=await W({workspacePath:t.workspacePath,filePath:c}),d=await st(f,r,i,o);return d.changed&&(n=1,h=d.count),{workspacePath:u(e),keyword:r,replacement:i,changedFiles:n,changedMatches:h}}async function l(f){let d;try{d=await s.readdir(f,{withFileTypes:!0})}catch{return}for(const y of d){const m=C(e,w.join(f,y.name));if(y.isSymbolicLink())continue;if(y.isDirectory()){if(j(y.name))continue;await l(m);continue}if(!y.isFile()||a&&!at(y.name,a))continue;try{if((await s.stat(m)).size>nt)continue}catch{continue}const g=await st(m,r,i,o);g.changed&&(n+=1,h+=g.count)}}return await l(e),{workspacePath:u(e),keyword:r,replacement:i,changedFiles:n,changedMatches:h}}export{Xt as chmodWorkspaceEntry,Jt as createWorkspaceEntry,ee as deleteWorkspaceEntry,Wt as extractWorkspaceArchive,Kt as getWorkspaceEntryProperties,Gt as listWorkspaceDirectory,te as moveWorkspaceEntry,Vt as previewWorkspaceDocument,Ht as readWorkspaceFile,Qt as renameWorkspaceEntry,ce as replaceWorkspaceFiles,ie as resolveWorkspaceDownloadTarget,ne as searchWorkspaceFiles,se as searchWorkspacePaths,oe as streamWorkspaceDirectoryZip,re as uploadWorkspaceFiles,Yt as writeWorkspaceFile,ae as zipWorkspaceDirectoryToTemp};
1
+ import{createReadStream as O,createWriteStream as _,promises as s}from"node:fs";import ut from"node:os";import w from"node:path";import{Readable as wt}from"node:stream";import{pipeline as dt}from"node:stream/promises";import{TextDecoder as ft}from"node:util";import{createGunzip as pt}from"node:zlib";import*as mt from"tar";import yt from"unzipper";const{ZipArchive:Z}=await import("archiver"),x={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},gt=new ft("gbk");function u(t){return String(t||"").replace(/\\/g,"/")}function G(t){const e=w.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function Pt(t){const e=G(t),r=await s.realpath(e);if(!(await s.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function v(t){return{requestedRoot:G(t),workspaceRoot:await Pt(t)}}function C(t,e){const r=w.resolve(e),i=w.relative(t,r);if(i.startsWith("..")||w.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function R(t){return s.realpath(t)}async function D(t){await s.realpath(w.dirname(t));try{if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${t}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}function $(t,e,r){const i=w.resolve(t,r),o=w.relative(t,i);return o.startsWith("..")||w.isAbsolute(o)?i:C(e,w.join(e,o))}async function B({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim(),a=o?$(r,i,o):i,c=await R(a);return{workspaceRoot:i,resolvedTargetPath:c}}function L(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||w.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function H(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function I(t){const e=H(t);return L(w.basename(e.replace(/\\/g,"/")))}function kt(t,e){if(!t)return I(e);const i=H(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(o=>L(o));return i.length===0?I(e):w.join(...i)}function K(t){return(t&4095).toString(8).padStart(4,"0")}function xt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function M(t,e,r,i,o,a,c){const n=t&e?"r":"-",l=t&r?"w":"-",h=(t&i)!==0,f=(t&o)!==0?h?a:c:h?"x":"-";return`${n}${l}${f}`}function vt(t){return[M(t,256,128,64,2048,"s","S"),M(t,32,16,8,1024,"s","S"),M(t,4,2,1,512,"t","T")].join("")}function Et(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function St(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function Rt(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function Dt(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function X(t,e){if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);const i=await s.stat(t);if(!e||!i.isDirectory())return[t];const o=await s.readdir(t),a=await Promise.all(o.map(c=>{const n=w.join(t,c);return X(n,!0)}));return[t,...a.flat()]}async function Ft(t){if((await s.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);await R(t)}async function Tt(t){const e=await s.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),o=r.subarray(0,i);return Et(o)||St(o)||Rt(o)||Dt(o)}finally{await e.close()}}async function F({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:o}=await v(t),a=String(e||"").trim();if(!a)throw new Error("targetPath is required");const c=$(i,o,a),n=await R(c);if(!r.allowWorkspaceRoot&&n===o)throw new Error("Workspace root cannot be modified");return{workspaceRoot:o,resolvedTargetPath:n}}async function V(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const o=$(e,r,i);return await rt(o)?{workspaceRoot:r,resolvedTargetPath:await s.realpath(o)}:(await s.realpath(w.dirname(o)),{workspaceRoot:r,resolvedTargetPath:o})}function Y(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function J(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function zt(t,e){const r=L(J(e)||"extracted");return w.join(t,r)}function N(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>x.maxPathLength||i.length>x.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return C(t,w.join(t,r))}function $t(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=gt.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function Q(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function j(t){if(t.entries+=1,t.entries>x.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${x.maxEntries}`)}function tt(t,e){if(t.totalBytes+=e,e>x.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${x.maxFileBytes} bytes`);if(t.totalBytes>x.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${x.maxTotalBytes} bytes`)}async function et(t,e,r){let i=0;t.on("data",o=>{const a=Buffer.isBuffer(o)?o.length:Buffer.byteLength(o);i+=a,(i>x.maxFileBytes||r.totalBytes+i>x.maxTotalBytes)&&t instanceof wt&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await dt(t,_(e)),tt(r,i)}async function rt(t){try{return await s.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function W({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim();if(!o)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:$(r,i,o)}}async function Kt(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.targetPath||"").trim();if(!r){const{resolvedTargetPath:l}=await B(t);return it(e,l)}const i=w.resolve(r);let o;try{o=await s.realpath(i)}catch{throw new Error(`Path not found: ${i}`)}if(!(await s.stat(o)).isDirectory())throw new Error(`Path is not a directory: ${i}`);const c=w.relative(e,o),n=c===""||!c.startsWith("..")&&!w.isAbsolute(c);return it(e,n?o:i)}async function it(t,e){if(!(await s.stat(e)).isDirectory())throw new Error(`Path is not a directory: ${e}`);const i=await s.readdir(e,{withFileTypes:!0}),o=await Promise.all(i.map(async a=>{const c=w.join(e,a.name);let n;if(a.isFile())try{n=(await s.stat(c)).size}catch{n=void 0}return{name:a.name,path:u(c),isDirectory:a.isDirectory(),size:n}}));return{currentPath:u(e),workspacePath:u(t),items:o}}async function Xt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await R(r),o=await s.stat(i);if(!o.isFile())throw new Error(`Path is not a file: ${i}`);const a=await s.readFile(i,"utf-8");return{workspacePath:u(e),filePath:u(i),content:a,mtimeMs:o.mtimeMs}}const ot={maxEntries:1e5,maxDepth:32};async function Lt(t){let e=0,r=0;async function i(o,a){if(a>=ot.maxDepth)return;let c;try{c=await s.readdir(o,{withFileTypes:!0})}catch{return}for(const n of c){if(r+=1,r>ot.maxEntries)return;if(n.isSymbolicLink())continue;const l=w.join(o,n.name);try{if(n.isDirectory())await i(l,a+1);else if(n.isFile()){const h=await s.stat(l);e+=h.size}}catch{}}}return await i(t,0),e}async function Vt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await F(t,{allowWorkspaceRoot:!0}),[i,o]=await Promise.all([s.stat(r),s.lstat(r)]),a=i.isDirectory(),c=i.isFile()?await Tt(r):void 0,n=a?await Lt(r):i.size;return{workspacePath:u(e),targetPath:u(r),name:w.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:o.isSymbolicLink(),size:n,mode:i.mode,modeOctal:K(i.mode),permissions:vt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...c?{image:c}:{}}}async function Yt(t){const e=xt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:o}=await F(t),a=await X(o,r);for(const c of a)await Ft(c),await s.chmod(c,e);return{ok:!0,workspacePath:u(i),targetPath:u(o),mode:e,modeOctal:K(e),recursive:r,changedCount:a.length}}async function Jt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await R(r),o=await s.stat(i);if(!o.isFile())throw new Error(`Path is not a file: ${i}`);const a=w.extname(i).toLowerCase();if(a===".doc"||a===".xls"||a===".ppt")return{workspacePath:u(e),filePath:u(i),kind:"unsupported",content:"",message:`\u65E7\u7248 ${a} \u683C\u5F0F\u5C06\u7531\u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A\u73B0\u4EE3\u683C\u5F0F\u540E\u6253\u5F00\uFF08\u539F\u6587\u4EF6\u4E0D\u53D8\uFF09\u3002`,size:o.size,mtimeMs:o.mtimeMs};if(!/\.(docx|docm|xlsx|xlsm|pptx|pptm)$/.test(a))throw new Error("Document preview only supports docx/xlsx/pptx files");return{workspacePath:u(e),filePath:u(i),kind:"html",content:"",size:o.size,mtimeMs:o.mtimeMs}}async function Qt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);await s.mkdir(w.dirname(r),{recursive:!0}),await D(r),await s.writeFile(r,String(t.content??""),"utf-8");const i=await s.stat(r);return{ok:!0,workspacePath:u(e),filePath:u(r),mtimeMs:i.mtimeMs}}async function te(t){const{workspaceRoot:e,resolvedTargetPath:r}=await B(t),i=L(t.entryName),o=w.join(r,i);if(!(await s.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await s.access(o),new Error(`Path already exists: ${o}`)}catch(c){if((typeof c=="object"&&c!==null?Reflect.get(c,"code"):void 0)!=="ENOENT")throw c}return t.isDirectory?await s.mkdir(o,{recursive:!1}):(await s.mkdir(w.dirname(o),{recursive:!0}),await D(o),await s.writeFile(o,"","utf-8")),{ok:!0,workspacePath:u(e),targetPath:u(o),isDirectory:t.isDirectory}}async function ee(t){const{workspaceRoot:e,resolvedTargetPath:r}=await F(t),i=L(t.newName),o=w.join(w.dirname(r),i);if(o===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)};await s.stat(r);try{throw await s.access(o),new Error(`Path already exists: ${o}`)}catch(a){if((typeof a=="object"&&a!==null?Reflect.get(a,"code"):void 0)!=="ENOENT")throw a}return await s.rename(r,o),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)}}async function re(t){const{workspaceRoot:e,resolvedTargetPath:r}=await F(t),i=await B({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await s.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await s.stat(r)).isDirectory()){const n=w.relative(r,i.resolvedTargetPath);if(n===""||!n.startsWith("..")&&!w.isAbsolute(n))throw new Error("Cannot move a directory into itself or its descendant")}const c=w.join(i.resolvedTargetPath,w.basename(r));if(c===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(c),destinationPath:u(i.resolvedTargetPath)};try{throw await s.access(c),new Error(`Path already exists: ${c}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await s.rename(r,c),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(c),destinationPath:u(i.resolvedTargetPath)}}async function ie(t){const{workspaceRoot:e,resolvedTargetPath:r}=await F(t),i=await s.stat(r),o=w.parse(r).root;if(r===o)throw new Error("Filesystem root cannot be deleted");return i.isDirectory()?await s.rm(r,{recursive:!0,force:!1}):await s.unlink(r),{ok:!0,workspacePath:u(e),targetPath:u(r)}}async function oe(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await V(t);if(!(await s.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const o=[];for(const a of t.files){const c=I(a.originalname),n=kt(a.relativePath,a.originalname),l=w.join(r,n);await s.mkdir(w.dirname(l),{recursive:!0}),await D(l),await s.copyFile(a.path,l);let h;t.extractArchives&&Y(l)&&(h=await Bt({workspacePath:e,archivePath:l,outputPath:zt(r,c)})),o.push({fileName:c,targetPath:u(l),size:a.size,extracted:h})}return{ok:!0,workspacePath:u(e),targetPath:u(r),files:o}}async function ae(t){const{workspaceRoot:e,resolvedTargetPath:r}=await F(t),i=await s.stat(r),o=w.basename(r);return{workspacePath:u(e),targetPath:u(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${o||"workspace"}.zip`:o,contentType:i.isDirectory()?"application/zip":At(r),isDirectory:i.isDirectory(),size:i.isDirectory()?void 0:i.size}}function At(t){const e=w.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function ne(t,e){const r=new Z({zlib:{level:9}}),i=new Promise((o,a)=>{e.on("close",o),e.on("finish",o),r.on("error",a),e.on("error",a)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function se(t,e,r,i){const o=w.join(ut.tmpdir(),"agentswork-bridge-downloads");await s.mkdir(o,{recursive:!0});const a=(e||"workspace.zip").replace(/[^\w.-]/g,"_").replace(/\.zip$/i,"")||"workspace",c=w.join(o,`${a}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),n=new Z({zlib:{level:9}}),l=()=>n.pointer(),h=()=>n.abort(),d=_(c),f=new Promise((p,g)=>{d.on("close",p),d.on("finish",p),n.on("error",g),d.on("error",g)});f.catch(()=>{}),n.pipe(d);const m=setInterval(()=>{try{if(i?.()){h();return}r?.(l())}catch{}},200);try{return n.on("progress",()=>{i?.()&&h()}),n.directory(t,!1),i?.()&&h(),await n.finalize(),await f,r?.(l()),c}catch(p){throw await s.rm(c,{force:!0}).catch(()=>{}),p}finally{clearInterval(m)}}async function Ct(t,e,r){let i=0,o;const a=O(t),c=a.pipe(yt.Parse({forceStream:!0}));try{for await(const n of c){if(o){await Q(n);continue}const l=$t(n);try{const h=N(e,l);if(j(r),n.type==="Directory"){await s.mkdir(h,{recursive:!0});continue}if(n.type!=="File")throw new Error(`Unsupported archive entry type: ${l}`);await s.mkdir(w.dirname(h),{recursive:!0}),await D(h),await et(n,h,r),i+=1}catch(h){o=h,await Q(n)}}}finally{a.destroy(),c.destroy()}if(o)throw o;return i}async function Wt(t,e,r,i){let o=0,a;const c=(n,l)=>{if(a)return!1;try{N(e,n),j(i);const h=String(l.type||"").toLowerCase();if(h==="symboliclink"||h==="link")throw new Error(`Archive links are not supported: ${n}`);if(h!=="file"&&h!=="oldfile"&&h!=="contiguousfile"&&h!=="directory")throw new Error(`Unsupported archive entry type: ${n}`);return typeof l.size=="number"&&tt(i,l.size),h!=="directory"&&(o+=1),!0}catch(h){return a=h,!1}};if(await mt.x({file:t,cwd:e,gzip:r,strict:!0,filter:(n,l)=>c(n,l)}),a)throw a;return o}async function bt(t,e,r){const i=J(w.basename(t)),o=N(e,i||"decompressed");return j(r),await s.mkdir(w.dirname(o),{recursive:!0}),await D(o),await et(O(t).pipe(pt()),o,r),1}async function Bt(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),{resolvedTargetPath:i}=await V({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),o=$(e,r,String(t.archivePath||"").trim()),a=await R(o);if(!(await s.stat(a)).isFile())throw new Error(`Archive path is not a file: ${a}`);if(await rt(i)||await s.mkdir(i,{recursive:!0}),!(await s.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const l=Y(a);if(!l)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let h=0;const d={entries:0,totalBytes:0};return l==="zip"?h=await Ct(a,i,d):l==="tar"||l==="tgz"?h=await Wt(a,i,l==="tgz",d):h=await bt(a,i,d),{ok:!0,workspacePath:u(r),archivePath:u(a),targetPath:u(i),extractedEntries:h}}function at(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,o]=e;return i.split(",").flatMap(a=>at(r+a.trim()+o))}function nt(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return at(r.toLowerCase()).some(a=>{if(a==="*")return!0;const c=a.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${c}$`).test(i)})}function U(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const st=10*1024*1024,It=1e3;async function ct(t,e){const{workspaceRoot:r}=await v(t),i=String(e||"").trim();if(!i)return{workspaceRoot:r,searchRoot:r};const o=w.resolve(/^[A-Za-z]:$/.test(i)?`${i}/`:i);let a;try{a=await s.realpath(o)}catch{throw new Error(`Path not found: ${o}`)}if(!(await s.stat(a)).isDirectory())throw new Error(`Path is not a directory: ${o}`);return{workspaceRoot:r,searchRoot:a}}async function ce(t){const{workspaceRoot:e,searchRoot:r}=await ct(t.workspacePath,t.rootPath),i=String(t.keyword||"").trim();if(!i)throw new Error("keyword is required");const o=!!t.ignoreCase,a=String(t.fileMask||"").trim(),c=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:It,n=o?i.toLowerCase():i,h=(t.mode==="filename"?"filename":"content")==="filename",d=[];let f=0,m=!1;async function p(g){if(m)return;let k;try{k=await s.readdir(g,{withFileTypes:!0})}catch{return}for(const y of k){if(m)return;const P=C(r,w.join(g,y.name));if(y.isSymbolicLink())continue;if(y.isDirectory()){if(U(y.name))continue;await p(P);continue}if(!y.isFile()||a&&!nt(y.name,a))continue;f+=1;let S;try{S=await s.stat(P)}catch{continue}if(S.size>st)continue;if(h){const E=(o?y.name.toLowerCase():y.name).indexOf(n);if(E!==-1&&(d.push({filePath:u(P),lineNumber:0,lineContent:y.name,matchStart:E,matchEnd:E+i.length}),d.length>=c)){m=!0;return}continue}let T;try{T=await s.readFile(P,"utf-8")}catch{continue}const b=T.split(/\r?\n/);for(let z=0;z<b.length;z+=1){const E=b[z],ht=o?E.toLowerCase():E;let q=0;for(;;){const A=ht.indexOf(n,q);if(A===-1)break;if(d.push({filePath:u(P),lineNumber:z+1,lineContent:E,matchStart:A,matchEnd:A+i.length}),d.length>=c){m=!0;return}q=A+1}}}}return await p(r),{workspacePath:u(e),keyword:i,totalFiles:f,totalMatches:d.length,matches:d,truncated:m}}const Mt=100,Nt=120;async function le(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,o=i?r.toLowerCase():r,a=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:Mt,c=typeof t.maxDirectories=="number"&&t.maxDirectories>0?t.maxDirectories:Nt,n=String(t.rootPath||"").trim(),l=n?w.resolve(/^[A-Za-z]:$/.test(n)?`${n}/`:n):e;let h;try{h=await s.realpath(l)}catch{throw new Error(`Path not found: ${l}`)}if(!(await s.stat(h)).isDirectory())throw new Error(`Path is not a directory: ${l}`);const f=[],m=[h];let p=0,g=!1;for(;m.length>0&&f.length<a&&p<c;){const k=m.shift();p+=1;let y;try{y=await s.readdir(k,{withFileTypes:!0})}catch{continue}for(const P of y){if(f.length>=a){g=!0;break}if(P.isSymbolicLink())continue;const S=w.join(k,P.name);if(P.isDirectory()&&U(P.name))continue;const T=w.relative(h,S).replace(/\\/g,"/");(i?T.toLowerCase():T).includes(o)&&f.push({name:P.name,path:u(S),isDirectory:P.isDirectory()}),P.isDirectory()&&p<c&&m.push(S)}}return(p>=c||f.length>=a)&&(g=!0),f.sort((k,y)=>k.isDirectory===y.isDirectory?k.name.localeCompare(y.name):k.isDirectory?-1:1),{workspacePath:u(e),rootPath:u(h),keyword:r,items:f,truncated:g}}async function lt(t,e,r,i){let o;try{o=await s.readFile(t,"utf-8")}catch{return{changed:!1,count:0}}const a=o;let c=0;if(i){const n=e.toLowerCase();let l=0;for(;;){const d=o.toLowerCase().indexOf(n,l);if(d===-1)break;o=o.slice(0,d)+r+o.slice(d+e.length),c+=1;const f=r.length||1;l=d+f}}else{let n=0;for(;;){const l=o.indexOf(e,n);if(l===-1)break;o=o.slice(0,l)+r+o.slice(l+e.length),c+=1;const h=r.length||1;n=l+h}}return o===a?{changed:!1,count:0}:(await D(t),await s.writeFile(t,o,"utf-8"),{changed:!0,count:c})}async function he(t){const{workspaceRoot:e,searchRoot:r}=await ct(t.workspacePath,t.rootPath),i=String(t.keyword||"").trim();if(!i)throw new Error("keyword is required");const o=String(t.replacement??""),a=!!t.ignoreCase,c=String(t.fileMask||"").trim(),n=String(t.filePath||"").trim();let l=0,h=0;if(n){const{resolvedFilePath:f}=await W({workspacePath:t.workspacePath,filePath:n}),m=await lt(f,i,o,a);return m.changed&&(l=1,h=m.count),{workspacePath:u(e),keyword:i,replacement:o,changedFiles:l,changedMatches:h}}async function d(f){let m;try{m=await s.readdir(f,{withFileTypes:!0})}catch{return}for(const p of m){const g=C(r,w.join(f,p.name));if(p.isSymbolicLink())continue;if(p.isDirectory()){if(U(p.name))continue;await d(g);continue}if(!p.isFile()||c&&!nt(p.name,c))continue;try{if((await s.stat(g)).size>st)continue}catch{continue}const k=await lt(g,i,o,a);k.changed&&(l+=1,h+=k.count)}}return await d(r),{workspacePath:u(e),keyword:i,replacement:o,changedFiles:l,changedMatches:h}}export{Yt as chmodWorkspaceEntry,te as createWorkspaceEntry,ie as deleteWorkspaceEntry,Bt as extractWorkspaceArchive,Vt as getWorkspaceEntryProperties,Kt as listWorkspaceDirectory,re as moveWorkspaceEntry,Jt as previewWorkspaceDocument,Xt as readWorkspaceFile,ee as renameWorkspaceEntry,he as replaceWorkspaceFiles,ae as resolveWorkspaceDownloadTarget,ce as searchWorkspaceFiles,le as searchWorkspacePaths,ne as streamWorkspaceDirectoryZip,oe as uploadWorkspaceFiles,Qt as writeWorkspaceFile,se as zipWorkspaceDirectoryToTemp};
2
2
 
@@ -1,2 +1,2 @@
1
- const s=new Set(["poll_message","get_message","send_private"]),i=new Set(["create_meeting"]),o=new Set(["poll_message","create_meeting"]);function t(e){const n=e.trim().toLowerCase();return n.split(/__|[./]/).filter(Boolean).at(-1)??n}function a(e){return s.has(t(e))}function c(e){return i.has(t(e))}function l(e){return o.has(t(e))}function m(e){return o.has(t(e))}export{t as getSemanticToolName,m as isMcpBatchExcludedToolName,c as isMcpBlockingToolName,l as isMcpIndefinitelyBlockingToolName,a as isMcpPollingToolName};
1
+ const s=new Set(["poll_message","get_message","send_private","get_send_status"]),a=new Set(["create_meeting"]),o=new Set(["poll_message","create_meeting"]);function t(e){const n=e.trim().toLowerCase();return n.split(/__|[./]/).filter(Boolean).at(-1)??n}function r(e){return s.has(t(e))}function c(e){return a.has(t(e))}function l(e){return o.has(t(e))}function _(e){return o.has(t(e))}export{t as getSemanticToolName,_ as isMcpBatchExcludedToolName,c as isMcpBlockingToolName,l as isMcpIndefinitelyBlockingToolName,r as isMcpPollingToolName};
2
2
 
@@ -114,7 +114,9 @@ export declare class AgentClient {
114
114
 
115
115
 
116
116
 
117
- sendGroupMessage(content: string, projectId?: string, roomId?: string, card?: Card, replyTo?: ReplyToRef[], voice?: VoiceSendOptions): Promise<SendMessageResult>;
117
+
118
+
119
+ sendGroupMessage(content: string, projectId?: string, roomId?: string, card?: Card, replyTo?: ReplyToRef[], voice?: VoiceSendOptions, mentions?: string[]): Promise<SendMessageResult>;
118
120
 
119
121
 
120
122
 
@@ -140,7 +142,9 @@ export declare class AgentClient {
140
142
 
141
143
 
142
144
 
143
- getDmSendStatus(ticket: string): Promise<unknown>;
145
+
146
+
147
+ getSendStatus(tickets: string[], waitTimeoutMs?: number): Promise<unknown>;
144
148
 
145
149
 
146
150