aws-runtime-bridge 1.9.184 → 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 +60 -0
- package/dist/adapter/types.js +1 -1
- package/dist/routes/file-browser.js +1 -1
- package/dist/services/auto-register.js +1 -1
- package/dist/services/bridge-pairing.js +1 -1
- package/dist/services/launch-manager.d.ts +33 -0
- package/dist/services/launch-manager.js +7 -7
- package/dist/services/launch-streams.d.ts +2 -0
- package/dist/services/launch-streams.js +4 -4
- package/dist/services/port-detector.js +5 -5
- package/dist/services/workspace-files.d.ts +4 -0
- package/dist/services/workspace-files.js +1 -1
- package/package/acode/dist/mcpToolNames.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +6 -2
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +4 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +6 -6
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +4 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +6 -6
- package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,66 @@ 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
|
+
|
|
41
|
+
## v1.9.185
|
|
42
|
+
|
|
43
|
+
_发布日期:2026-11-04_
|
|
44
|
+
|
|
45
|
+
### 修复:已配对机器重启不再自毁配对(面板实例消失的根因)
|
|
46
|
+
|
|
47
|
+
已配对机器(machineToken 自持凭据)优雅关闭时曾无条件调用 `/api/instances/unregister`
|
|
48
|
+
注销实例,而 server 端该路径会整行删除登记行——`machineToken` 的 hash 就存在这一行上,
|
|
49
|
+
行被删掉配对即被永久摧毁:重启后机器凭据注册被拒(invalid or revoked),面板上的实例
|
|
50
|
+
也随之消失。本次两端同修:
|
|
51
|
+
|
|
52
|
+
- Bridge:注销时识别机器凭据目标(machineToken / credentialType=MACHINE_TOKEN),
|
|
53
|
+
**跳过自删登记行**——配对身份必须跨重启存活;Agent 离线由隧道断开与服务端重启同步兜底,
|
|
54
|
+
真正下线机器请用管理页删除(Bearer 路径,带租户/负责人校验);
|
|
55
|
+
- Server:runtime token 自注销路径拒绝删除 MACHINE_TOKEN 行并返回明确错误
|
|
56
|
+
(兼容尚未升级的旧版 bridge,防止其重启时再毁配对);token 无效时不提前泄露
|
|
57
|
+
实例的存在性与凭据类型,仍按既有路径走登录态校验。
|
|
58
|
+
|
|
59
|
+
### 修复:票据里填写的实例名写回不再被旧主机名顶掉
|
|
60
|
+
|
|
61
|
+
server 配对响应的实例名以票据建议名为权威,但 bridge 写回配置用的是
|
|
62
|
+
`target.instanceName = target.instanceName || instanceName`——本机已有同址旧 target
|
|
63
|
+
(instanceName 为历史主机名,如此前用 API Key 注册过)时票据名永远写不进本地配置;
|
|
64
|
+
下次注册(配对后立即重注册或重启)机器上报旧主机名,server 又按请求名覆盖登记行,
|
|
65
|
+
用户在票据里填写的实例名就丢了。本次改为**无条件写回 server 响应名**,票据名在
|
|
66
|
+
本地配置与面板登记行之间保持一致。
|
|
67
|
+
|
|
8
68
|
## v1.9.184
|
|
9
69
|
|
|
10
70
|
_发布日期:2026-11-03_
|
package/dist/adapter/types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as S}from"node:events";const m="[temp clipboard paste file]",
|
|
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
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomBytes as ce}from"node:crypto";import p from"node:fs";import N from"node:os";import w from"axios";import{v4 as ue}from"uuid";import{getActiveBridgeHttpPort as de,getAutoRegisterConfigFilePath as S,isDesktopAppMode as le,schedulerBaseUrl as m}from"../config.js";import{runExclusiveConfigWrite as D}from"../utils/config-write-queue.js";import{logger as a}from"../utils/logger.js";import{buildTargetKey as x,fingerprintUserKey as fe,serverUrlOfTargetKey as F}from"../utils/target-identity.js";import{clearRuntimeBinding as ge,clearScopedRuntimeAccessToken as me,getRuntimeAccessToken as G,getRuntimeBindingPublicState as pe,getScopedRuntimeAccessToken as q,listScopedRuntimeAccessTokens as ye,loadRuntimeBinding as M,normalizeSchedulerBaseUrl as T,saveRuntimeBinding as Y,saveScopedRuntimeAccessToken as H}from"./runtime-binding.js";const V={enabled:!1,serverUrl:m,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3},d={registered:!1,registrations:{}};let k=[];const Ie=10*60*1e3,B=new Map;function X(){const e=Date.now();for(const[r,t]of B)t.expiresAt<=e&&B.delete(r)}function Je(){X();const e=[...k];for(const r of B.values())r.key&&!e.includes(r.key)&&e.push(r.key);return e}function qe(e){const r=String(e||"").trim();if(!r)return[];const t=K(),n=t.filter(s=>String(s.connectionKey||"").trim()===r);if(n.length>0)return n;X();const o=new Set;for(const[s,i]of B)i.key===r&&o.add(s);return o.size===0?[]:t.filter(s=>o.has(C(s)))}function Ye(){return k[0]??""}const Ae=3e4;function Q(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:Ae}const $=new Map;function Z(e){const r=e.serverUrl||m,t=C(e)||r;if($.has(t))return;const n=setTimeout(()=>{$.delete(t),Re(e,t)},Q());n.unref?.(),$.set(t,n)}async function Re(e,r){try{const t=K().find(n=>C(n)===r)||e;await J(t,{backgroundRetry:!0})}catch(t){a.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),Z(e)}}function He(){for(const e of $.values())clearTimeout(e);$.clear()}function Se(e){const r=e.trim();if(r.length<8)return r;for(let t=1;t<=r.length/2;t++){if(r.length%t!==0)continue;const n=r.slice(0,t);let o=!0;for(let s=t;s<r.length;s+=t)if(r.slice(s,s+t)!==n){o=!1;break}if(o&&n.length<r.length)return n}return r}function Te(e){const r=g(e);if(!r)return r;const t=Se(r);return t!==r&&a.warn(`[AutoRegister] userKey \u7591\u4F3C\u88AB\u590D\u5236\u4E86\u4E24\u904D\uFF08\u957F\u5EA6 ${r.length} \u2192 ${t.length}\uFF09\uFF0C\u5DF2\u81EA\u52A8\u53BB\u91CD\u3002\u8BF7\u68C0\u67E5\u914D\u7F6E\u6587\u4EF6\u662F\u5426\u91CD\u590D\u7C98\u8D34\u3002`),t}function g(e){return e==null?void 0:String(e).trim()||void 0}function _(e){return T(e)}function ee(e){return Array.isArray(e.autoRegisterTargets)?e.autoRegisterTargets.filter(r=>!!(r&&typeof r=="object"&&!Array.isArray(r))):[]}function C(e){const r=e?.serverUrl||m;return x(r,e?.credentialId||e?.userKey)}function y(e){return e?.credentialId||e?.userKey}function P(e){if(typeof e=="boolean")return e;if(typeof e=="string"){const r=e.trim().toLowerCase();if(["true","1","yes","y"].includes(r))return!0;if(["false","0","no","n"].includes(r))return!1}}function re(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function O(e){const r=g(e.serverUrl)||g(e.schedulerBaseUrl),t=g(e.connectionKey)||g(e.password);return{enabled:P(e.enabled),serverUrl:r,tenantId:g(e.tenantId),userId:g(e.userId),userKey:Te(e.userKey),machineToken:g(e.machineToken),connectionKey:t,instanceName:g(e.instanceName),instanceId:g(e.instanceId),credentialId:g(e.credentialId),credentialRef:g(e.credentialRef),credentialType:g(e.credentialType),projectId:g(e.projectId),roleName:g(e.roleName),workspacePath:g(e.workspacePath),registerAddr:g(e.registerAddr),registerIp:g(e.registerIp),virtualIp:g(e.virtualIp),tunnel:P(e.tunnel),preferSameLanIp:P(e.preferSameLanIp),maxRetries:re(e.maxRetries),retryInterval:re(e.retryInterval)}}function te(){const e=S();try{if(!p.existsSync(e))return a.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=p.readFileSync(e,"utf-8"),t=JSON.parse(r);return a.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),O(t)}catch(r){const t=r;return a.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function he(){const e=S();try{if(!p.existsSync(e))return[];const r=p.readFileSync(e,"utf-8"),t=JSON.parse(r),n=O(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey||n.machineToken?[n]:[]:o.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const i=O(s);return{...n,...i,machineToken:i.machineToken||(i.userKey?void 0:n.machineToken),connectionKey:i.connectionKey}})}catch(r){const t=r;return a.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function Ve(){const e=S();if(!p.existsSync(e))return[];let r;try{r=JSON.parse(p.readFileSync(e,"utf-8"))}catch(i){return a.warn(`[AutoRegister] \u8BFB\u53D6 config.json \u5931\u8D25\uFF08targets \u6458\u8981\uFF09: ${i.message}`),[]}const t=O(r),n=Array.isArray(r.autoRegisterTargets)?r.autoRegisterTargets:[],o=n.length>0?n.filter(i=>!!(i&&typeof i=="object"&&!Array.isArray(i))).map(i=>{const c=O(i);return{...t,...c,machineToken:c.machineToken||(c.userKey?void 0:t.machineToken),connectionKey:c.connectionKey}}):t.userKey||t.machineToken?[t]:[],s=i=>{const c=String(i||"").trim();return c?c.length<=8?"****":`${c.slice(0,8)}****${c.slice(-4)}`:""};return o.map((i,c)=>({index:c,serverUrl:i.serverUrl||"",instanceName:i.instanceName||"",registerAddr:i.registerAddr||i.registerIp||i.virtualIp||"",tunnel:typeof i.tunnel=="boolean"?i.tunnel:null,userKeyMasked:s(i.userKey),machineTokenSet:!!String(i.machineToken||"").trim(),credentialType:i.credentialType||"",credentialId:i.credentialId||"",credentialRef:i.credentialRef||"",connectionKeySet:!!String(i.connectionKey||"").trim()}))}function ne(){const e={};return process.env.AWS_AUTO_REGISTER==="true"&&(e.enabled=!0),process.env.AWS_RUNTIME_SCHEDULER_BASE_URL&&(e.serverUrl=process.env.AWS_RUNTIME_SCHEDULER_BASE_URL),process.env.AWS_TENANT_ID&&(e.tenantId=process.env.AWS_TENANT_ID),process.env.AWS_USER_KEY&&(e.userKey=process.env.AWS_USER_KEY),process.env.AWS_BRIDGE_MACHINE_TOKEN&&(e.machineToken=process.env.AWS_BRIDGE_MACHINE_TOKEN),process.env.AWS_BRIDGE_CONNECTION_KEY&&(e.connectionKey=process.env.AWS_BRIDGE_CONNECTION_KEY),process.env.AWS_INSTANCE_NAME&&(e.instanceName=process.env.AWS_INSTANCE_NAME),process.env.AWS_PROJECT_NAME&&(e.projectId=process.env.AWS_PROJECT_NAME),process.env.AWS_ROLE_NAME&&(e.roleName=process.env.AWS_ROLE_NAME),process.env.AWS_WORKSPACE_PATH&&(e.workspacePath=process.env.AWS_WORKSPACE_PATH),process.env.AWS_REGISTER_ADDR&&(e.registerAddr=process.env.AWS_REGISTER_ADDR),process.env.AWS_REGISTER_IP&&(e.registerIp=process.env.AWS_REGISTER_IP),process.env.AWS_VIRTUAL_IP&&(e.virtualIp=process.env.AWS_VIRTUAL_IP),process.env.AWS_PREFER_SAME_LAN_IP&&(e.preferSameLanIp=P(process.env.AWS_PREFER_SAME_LAN_IP)??!0),process.env.AWS_REGISTER_MAX_RETRIES&&(e.maxRetries=parseInt(process.env.AWS_REGISTER_MAX_RETRIES,10)),process.env.AWS_REGISTER_RETRY_INTERVAL&&(e.retryInterval=parseInt(process.env.AWS_REGISTER_RETRY_INTERVAL,10)),e}function W(){const e=te(),r=ne(),t={...V,...e||{},...r},n=t.instanceName||N.hostname();let o=!1;return process.env.AWS_AUTO_REGISTER!==void 0?o=process.env.AWS_AUTO_REGISTER==="true":e&&(o=!0),{enabled:o,serverUrl:t.serverUrl||m,tenantId:t.tenantId||"",userKey:t.userKey||"",machineToken:t.machineToken,connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,credentialId:t.credentialId,credentialRef:t.credentialRef,credentialType:t.credentialType,projectId:t.projectId,roleName:t.roleName,workspacePath:t.workspacePath,registerAddr:t.registerAddr,registerIp:t.registerIp,virtualIp:t.virtualIp,tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,preferSameLanIp:t.preferSameLanIp!==!1,maxRetries:t.maxRetries,retryInterval:t.retryInterval}}function K(){const e=he(),r=ne(),n=(e.length>0?e:[te()||{}]).map(o=>{const s={...V,...o,...r};let i=!1;return process.env.AWS_AUTO_REGISTER!==void 0?i=process.env.AWS_AUTO_REGISTER==="true":i=e.length>0||!!(s.userKey||s.machineToken),{enabled:i,serverUrl:s.serverUrl||m,tenantId:s.tenantId||"",userId:s.userId,userKey:s.userKey||"",machineToken:s.machineToken,connectionKey:s.connectionKey,instanceName:s.instanceName||N.hostname(),instanceId:s.instanceId,credentialId:s.credentialId,credentialRef:s.credentialRef,credentialType:s.credentialType,projectId:s.projectId,roleName:s.roleName,workspacePath:s.workspacePath,registerAddr:s.registerAddr,registerIp:s.registerIp,virtualIp:s.virtualIp,tunnel:typeof s.tunnel=="boolean"?s.tunnel:void 0,preferSameLanIp:s.preferSameLanIp!==!1,maxRetries:s.maxRetries,retryInterval:s.retryInterval}});return k=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function ve(){const e=N.networkInterfaces(),r=[];for(const t of Object.keys(e)){const n=e[t];if(n)for(const o of n)o.family==="IPv4"&&!o.internal&&r.push({address:o.address,netmask:o.netmask,name:t})}return r}function z(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Ke(e,r,t){const n=z(e),o=z(r),s=z(t);return(n&s)===(o&s)}function Ue(e){try{const r=new URL(e);return r.hostname==="localhost"||r.hostname==="127.0.0.1"?"127.0.0.1":/^(\d{1,3}\.){3}\d{1,3}$/.test(r.hostname)?r.hostname:null}catch{return null}}function ke(e){return e==="127.0.0.1"}function _e(e){const r=ve();if(r.length===0)return a.warn("[AutoRegister] \u672A\u627E\u5230\u4EFB\u4F55\u975E\u5185\u90E8 IP \u5730\u5740\uFF0C\u4F7F\u7528 127.0.0.1"),"127.0.0.1";const t=Ue(e);if(t){if(ke(t))return"127.0.0.1";for(const n of r)if(Ke(n.address,t,n.netmask))return a.info(`[AutoRegister] \u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u540C\u5B50\u7F51\u7684 IP: ${n.address} (\u7F51\u5361: ${n.name}, \u5B50\u7F51\u63A9\u7801: ${n.netmask})`),n.address;a.warn(`[AutoRegister] \u672A\u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3 (${t}) \u540C\u5B50\u7F51\u7684\u672C\u673A IP\uFF0C\u4F7F\u7528 127.0.0.1`)}else a.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function Ee(e=m){return _e(e)}function se(e){const r=e||W();return!r.enabled||!r.userKey&&!r.machineToken?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function L(e){const r=de();if(se(e)){const s=e.serverUrl||m;try{const i=new URL(s);if(!e.instanceId)a.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${i.protocol}//${i.host}/tunnel/${e.instanceId}`}catch{a.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=le()||e.preferSameLanIp===!1?"127.0.0.1":Ee(e.serverUrl||m),n=e.registerAddr||e.registerIp||e.virtualIp||t;return n?/^https?:\/\//i.test(n)?n:/:[0-9]+$/.test(n)?`http://${n}`:`http://${n}:${r}`:`http://127.0.0.1:${r}`}function b(){return K()[0]||W()}function Xe(e){return Ne(e)?.serverUrl}function Ne(e){const r=String(e||"").trim();if(!r)return;for(const n of K()){if(!n?.serverUrl)continue;const o=q(n.serverUrl,y(n));if(o&&o===r)return{serverUrl:T(n.serverUrl)||n.serverUrl,userKey:n.userKey,identityKey:y(n),instanceId:n.instanceId};const s=ye(n.serverUrl);for(const i of s){if(i.accessToken!==r)continue;const c=fe(n.userKey)===i.userKeyFingerprint?n.userKey:void 0;return{serverUrl:T(n.serverUrl)||n.serverUrl,userKey:c,identityKey:c}}}const t=M();if(t.schedulerBaseUrl){const n=q(t.schedulerBaseUrl);if(n&&n===r)return{serverUrl:T(t.schedulerBaseUrl)||t.schedulerBaseUrl}}}function we(e){if(!e)return;const r=Object.entries(d.registrations).find(([,t])=>t===e);return r?F(r[0]):void 0}function Be(e,r){const t=T(e);if(!t)return;if(String(r||"").trim()){const o=d.registrations[x(t,r)];return o||void 0}const n=Object.entries(d.registrations).filter(([o])=>F(o)===t);return n.length===1?n[0][1]:void 0}function j(e,r){const t=we(r);if(t)return t;const n=g(e.serverUrl);if(n&&(n!==m||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=M(),s=g(o.schedulerBaseUrl);return o.status==="paired"&&s?s:n||m}async function $e(e){const r=e.instanceName||N.hostname(),t=e.serverUrl||m,n={tenantId:e.tenantId,userKey:e.machineToken?void 0:e.userKey,machineToken:e.machineToken,instanceId:e.instanceId,instanceName:r,hostname:N.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:L(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectId:e.projectId,roleName:e.roleName};return a.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),a.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),a.info(e.machineToken?"[AutoRegister] \u51ED\u636E: \u673A\u5668\u81EA\u6301\u51ED\u636E\uFF08machineToken\uFF09":`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),a.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await w.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}function Ce(e,r,t,n,o,s,i,c,u){const l=S();return D(l,()=>{if(!p.existsSync(l))return;const I=p.readFileSync(l,"utf-8"),h=JSON.parse(I),U=T(e);if(!U)return;let A=!1;const R=f=>T(String(f.serverUrl||f.schedulerBaseUrl||""))!==U?!1:String(t||"").trim()?String(f.userKey??"").trim()===String(t).trim():String(i||"").trim()?String(f.machineToken??"").trim()===String(i).trim():!0,v=f=>{R(f)&&(r&&f.instanceId!==r&&(f.instanceId=r,A=!0),n&&f.credentialId!==n&&(f.credentialId=n,A=!0),o&&f.credentialRef!==o&&(f.credentialRef=o,A=!0),s&&f.credentialType!==s&&(f.credentialType=s,A=!0),c&&f.tenantId!==c&&(f.tenantId=c,A=!0),u&&f.userId!==u&&(f.userId=u,A=!0))};if(Array.isArray(h.autoRegisterTargets))for(const f of h.autoRegisterTargets)f&&typeof f=="object"&&!Array.isArray(f)&&v(f);else v(h);A&&(p.writeFileSync(l,JSON.stringify(h,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e}${t?`, userKey=****${String(t).slice(-4)}`:""})`))})}async function Oe(e,r){const t=String(r||"").trim();if(!t)return;const n=y(e),o=_(e.serverUrl);if(!n||!o)return;const s=S();try{await D(s,()=>{if(!p.existsSync(s))return;const i=p.readFileSync(s,"utf-8"),c=JSON.parse(i),l=ee(c).find(I=>_(I.serverUrl||I.schedulerBaseUrl)===o&&String(I.credentialId||I.userKey||"").trim()===n);if(!l){a.warn(`[AutoRegister] \u65E0\u6CD5\u5B9A\u4F4D connectionKey \u5BF9\u5E94 target\uFF0C\u8DF3\u8FC7\u5199\u76D8: server=${o}`);return}String(l.connectionKey??"").trim()!==t&&(l.connectionKey=t,p.writeFileSync(s,JSON.stringify(c,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u5199\u5165 target connectionKey \u5230 config.json: ${s}`))})}catch(i){a.warn(`[AutoRegister] \u5199\u5165 target connectionKey \u5230 config.json \u5931\u8D25: ${i.message}`)}}async function Qe(e,r){const t=String(e||"").trim();if(!t)throw new Error("connectionKey is required");const n=_(r?.serverUrl);if(!n)throw new Error("serverUrl is required to rotate a target connectionKey");const o=S();let s;const i=await D(o,()=>{if(!p.existsSync(o))return!1;const l=JSON.parse(p.readFileSync(o,"utf-8")),I=ee(l),h=String(r?.instanceId||"").trim(),U=String(r?.credentialId||"").trim(),A=I.filter(v=>{if(_(v.serverUrl||v.schedulerBaseUrl)!==n)return!1;if(h&&String(v.instanceId||"").trim()===h)return!0;if(U){const E=String(v.credentialId||"").trim(),ae=String(v.userKey||"").trim();if(E===U||ae===U)return!0}return!h&&!U&&I.filter(E=>_(E.serverUrl||E.schedulerBaseUrl)===n).length===1});if(A.length!==1)throw new Error(`\u65E0\u6CD5\u552F\u4E00\u5B9A\u4F4D connectionKey \u5BF9\u5E94 target\uFF08server=${n}, matches=${A.length}\uFF09`);const R=A[0];return String(R.connectionKey??"").trim()===t?!1:(s={targetKey:x(String(R.serverUrl??R.schedulerBaseUrl??""),R.credentialId||R.userKey),previousConnectionKey:String(R.connectionKey??"").trim()},R.connectionKey=t,String(l.connectionKey??"").trim()&&delete l.connectionKey,p.writeFileSync(o,JSON.stringify(l,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u8F6E\u6362 target connectionKey \u5E76\u5199\u56DE config.json: ${o}`),!0)}),c=!!process.env.AWS_BRIDGE_CONNECTION_KEY;c&&i&&(process.env.AWS_BRIDGE_CONNECTION_KEY=t,a.warn("[AutoRegister] \u68C0\u6D4B\u5230 AWS_BRIDGE_CONNECTION_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u5DF2\u540C\u6B65\u66F4\u65B0\u5F53\u524D\u8FDB\u7A0B\uFF1B\u91CD\u542F bridge \u540E\u4F1A\u56DE\u5230\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u65E7\u503C\uFF0C\u8BF7\u4E00\u5E76\u4FEE\u6539\u90E8\u7F72\u914D\u7F6E"));const u=K().map(l=>String(l.connectionKey||"").trim()).filter(l=>l.length>0);return k=i?[...new Set([t,...u])]:[...new Set(u)],i&&s&&s.previousConnectionKey!==t&&B.set(s.targetKey,{key:s.previousConnectionKey,expiresAt:Date.now()+Ie}),{envOverrideWarning:c,rotated:i}}async function ie(e){let r=0;for(const t of e)String(t.connectionKey||"").trim()||(t.connectionKey=`awsb_${ce(24).toString("base64url")}`,r+=1,k.includes(t.connectionKey)||(k=[...k,t.connectionKey]),await Oe(t,t.connectionKey));r!==0&&a.info(`[AutoRegister] ${r} \u4E2A\u76EE\u6807\u7F3A\u5C11 connectionKey\uFF0C\u5DF2\u6309 target \u72EC\u7ACB\u751F\u6210\u5E76\u5199\u5165 config.json`)}function oe(e){if(!e)return;const r=Object.entries(d.registrations).find(([,s])=>s===e);if(!r)return;const t=r[0],n=F(t),o=K().find(s=>C(s)===t);return{serverUrl:n,identityKey:o?y(o):void 0,targetKey:t}}async function be(e){const r=oe(e),t=r?.serverUrl||j(b(),e),n=G(t,r?.identityKey);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await w.post(`${t}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function Ze(e){const r=e?[{...W(),...e}]:K();await ie(r);const n=(r.length>1?await Promise.all(r.map(s=>J(s))):[await J(r[0]||W())]).every(Boolean),o=r.every(s=>!s.enabled||!s.userKey&&!s.machineToken);if(!e&&(n||o)){const{stopTunnelClientsExcept:s}=await import("./tunnel-client.js");s(r.filter(i=>!!i.serverUrl).map(i=>x(i.serverUrl,y(i))))}return n}async function J(e,r){if(!e.enabled)return a.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),a.info(`[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ${S()} \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true`),!1;if(!e.userKey&&!e.machineToken)return a.error("[AutoRegister] \u7F3A\u5C11\u6CE8\u518C\u51ED\u636E\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216 machineToken\uFF0C\u4E5F\u53EF\u7528 `awsb pair --ticket ...` \u914D\u5BF9\uFF09"),!1;String(e.connectionKey||"").trim()||await ie([e]);const t=se(e);t&&!e.instanceId&&(e.instanceId=`bridge-${ue().replace(/-/g,"").slice(0,12)}`,a.info(`[AutoRegister] \u96A7\u9053\u6A21\u5F0F\uFF1A\u5DF2\u9884\u58F0\u660E instanceId=${e.instanceId}`));const n=r?.backgroundRetry?1:e.maxRetries??3,o=e.retryInterval??2e3;for(let s=1;s<=n;s++){try{d.lastAttempt=new Date;const i=await $e(e);if(i.success){if(d.registered=!0,d.instanceId=i.instanceId,i.credentialId&&(e.credentialId=i.credentialId,e.credentialType=i.credentialType),e.credentialRef=i.credentialRef,i.tenantId&&(e.tenantId=i.tenantId),i.userId&&(e.userId=i.userId),i.instanceId&&(d.registrations[C(e)]=i.instanceId,e.instanceId=i.instanceId),d.error=void 0,a.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),a.info(`[AutoRegister] \u5B9E\u4F8B ID: ${i.instanceId}`),a.info(`[AutoRegister] \u6D88\u606F: ${i.message}`),a.info(`[AutoRegister] \u66F4\u65B0: ${i.isUpdate?"\u662F":"\u5426"}`),i.runtimeAccessToken)try{const c=e.serverUrl||m;Y({accessToken:i.runtimeAccessToken,instanceId:i.instanceId,userId:i.userId,schedulerBaseUrl:c,userKey:y(e)});const u=T(i.serverUrl);u&&u!==T(c)&&(H({serverBaseUrl:u,accessToken:i.runtimeAccessToken,userKey:y(e)}),a.info(`[AutoRegister] \u68C0\u6D4B\u5230 server \u4E0B\u53D1\u5730\u5740(${u})\u4E0E\u8FDE\u63A5\u5730\u5740(${c})\u4E0D\u540C\uFF0C\u5DF2\u6309\u4E24\u4E2A\u5730\u5740\u5206\u522B\u4FDD\u5B58 scoped token`)),a.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),i.instanceId&&Ce(e.serverUrl||m,i.instanceId,e.userKey,i.credentialId,i.credentialRef,i.credentialType,e.machineToken,i.tenantId,i.userId).catch(l=>{a.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${l.message}`)})}catch(c){const u=c;return a.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${u.message}`),d.error=u.message,!1}else a.warn("[AutoRegister] \u6CE8\u518C\u54CD\u5E94\u672A\u5305\u542B runtime access token\uFF0CMCP \u5C06\u65E0\u6CD5\u8BBF\u95EE\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8BF7\u68C0\u67E5 server \u7684 bridge \u5B9E\u4F8B token \u9881\u53D1\u903B\u8F91");if(t&&i.instanceId&&i.runtimeAccessToken){const{startTunnelClient:c}=await import("./tunnel-client.js"),u=e.serverUrl||m;c(u,i.instanceId,y(e)),a.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${u}, instanceId=${i.instanceId}${e.userKey?`, userKey=****${e.userKey.slice(-4)}`:""}`)}return!0}else a.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${i.message}`),d.error=i.message}catch(i){const c=i,u=c.response?.data?JSON.stringify(c.response.data):c.message;a.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${s}/${n}): ${u}`),d.error=u}s<n&&(a.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(i=>setTimeout(i,o)))}return r?.backgroundRetry?a.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||m}): ${d.error||"\u672A\u77E5"}`):(a.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),a.error("[AutoRegister] ========================================"),a.error("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u5DF2\u542F\u7528\u4F46\u6CE8\u518C\u5931\u8D25\uFF0C\u5B9E\u4F8B\u6682\u65F6\u65E0\u6CD5\u6B63\u5E38\u5DE5\u4F5C"),a.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),a.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),a.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),a.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),a.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||m}`),a.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${d.error||"\u672A\u77E5"}`),a.error("[AutoRegister] ========================================"),a.error(`[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A${S()} \u6216\u73AF\u5883\u53D8\u91CF`),a.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(Q()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),Z(e),!1}async function er(){const e=b();if(!e.userKey&&!e.machineToken)return{success:!1,error:"userKey or machineToken is required for runtime token refresh"};const r=pe(),t=j(e),n=L(e);try{const o=await w.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.machineToken?void 0:e.userKey,machineToken:e.machineToken,instanceId:r.instanceId,instanceName:e.instanceName,runtimeBridgeBaseUrl:n},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!o.data.success||!o.data.runtimeAccessToken)return{success:!1,error:o.data.message||"scheduler did not return runtimeAccessToken"};const i=G(t,y(e))!==o.data.runtimeAccessToken;return Y({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey||e.credentialId||"",schedulerBaseUrl:t,userKey:y(e)}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:i}}catch(o){const s=o;return{success:!1,error:s.response?.data?JSON.stringify(s.response.data):s.message}}}async function rr(e,r){const t=_(e);if(!t)return{success:!1,error:"serverUrl is required for runtime token refresh"};const n=K().filter(u=>_(u.serverUrl)===t),o=String(r||"").trim(),s=o?n.find(u=>String(u.userKey||"").trim()===o||String(u.credentialId||"").trim()===o):n.length===1?n[0]:void 0;if(!s)return{success:!1,error:o?"no auto-register target matches this identity":"multiple auto-register targets share this serverUrl; specify the target identity"};if(!s.userKey&&!s.machineToken)return{success:!1,error:"userKey or machineToken is required for runtime token refresh"};const i={instanceId:Be(t,y(s))},c=L(s);try{const u=await w.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:s.tenantId,userKey:s.machineToken?void 0:s.userKey,machineToken:s.machineToken,instanceId:i.instanceId,instanceName:s.instanceName,runtimeBridgeBaseUrl:c},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!u.data.success||!u.data.runtimeAccessToken)return{success:!1,error:u.data.message||"scheduler did not return runtimeAccessToken"};const I=G(t,y(s))!==u.data.runtimeAccessToken;return H({serverBaseUrl:t,accessToken:u.data.runtimeAccessToken,userKey:y(s)}),{success:!0,runtimeAccessToken:u.data.runtimeAccessToken,updated:I}}catch(u){const l=u;return{success:!1,error:l.response?.data?JSON.stringify(l.response.data):l.message}}}async function tr(){if(!d.registered||!d.instanceId)return a.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;try{const e=await be(d.instanceId);if(e.success){a.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${d.instanceId}`);const r=oe(d.instanceId),t=r?.serverUrl||j(b(),d.instanceId);M().schedulerBaseUrl&&me(t,r?.identityKey),ge();try{const{stopTunnelClientForTarget:o}=await import("./tunnel-client.js");o(t,r?.identityKey??y(b()))}catch(o){a.warn(`[AutoRegister] \u6CE8\u9500\u540E\u505C\u6B62\u96A7\u9053\u5931\u8D25: ${o.message}`)}if(!r)d.registered=!1,d.instanceId=void 0;else{delete d.registrations[r.targetKey];const o=Object.entries(d.registrations);d.registered=o.length>0,d.instanceId=o[o.length-1]?.[1]}return!0}else return a.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${e.message}`),!1}catch(e){const r=e;return a.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${r.message}`),!1}}function nr(){return{...d}}function sr(){return d.registered}function ir(){return d.instanceId}async function or(){const e=b(),r=d.instanceId,t=j(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=L(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return a.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{a.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),a.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),a.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await w.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(a.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),a.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(a.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const s=o,i=s.response?.data?JSON.stringify(s.response.data):s.message;return a.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${i}`),{success:!1,error:i}}}export{Ze as autoRegister,or as bridgeRestartCleanup,He as cancelBackgroundRegisterRetries,Se as collapseDuplicatedSecret,qe as findConfigsByConnectionKey,S as getAutoRegisterConfigFilePath,Je as getConfiguredConnectionKeys,Ye as getCurrentConfiguredConnectionKey,ir as getInstanceId,nr as getRegistrationState,sr as isRegistered,se as isTunnelConfig,Ve as listConfiguredTargetSummaries,W as loadConfig,K as loadConfigs,b as loadPrimaryLifecycleConfig,Qe as replaceConfiguredConnectionKey,er as requestRuntimeAccessTokenRefresh,rr as requestRuntimeAccessTokenRefreshForServer,Be as resolveInstanceIdForTarget,L as resolveRuntimeBridgeBaseUrl,Xe as resolveSchedulerBaseUrlFromToken,Ne as resolveTargetIdentityFromToken,tr as unregister};
|
|
1
|
+
import{randomBytes as ce}from"node:crypto";import p from"node:fs";import w from"node:os";import $ from"axios";import{v4 as ue}from"uuid";import{getActiveBridgeHttpPort as de,getAutoRegisterConfigFilePath as T,isDesktopAppMode as le,schedulerBaseUrl as m}from"../config.js";import{runExclusiveConfigWrite as D}from"../utils/config-write-queue.js";import{logger as a}from"../utils/logger.js";import{buildTargetKey as x,fingerprintUserKey as fe,serverUrlOfTargetKey as F}from"../utils/target-identity.js";import{clearRuntimeBinding as ge,clearScopedRuntimeAccessToken as me,getRuntimeAccessToken as G,getRuntimeBindingPublicState as pe,getScopedRuntimeAccessToken as Y,listScopedRuntimeAccessTokens as ye,loadRuntimeBinding as M,normalizeSchedulerBaseUrl as S,saveRuntimeBinding as H,saveScopedRuntimeAccessToken as V}from"./runtime-binding.js";const X={enabled:!1,serverUrl:m,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3},d={registered:!1,registrations:{}};let k=[];const Ie=10*60*1e3,B=new Map;function Q(){const e=Date.now();for(const[r,t]of B)t.expiresAt<=e&&B.delete(r)}function Je(){Q();const e=[...k];for(const r of B.values())r.key&&!e.includes(r.key)&&e.push(r.key);return e}function qe(e){const r=String(e||"").trim();if(!r)return[];const t=h(),n=t.filter(i=>String(i.connectionKey||"").trim()===r);if(n.length>0)return n;Q();const o=new Set;for(const[i,s]of B)s.key===r&&o.add(i);return o.size===0?[]:t.filter(i=>o.has(N(i)))}function Ye(){return k[0]??""}const Ae=3e4;function Z(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:Ae}const C=new Map;function ee(e){const r=e.serverUrl||m,t=N(e)||r;if(C.has(t))return;const n=setTimeout(()=>{C.delete(t),Re(e,t)},Z());n.unref?.(),C.set(t,n)}async function Re(e,r){try{const t=h().find(n=>N(n)===r)||e;await q(t,{backgroundRetry:!0})}catch(t){a.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),ee(e)}}function He(){for(const e of C.values())clearTimeout(e);C.clear()}function Te(e){const r=e.trim();if(r.length<8)return r;for(let t=1;t<=r.length/2;t++){if(r.length%t!==0)continue;const n=r.slice(0,t);let o=!0;for(let i=t;i<r.length;i+=t)if(r.slice(i,i+t)!==n){o=!1;break}if(o&&n.length<r.length)return n}return r}function Se(e){const r=g(e);if(!r)return r;const t=Te(r);return t!==r&&a.warn(`[AutoRegister] userKey \u7591\u4F3C\u88AB\u590D\u5236\u4E86\u4E24\u904D\uFF08\u957F\u5EA6 ${r.length} \u2192 ${t.length}\uFF09\uFF0C\u5DF2\u81EA\u52A8\u53BB\u91CD\u3002\u8BF7\u68C0\u67E5\u914D\u7F6E\u6587\u4EF6\u662F\u5426\u91CD\u590D\u7C98\u8D34\u3002`),t}function g(e){return e==null?void 0:String(e).trim()||void 0}function _(e){return S(e)}function re(e){return Array.isArray(e.autoRegisterTargets)?e.autoRegisterTargets.filter(r=>!!(r&&typeof r=="object"&&!Array.isArray(r))):[]}function N(e){const r=e?.serverUrl||m;return x(r,e?.credentialId||e?.userKey)}function y(e){return e?.credentialId||e?.userKey}function P(e){if(typeof e=="boolean")return e;if(typeof e=="string"){const r=e.trim().toLowerCase();if(["true","1","yes","y"].includes(r))return!0;if(["false","0","no","n"].includes(r))return!1}}function te(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function O(e){const r=g(e.serverUrl)||g(e.schedulerBaseUrl),t=g(e.connectionKey)||g(e.password);return{enabled:P(e.enabled),serverUrl:r,tenantId:g(e.tenantId),userId:g(e.userId),userKey:Se(e.userKey),machineToken:g(e.machineToken),connectionKey:t,instanceName:g(e.instanceName),instanceId:g(e.instanceId),credentialId:g(e.credentialId),credentialRef:g(e.credentialRef),credentialType:g(e.credentialType),projectId:g(e.projectId),roleName:g(e.roleName),workspacePath:g(e.workspacePath),registerAddr:g(e.registerAddr),registerIp:g(e.registerIp),virtualIp:g(e.virtualIp),tunnel:P(e.tunnel),preferSameLanIp:P(e.preferSameLanIp),maxRetries:te(e.maxRetries),retryInterval:te(e.retryInterval)}}function ne(){const e=T();try{if(!p.existsSync(e))return a.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=p.readFileSync(e,"utf-8"),t=JSON.parse(r);return a.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),O(t)}catch(r){const t=r;return a.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function he(){const e=T();try{if(!p.existsSync(e))return[];const r=p.readFileSync(e,"utf-8"),t=JSON.parse(r),n=O(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey||n.machineToken?[n]:[]:o.filter(i=>!!(i&&typeof i=="object"&&!Array.isArray(i))).map(i=>{const s=O(i);return{...n,...s,machineToken:s.machineToken||(s.userKey?void 0:n.machineToken),connectionKey:s.connectionKey}})}catch(r){const t=r;return a.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function Ve(){const e=T();if(!p.existsSync(e))return[];let r;try{r=JSON.parse(p.readFileSync(e,"utf-8"))}catch(s){return a.warn(`[AutoRegister] \u8BFB\u53D6 config.json \u5931\u8D25\uFF08targets \u6458\u8981\uFF09: ${s.message}`),[]}const t=O(r),n=Array.isArray(r.autoRegisterTargets)?r.autoRegisterTargets:[],o=n.length>0?n.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const c=O(s);return{...t,...c,machineToken:c.machineToken||(c.userKey?void 0:t.machineToken),connectionKey:c.connectionKey}}):t.userKey||t.machineToken?[t]:[],i=s=>{const c=String(s||"").trim();return c?c.length<=8?"****":`${c.slice(0,8)}****${c.slice(-4)}`:""};return o.map((s,c)=>({index:c,serverUrl:s.serverUrl||"",instanceName:s.instanceName||"",registerAddr:s.registerAddr||s.registerIp||s.virtualIp||"",tunnel:typeof s.tunnel=="boolean"?s.tunnel:null,userKeyMasked:i(s.userKey),machineTokenSet:!!String(s.machineToken||"").trim(),credentialType:s.credentialType||"",credentialId:s.credentialId||"",credentialRef:s.credentialRef||"",connectionKeySet:!!String(s.connectionKey||"").trim()}))}function se(){const e={};return process.env.AWS_AUTO_REGISTER==="true"&&(e.enabled=!0),process.env.AWS_RUNTIME_SCHEDULER_BASE_URL&&(e.serverUrl=process.env.AWS_RUNTIME_SCHEDULER_BASE_URL),process.env.AWS_TENANT_ID&&(e.tenantId=process.env.AWS_TENANT_ID),process.env.AWS_USER_KEY&&(e.userKey=process.env.AWS_USER_KEY),process.env.AWS_BRIDGE_MACHINE_TOKEN&&(e.machineToken=process.env.AWS_BRIDGE_MACHINE_TOKEN),process.env.AWS_BRIDGE_CONNECTION_KEY&&(e.connectionKey=process.env.AWS_BRIDGE_CONNECTION_KEY),process.env.AWS_INSTANCE_NAME&&(e.instanceName=process.env.AWS_INSTANCE_NAME),process.env.AWS_PROJECT_NAME&&(e.projectId=process.env.AWS_PROJECT_NAME),process.env.AWS_ROLE_NAME&&(e.roleName=process.env.AWS_ROLE_NAME),process.env.AWS_WORKSPACE_PATH&&(e.workspacePath=process.env.AWS_WORKSPACE_PATH),process.env.AWS_REGISTER_ADDR&&(e.registerAddr=process.env.AWS_REGISTER_ADDR),process.env.AWS_REGISTER_IP&&(e.registerIp=process.env.AWS_REGISTER_IP),process.env.AWS_VIRTUAL_IP&&(e.virtualIp=process.env.AWS_VIRTUAL_IP),process.env.AWS_PREFER_SAME_LAN_IP&&(e.preferSameLanIp=P(process.env.AWS_PREFER_SAME_LAN_IP)??!0),process.env.AWS_REGISTER_MAX_RETRIES&&(e.maxRetries=parseInt(process.env.AWS_REGISTER_MAX_RETRIES,10)),process.env.AWS_REGISTER_RETRY_INTERVAL&&(e.retryInterval=parseInt(process.env.AWS_REGISTER_RETRY_INTERVAL,10)),e}function W(){const e=ne(),r=se(),t={...X,...e||{},...r},n=t.instanceName||w.hostname();let o=!1;return process.env.AWS_AUTO_REGISTER!==void 0?o=process.env.AWS_AUTO_REGISTER==="true":e&&(o=!0),{enabled:o,serverUrl:t.serverUrl||m,tenantId:t.tenantId||"",userKey:t.userKey||"",machineToken:t.machineToken,connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,credentialId:t.credentialId,credentialRef:t.credentialRef,credentialType:t.credentialType,projectId:t.projectId,roleName:t.roleName,workspacePath:t.workspacePath,registerAddr:t.registerAddr,registerIp:t.registerIp,virtualIp:t.virtualIp,tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,preferSameLanIp:t.preferSameLanIp!==!1,maxRetries:t.maxRetries,retryInterval:t.retryInterval}}function h(){const e=he(),r=se(),n=(e.length>0?e:[ne()||{}]).map(o=>{const i={...X,...o,...r};let s=!1;return process.env.AWS_AUTO_REGISTER!==void 0?s=process.env.AWS_AUTO_REGISTER==="true":s=e.length>0||!!(i.userKey||i.machineToken),{enabled:s,serverUrl:i.serverUrl||m,tenantId:i.tenantId||"",userId:i.userId,userKey:i.userKey||"",machineToken:i.machineToken,connectionKey:i.connectionKey,instanceName:i.instanceName||w.hostname(),instanceId:i.instanceId,credentialId:i.credentialId,credentialRef:i.credentialRef,credentialType:i.credentialType,projectId:i.projectId,roleName:i.roleName,workspacePath:i.workspacePath,registerAddr:i.registerAddr,registerIp:i.registerIp,virtualIp:i.virtualIp,tunnel:typeof i.tunnel=="boolean"?i.tunnel:void 0,preferSameLanIp:i.preferSameLanIp!==!1,maxRetries:i.maxRetries,retryInterval:i.retryInterval}});return k=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function ve(){const e=w.networkInterfaces(),r=[];for(const t of Object.keys(e)){const n=e[t];if(n)for(const o of n)o.family==="IPv4"&&!o.internal&&r.push({address:o.address,netmask:o.netmask,name:t})}return r}function z(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Ke(e,r,t){const n=z(e),o=z(r),i=z(t);return(n&i)===(o&i)}function Ue(e){try{const r=new URL(e);return r.hostname==="localhost"||r.hostname==="127.0.0.1"?"127.0.0.1":/^(\d{1,3}\.){3}\d{1,3}$/.test(r.hostname)?r.hostname:null}catch{return null}}function ke(e){return e==="127.0.0.1"}function _e(e){const r=ve();if(r.length===0)return a.warn("[AutoRegister] \u672A\u627E\u5230\u4EFB\u4F55\u975E\u5185\u90E8 IP \u5730\u5740\uFF0C\u4F7F\u7528 127.0.0.1"),"127.0.0.1";const t=Ue(e);if(t){if(ke(t))return"127.0.0.1";for(const n of r)if(Ke(n.address,t,n.netmask))return a.info(`[AutoRegister] \u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u540C\u5B50\u7F51\u7684 IP: ${n.address} (\u7F51\u5361: ${n.name}, \u5B50\u7F51\u63A9\u7801: ${n.netmask})`),n.address;a.warn(`[AutoRegister] \u672A\u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3 (${t}) \u540C\u5B50\u7F51\u7684\u672C\u673A IP\uFF0C\u4F7F\u7528 127.0.0.1`)}else a.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function Ee(e=m){return _e(e)}function ie(e){const r=e||W();return!r.enabled||!r.userKey&&!r.machineToken?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function L(e){const r=de();if(ie(e)){const i=e.serverUrl||m;try{const s=new URL(i);if(!e.instanceId)a.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${s.protocol}//${s.host}/tunnel/${e.instanceId}`}catch{a.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=le()||e.preferSameLanIp===!1?"127.0.0.1":Ee(e.serverUrl||m),n=e.registerAddr||e.registerIp||e.virtualIp||t;return n?/^https?:\/\//i.test(n)?n:/:[0-9]+$/.test(n)?`http://${n}`:`http://${n}:${r}`:`http://127.0.0.1:${r}`}function b(){return h()[0]||W()}function Xe(e){return Ne(e)?.serverUrl}function Ne(e){const r=String(e||"").trim();if(!r)return;for(const n of h()){if(!n?.serverUrl)continue;const o=Y(n.serverUrl,y(n));if(o&&o===r)return{serverUrl:S(n.serverUrl)||n.serverUrl,userKey:n.userKey,identityKey:y(n),instanceId:n.instanceId};const i=ye(n.serverUrl);for(const s of i){if(s.accessToken!==r)continue;const c=fe(n.userKey)===s.userKeyFingerprint?n.userKey:void 0;return{serverUrl:S(n.serverUrl)||n.serverUrl,userKey:c,identityKey:c}}}const t=M();if(t.schedulerBaseUrl){const n=Y(t.schedulerBaseUrl);if(n&&n===r)return{serverUrl:S(t.schedulerBaseUrl)||t.schedulerBaseUrl}}}function we(e){if(!e)return;const r=Object.entries(d.registrations).find(([,t])=>t===e);return r?F(r[0]):void 0}function $e(e,r){const t=S(e);if(!t)return;if(String(r||"").trim()){const o=d.registrations[x(t,r)];return o||void 0}const n=Object.entries(d.registrations).filter(([o])=>F(o)===t);return n.length===1?n[0][1]:void 0}function j(e,r){const t=we(r);if(t)return t;const n=g(e.serverUrl);if(n&&(n!==m||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=M(),i=g(o.schedulerBaseUrl);return o.status==="paired"&&i?i:n||m}async function Be(e){const r=e.instanceName||w.hostname(),t=e.serverUrl||m,n={tenantId:e.tenantId,userKey:e.machineToken?void 0:e.userKey,machineToken:e.machineToken,instanceId:e.instanceId,instanceName:r,hostname:w.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:L(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectId:e.projectId,roleName:e.roleName};return a.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),a.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),a.info(e.machineToken?"[AutoRegister] \u51ED\u636E: \u673A\u5668\u81EA\u6301\u51ED\u636E\uFF08machineToken\uFF09":`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),a.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await $.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}function Ce(e,r,t,n,o,i,s,c,u){const l=T();return D(l,()=>{if(!p.existsSync(l))return;const I=p.readFileSync(l,"utf-8"),v=JSON.parse(I),U=S(e);if(!U)return;let A=!1;const R=f=>S(String(f.serverUrl||f.schedulerBaseUrl||""))!==U?!1:String(t||"").trim()?String(f.userKey??"").trim()===String(t).trim():String(s||"").trim()?String(f.machineToken??"").trim()===String(s).trim():!0,K=f=>{R(f)&&(r&&f.instanceId!==r&&(f.instanceId=r,A=!0),n&&f.credentialId!==n&&(f.credentialId=n,A=!0),o&&f.credentialRef!==o&&(f.credentialRef=o,A=!0),i&&f.credentialType!==i&&(f.credentialType=i,A=!0),c&&f.tenantId!==c&&(f.tenantId=c,A=!0),u&&f.userId!==u&&(f.userId=u,A=!0))};if(Array.isArray(v.autoRegisterTargets))for(const f of v.autoRegisterTargets)f&&typeof f=="object"&&!Array.isArray(f)&&K(f);else K(v);A&&(p.writeFileSync(l,JSON.stringify(v,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e}${t?`, userKey=****${String(t).slice(-4)}`:""})`))})}async function Oe(e,r){const t=String(r||"").trim();if(!t)return;const n=y(e),o=_(e.serverUrl);if(!n||!o)return;const i=T();try{await D(i,()=>{if(!p.existsSync(i))return;const s=p.readFileSync(i,"utf-8"),c=JSON.parse(s),l=re(c).find(I=>_(I.serverUrl||I.schedulerBaseUrl)===o&&String(I.credentialId||I.userKey||"").trim()===n);if(!l){a.warn(`[AutoRegister] \u65E0\u6CD5\u5B9A\u4F4D connectionKey \u5BF9\u5E94 target\uFF0C\u8DF3\u8FC7\u5199\u76D8: server=${o}`);return}String(l.connectionKey??"").trim()!==t&&(l.connectionKey=t,p.writeFileSync(i,JSON.stringify(c,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u5199\u5165 target connectionKey \u5230 config.json: ${i}`))})}catch(s){a.warn(`[AutoRegister] \u5199\u5165 target connectionKey \u5230 config.json \u5931\u8D25: ${s.message}`)}}async function Qe(e,r){const t=String(e||"").trim();if(!t)throw new Error("connectionKey is required");const n=_(r?.serverUrl);if(!n)throw new Error("serverUrl is required to rotate a target connectionKey");const o=T();let i;const s=await D(o,()=>{if(!p.existsSync(o))return!1;const l=JSON.parse(p.readFileSync(o,"utf-8")),I=re(l),v=String(r?.instanceId||"").trim(),U=String(r?.credentialId||"").trim(),A=I.filter(K=>{if(_(K.serverUrl||K.schedulerBaseUrl)!==n)return!1;if(v&&String(K.instanceId||"").trim()===v)return!0;if(U){const E=String(K.credentialId||"").trim(),ae=String(K.userKey||"").trim();if(E===U||ae===U)return!0}return!v&&!U&&I.filter(E=>_(E.serverUrl||E.schedulerBaseUrl)===n).length===1});if(A.length!==1)throw new Error(`\u65E0\u6CD5\u552F\u4E00\u5B9A\u4F4D connectionKey \u5BF9\u5E94 target\uFF08server=${n}, matches=${A.length}\uFF09`);const R=A[0];return String(R.connectionKey??"").trim()===t?!1:(i={targetKey:x(String(R.serverUrl??R.schedulerBaseUrl??""),R.credentialId||R.userKey),previousConnectionKey:String(R.connectionKey??"").trim()},R.connectionKey=t,String(l.connectionKey??"").trim()&&delete l.connectionKey,p.writeFileSync(o,JSON.stringify(l,null,2),"utf-8"),a.info(`[AutoRegister] \u5DF2\u8F6E\u6362 target connectionKey \u5E76\u5199\u56DE config.json: ${o}`),!0)}),c=!!process.env.AWS_BRIDGE_CONNECTION_KEY;c&&s&&(process.env.AWS_BRIDGE_CONNECTION_KEY=t,a.warn("[AutoRegister] \u68C0\u6D4B\u5230 AWS_BRIDGE_CONNECTION_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u5DF2\u540C\u6B65\u66F4\u65B0\u5F53\u524D\u8FDB\u7A0B\uFF1B\u91CD\u542F bridge \u540E\u4F1A\u56DE\u5230\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u65E7\u503C\uFF0C\u8BF7\u4E00\u5E76\u4FEE\u6539\u90E8\u7F72\u914D\u7F6E"));const u=h().map(l=>String(l.connectionKey||"").trim()).filter(l=>l.length>0);return k=s?[...new Set([t,...u])]:[...new Set(u)],s&&i&&i.previousConnectionKey!==t&&B.set(i.targetKey,{key:i.previousConnectionKey,expiresAt:Date.now()+Ie}),{envOverrideWarning:c,rotated:s}}async function oe(e){let r=0;for(const t of e)String(t.connectionKey||"").trim()||(t.connectionKey=`awsb_${ce(24).toString("base64url")}`,r+=1,k.includes(t.connectionKey)||(k=[...k,t.connectionKey]),await Oe(t,t.connectionKey));r!==0&&a.info(`[AutoRegister] ${r} \u4E2A\u76EE\u6807\u7F3A\u5C11 connectionKey\uFF0C\u5DF2\u6309 target \u72EC\u7ACB\u751F\u6210\u5E76\u5199\u5165 config.json`)}function J(e){if(!e)return;const r=Object.entries(d.registrations).find(([,i])=>i===e);if(!r)return;const t=r[0],n=F(t),o=h().find(i=>N(i)===t);return{serverUrl:n,identityKey:o?y(o):void 0,targetKey:t}}async function be(e){const r=J(e),t=r?.serverUrl||j(b(),e),n=G(t,r?.identityKey);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await $.post(`${t}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function Ze(e){const r=e?[{...W(),...e}]:h();await oe(r);const n=(r.length>1?await Promise.all(r.map(i=>q(i))):[await q(r[0]||W())]).every(Boolean),o=r.every(i=>!i.enabled||!i.userKey&&!i.machineToken);if(!e&&(n||o)){const{stopTunnelClientsExcept:i}=await import("./tunnel-client.js");i(r.filter(s=>!!s.serverUrl).map(s=>x(s.serverUrl,y(s))))}return n}async function q(e,r){if(!e.enabled)return a.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),a.info(`[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ${T()} \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true`),!1;if(!e.userKey&&!e.machineToken)return a.error("[AutoRegister] \u7F3A\u5C11\u6CE8\u518C\u51ED\u636E\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216 machineToken\uFF0C\u4E5F\u53EF\u7528 `awsb pair --ticket ...` \u914D\u5BF9\uFF09"),!1;String(e.connectionKey||"").trim()||await oe([e]);const t=ie(e);t&&!e.instanceId&&(e.instanceId=`bridge-${ue().replace(/-/g,"").slice(0,12)}`,a.info(`[AutoRegister] \u96A7\u9053\u6A21\u5F0F\uFF1A\u5DF2\u9884\u58F0\u660E instanceId=${e.instanceId}`));const n=r?.backgroundRetry?1:e.maxRetries??3,o=e.retryInterval??2e3;for(let i=1;i<=n;i++){try{d.lastAttempt=new Date;const s=await Be(e);if(s.success){if(d.registered=!0,d.instanceId=s.instanceId,s.credentialId&&(e.credentialId=s.credentialId,e.credentialType=s.credentialType),e.credentialRef=s.credentialRef,s.tenantId&&(e.tenantId=s.tenantId),s.userId&&(e.userId=s.userId),s.instanceId&&(d.registrations[N(e)]=s.instanceId,e.instanceId=s.instanceId),d.error=void 0,a.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),a.info(`[AutoRegister] \u5B9E\u4F8B ID: ${s.instanceId}`),a.info(`[AutoRegister] \u6D88\u606F: ${s.message}`),a.info(`[AutoRegister] \u66F4\u65B0: ${s.isUpdate?"\u662F":"\u5426"}`),s.runtimeAccessToken)try{const c=e.serverUrl||m;H({accessToken:s.runtimeAccessToken,instanceId:s.instanceId,userId:s.userId,schedulerBaseUrl:c,userKey:y(e)});const u=S(s.serverUrl);u&&u!==S(c)&&(V({serverBaseUrl:u,accessToken:s.runtimeAccessToken,userKey:y(e)}),a.info(`[AutoRegister] \u68C0\u6D4B\u5230 server \u4E0B\u53D1\u5730\u5740(${u})\u4E0E\u8FDE\u63A5\u5730\u5740(${c})\u4E0D\u540C\uFF0C\u5DF2\u6309\u4E24\u4E2A\u5730\u5740\u5206\u522B\u4FDD\u5B58 scoped token`)),a.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),s.instanceId&&Ce(e.serverUrl||m,s.instanceId,e.userKey,s.credentialId,s.credentialRef,s.credentialType,e.machineToken,s.tenantId,s.userId).catch(l=>{a.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${l.message}`)})}catch(c){const u=c;return a.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${u.message}`),d.error=u.message,!1}else a.warn("[AutoRegister] \u6CE8\u518C\u54CD\u5E94\u672A\u5305\u542B runtime access token\uFF0CMCP \u5C06\u65E0\u6CD5\u8BBF\u95EE\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8BF7\u68C0\u67E5 server \u7684 bridge \u5B9E\u4F8B token \u9881\u53D1\u903B\u8F91");if(t&&s.instanceId&&s.runtimeAccessToken){const{startTunnelClient:c}=await import("./tunnel-client.js"),u=e.serverUrl||m;c(u,s.instanceId,y(e)),a.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${u}, instanceId=${s.instanceId}${e.userKey?`, userKey=****${e.userKey.slice(-4)}`:""}`)}return!0}else a.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${s.message}`),d.error=s.message}catch(s){const c=s,u=c.response?.data?JSON.stringify(c.response.data):c.message;a.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${i}/${n}): ${u}`),d.error=u}i<n&&(a.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(s=>setTimeout(s,o)))}return r?.backgroundRetry?a.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||m}): ${d.error||"\u672A\u77E5"}`):(a.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),a.error("[AutoRegister] ========================================"),a.error("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u5DF2\u542F\u7528\u4F46\u6CE8\u518C\u5931\u8D25\uFF0C\u5B9E\u4F8B\u6682\u65F6\u65E0\u6CD5\u6B63\u5E38\u5DE5\u4F5C"),a.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),a.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),a.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),a.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),a.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||m}`),a.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${d.error||"\u672A\u77E5"}`),a.error("[AutoRegister] ========================================"),a.error(`[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A${T()} \u6216\u73AF\u5883\u53D8\u91CF`),a.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(Z()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),ee(e),!1}async function er(){const e=b();if(!e.userKey&&!e.machineToken)return{success:!1,error:"userKey or machineToken is required for runtime token refresh"};const r=pe(),t=j(e),n=L(e);try{const o=await $.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.machineToken?void 0:e.userKey,machineToken:e.machineToken,instanceId:r.instanceId,instanceName:e.instanceName,runtimeBridgeBaseUrl:n},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!o.data.success||!o.data.runtimeAccessToken)return{success:!1,error:o.data.message||"scheduler did not return runtimeAccessToken"};const s=G(t,y(e))!==o.data.runtimeAccessToken;return H({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey||e.credentialId||"",schedulerBaseUrl:t,userKey:y(e)}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:s}}catch(o){const i=o;return{success:!1,error:i.response?.data?JSON.stringify(i.response.data):i.message}}}async function rr(e,r){const t=_(e);if(!t)return{success:!1,error:"serverUrl is required for runtime token refresh"};const n=h().filter(u=>_(u.serverUrl)===t),o=String(r||"").trim(),i=o?n.find(u=>String(u.userKey||"").trim()===o||String(u.credentialId||"").trim()===o):n.length===1?n[0]:void 0;if(!i)return{success:!1,error:o?"no auto-register target matches this identity":"multiple auto-register targets share this serverUrl; specify the target identity"};if(!i.userKey&&!i.machineToken)return{success:!1,error:"userKey or machineToken is required for runtime token refresh"};const s={instanceId:$e(t,y(i))},c=L(i);try{const u=await $.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:i.tenantId,userKey:i.machineToken?void 0:i.userKey,machineToken:i.machineToken,instanceId:s.instanceId,instanceName:i.instanceName,runtimeBridgeBaseUrl:c},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!u.data.success||!u.data.runtimeAccessToken)return{success:!1,error:u.data.message||"scheduler did not return runtimeAccessToken"};const I=G(t,y(i))!==u.data.runtimeAccessToken;return V({serverBaseUrl:t,accessToken:u.data.runtimeAccessToken,userKey:y(i)}),{success:!0,runtimeAccessToken:u.data.runtimeAccessToken,updated:I}}catch(u){const l=u;return{success:!1,error:l.response?.data?JSON.stringify(l.response.data):l.message}}}async function tr(){if(!d.registered||!d.instanceId)return a.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;const e=J(d.instanceId),r=e?h().find(t=>N(t)===e.targetKey):void 0;if(r&&(String(r.machineToken||"").trim()||r.credentialType==="MACHINE_TOKEN"))return a.info(`[AutoRegister] \u673A\u5668\u51ED\u636E\u76EE\u6807\uFF08instanceId=${d.instanceId}\uFF09\u8DF3\u8FC7\u6CE8\u9500\uFF1A\u914D\u5BF9\u8EAB\u4EFD\u5FC5\u987B\u8DE8\u91CD\u542F\u5B58\u6D3B\uFF0C\u4E0D\u5220\u9664\u8C03\u5EA6\u4E2D\u5FC3\u767B\u8BB0\u884C`),!0;try{const t=await be(d.instanceId);if(t.success){a.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${d.instanceId}`);const n=J(d.instanceId),o=n?.serverUrl||j(b(),d.instanceId);M().schedulerBaseUrl&&me(o,n?.identityKey),ge();try{const{stopTunnelClientForTarget:s}=await import("./tunnel-client.js");s(o,n?.identityKey??y(b()))}catch(s){a.warn(`[AutoRegister] \u6CE8\u9500\u540E\u505C\u6B62\u96A7\u9053\u5931\u8D25: ${s.message}`)}if(!n)d.registered=!1,d.instanceId=void 0;else{delete d.registrations[n.targetKey];const s=Object.entries(d.registrations);d.registered=s.length>0,d.instanceId=s[s.length-1]?.[1]}return!0}else return a.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${t.message}`),!1}catch(t){const n=t;return a.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${n.message}`),!1}}function nr(){return{...d}}function sr(){return d.registered}function ir(){return d.instanceId}async function or(){const e=b(),r=d.instanceId,t=j(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=L(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return a.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{a.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),a.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),a.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await $.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(a.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),a.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(a.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const i=o,s=i.response?.data?JSON.stringify(i.response.data):i.message;return a.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${s}`),{success:!1,error:s}}}export{Ze as autoRegister,or as bridgeRestartCleanup,He as cancelBackgroundRegisterRetries,Te as collapseDuplicatedSecret,qe as findConfigsByConnectionKey,T as getAutoRegisterConfigFilePath,Je as getConfiguredConnectionKeys,Ye as getCurrentConfiguredConnectionKey,ir as getInstanceId,nr as getRegistrationState,sr as isRegistered,ie as isTunnelConfig,Ve as listConfiguredTargetSummaries,W as loadConfig,h as loadConfigs,b as loadPrimaryLifecycleConfig,Qe as replaceConfiguredConnectionKey,er as requestRuntimeAccessTokenRefresh,rr as requestRuntimeAccessTokenRefreshForServer,$e as resolveInstanceIdForTarget,L as resolveRuntimeBridgeBaseUrl,Xe as resolveSchedulerBaseUrlFromToken,Ne as resolveTargetIdentityFromToken,tr as unregister};
|
|
2
2
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomBytes as E}from"node:crypto";import{existsSync as
|
|
1
|
+
import{randomBytes as E}from"node:crypto";import{existsSync as S,mkdirSync as P,readFileSync as w,writeFileSync as x}from"node:fs";import A from"node:os";import _ from"node:path";import N from"axios";import{getActiveBridgeHttpPort as K,getAutoRegisterConfigFilePath as k,getBridgeRuntimePropertiesFilePath as b,schedulerBaseUrl as F}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{logger as R}from"../utils/logger.js";import{normalizeTargetServerUrl as h}from"../utils/target-identity.js";const C="awsp_";function B(r){if(!S(r))return{};try{return JSON.parse(w(r,"utf-8"))}catch(n){throw new Error(`\u65E0\u6CD5\u89E3\u6790\u914D\u7F6E\u6587\u4EF6 ${r}: ${n.message}`)}}function $(r){return Array.isArray(r.autoRegisterTargets)?r.autoRegisterTargets.filter(n=>!!(n&&typeof n=="object"&&!Array.isArray(n))):[]}function M(){return`bridge-${E(6).toString("hex")}`}function j(r,n){return/^https?:\/\//i.test(r)?r:/:[0-9]+$/.test(r)?`http://${r}`:`http://${r}:${n}`}function q(r,n){const s=$(r),l=n.serverUrl?.trim(),e=l?h(l):"",f=String(n.instanceId||"").trim(),y=String(n.targetIdentity||"").trim();let i=-1,c=r,g=!1;if(s.length>0){const d=s.map((o,U)=>({item:o,itemIndex:U})).filter(({item:o})=>!e||h(o.serverUrl||o.schedulerBaseUrl)===e);let m=d;if(f){const o=d.filter(({item:U})=>String(U.instanceId||"").trim()===f);if(o.length>1)throw new Error(`\u5339\u914D\u5230\u591A\u4E2A instanceId=${f} \u7684\u914D\u5BF9\u76EE\u6807\uFF0C\u8BF7\u68C0\u67E5 config.json`);o.length===1?m=o:(m=[],g=!0)}else if(y){if(m=d.filter(({item:o})=>String(o.credentialId||o.userKey||"").trim()===y),m.length>1)throw new Error("\u5339\u914D\u5230\u591A\u4E2A\u914D\u5BF9\u76EE\u6807\uFF0C\u8BF7\u7528 --instance-id \u6216 --target-identity \u7CBE\u786E\u6307\u5B9A");if(m.length===0)throw new Error("\u6307\u5B9A targetIdentity \u7684\u914D\u5BF9\u76EE\u6807\u4E0D\u5B58\u5728")}else if(d.length>1)throw new Error("\u5B58\u5728\u591A\u4E2A\u6CE8\u518C\u76EE\u6807\uFF0C\u8BF7\u7528 --server-url \u642D\u914D --instance-id / --target-identity \u6307\u5B9A\u672C\u6B21\u914D\u5BF9\u76EE\u6807");if(g)i=s.length;else if(i=m[0]?.itemIndex??-1,i<0){if(!e)throw new Error("\u65E0\u6CD5\u786E\u5B9A\u914D\u5BF9\u76EE\u6807\uFF0C\u8BF7\u7528 --server-url \u6307\u5B9A");c={serverUrl:l},i=s.length,g=!0}else c=s[i]||{}}const p=h(l||c.serverUrl||c.schedulerBaseUrl||F);if(!p)throw new Error("\u65E0\u6CD5\u786E\u5B9A\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u8BF7\u7528 --server-url \u6307\u5B9A");const a=n.registerAddr||String(c.registerAddr||c.registerIp||c.virtualIp||"").trim(),u=c.tunnel===!0?!0:c.tunnel===!1?!1:!a;let t=f||String(c.instanceId||"").trim()||void 0,I;if(u){t||(t=M());const d=new URL(p);I=`${d.protocol}//${d.host}/tunnel/${t}`}else I=j(a,K());const v=String(c.connectionKey||"").trim(),T=n.instanceName?.trim()||String(c.instanceName||"").trim()||A.hostname();return{index:i,target:c,serverUrl:p,tunnel:u,instanceId:t,connectionKey:v,instanceName:T,runtimeBridgeBaseUrl:I,isNewTarget:g}}async function G(r,n){return(await N.post(r,n,{headers:{"Content-Type":"application/json"},timeout:15e3})).data}const Y=2500,D=1e3;function z(){try{const r=b();if(!S(r))return null;const s=w(r,"utf-8").split(/\r?\n/).find(e=>/^port\s*[=:]/.test(e.trim()));if(!s)return null;const l=Number(s.split(/[=:]/)[1]?.trim());return Number.isInteger(l)&&l>=1&&l<=65535?l:null}catch{return null}}async function re(){const r=z();if(!r)return!1;const n=`http://127.0.0.1:${r}/runtime/pair-applied`;for(let s=1;s<=2;s+=1)try{return(await N.post(n,{},{timeout:Y})).data?.ok===!0}catch(l){if(l.response?.status===404)return!1;s<2&&await new Promise(f=>setTimeout(f,D))}return!1}async function te(r){const n=String(r.ticket||"").trim();if(!n.startsWith(C))return{success:!1,error:"\u914D\u5BF9\u7968\u636E\u683C\u5F0F\u4E0D\u6B63\u786E\uFF08\u5E94\u4EE5 awsp_ \u5F00\u5934\uFF09\uFF0C\u8BF7\u4ECE\u7BA1\u7406\u9875\u91CD\u65B0\u590D\u5236"};const s=r.configPath||k(),l=B(s);let e;try{e=q(l,r)}catch(a){return{success:!1,error:a.message}}const f={pairingTicket:n,instanceName:e.instanceName,runtimeBridgeBaseUrl:e.runtimeBridgeBaseUrl};e.instanceId&&(f.instanceId=e.instanceId);const y=r.postPair||G;let i;try{i=await y(`${e.serverUrl}/api/instances/pair`,f)}catch(a){const u=a;return{success:!1,error:u.response?.data?.message||u.message||"\u914D\u5BF9\u8BF7\u6C42\u5931\u8D25"}}if(!i?.success||!i.machineToken||!i.instanceId)return{success:!1,error:i?.message||"\u8C03\u5EA6\u4E2D\u5FC3\u672A\u8FD4\u56DE\u673A\u5668\u51ED\u636E"};const c=String(i.connectionKey||"").trim();if(!c)return{success:!1,error:"\u8C03\u5EA6\u4E2D\u5FC3\u672A\u8FD4\u56DE\u8FDE\u63A5\u5BC6\u94A5\uFF08server \u7248\u672C\u8FC7\u65E7\uFF1F\uFF09\uFF0C\u8BF7\u5347\u7EA7 server \u540E\u91CD\u8BD5"};const g=i.instanceId,p=i.instanceName||e.instanceName;try{await O(s,()=>{const a=B(s),u=$(a);let t;if(Array.isArray(a.autoRegisterTargets)&&u.length>0){const I=u.filter(o=>h(o.serverUrl||o.schedulerBaseUrl)===h(e.serverUrl)),v=String(e.target.userKey||"").trim(),T=e.instanceId?I.filter(o=>String(o.instanceId||"").trim()===e.instanceId):[],d=v?I.filter(o=>String(o.userKey||"").trim()===v):[];if(T.length>1||d.length>1)throw new Error(`\u914D\u5BF9\u5199\u56DE\u5931\u8D25\uFF1A\u5339\u914D\u5230\u591A\u4E2A\u76EE\u6807\uFF08server=${e.serverUrl}\uFF09\uFF0C\u8BF7\u7528 instanceId / target identity \u7CBE\u786E\u6307\u5B9A`);const m=T[0]||d[0]||(I.length===1&&!e.isNewTarget?I[0]:void 0);m?t=m:!e.isNewTarget&&e.index>=0&&u[e.index]?t=u[e.index]:(t={serverUrl:e.serverUrl},u.push(t),a.autoRegisterTargets=u)}else t=a;t.serverUrl=t.serverUrl||e.serverUrl,t.instanceName=p,t.instanceId=g,t.credentialId=g,delete t.credentialRef,t.credentialType=i.credentialType||"MACHINE_TOKEN",t.machineToken=i.machineToken,t.connectionKey=c,i.tenantId&&(t.tenantId=i.tenantId),i.userId&&(t.userId=i.userId),Array.isArray(a.autoRegisterTargets)&&a.autoRegisterTargets.length>0&&String(a.connectionKey||"").trim()&&delete a.connectionKey,P(_.dirname(s),{recursive:!0}),x(s,JSON.stringify(a,null,2),"utf-8")})}catch(a){return{success:!1,error:a.message}}return e.instanceId&&e.instanceId!==g&&R.info(`[Pair] server \u5DF2\u6309\u7968\u636E\u786E\u5B9A\u5B9E\u4F8B id\uFF1A${g}\uFF08\u672C\u5730\u6B64\u524D\u4E3A ${e.instanceId}\uFF09\uFF0Cconfig.json \u5DF2\u5199\u56DE\u65B0 id`),R.info(`[Pair] \u914D\u5BF9\u6210\u529F: server=${e.serverUrl}, instanceId=${g}, machineToken \u5DF2\u5199\u5165 ${s}`),{success:!0,instanceId:g,serverUrl:e.serverUrl}}export{re as notifyRunningBridgeAfterPair,te as pairBridge,z as readRunningBridgePort,q as resolvePairTarget};
|
|
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
|
|
2
|
-
`)?
|
|
3
|
-
`:
|
|
4
|
-
`});
|
|
5
|
-
`);const c=this.runs.get(e);c&&
|
|
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
|
|
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
|
-
`,
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|