aws-runtime-bridge 1.9.102 → 1.9.103
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/README.md +2 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts +2 -0
- package/dist/adapter/AcodeSdkAdapter.js +3 -3
- package/dist/index.js +1 -1
- package/dist/remote-desktop/computer-use-backend.d.ts +111 -0
- package/dist/remote-desktop/computer-use-backend.js +1 -0
- package/dist/remote-desktop/types.d.ts +79 -0
- package/dist/routes/computer.d.ts +1 -0
- package/dist/routes/computer.js +2 -0
- package/dist/routes/file-change-diff.d.ts +27 -0
- package/dist/routes/file-change-diff.js +5 -0
- package/dist/routes/instance.js +2 -2
- package/dist/routes/pty.d.ts +2 -2
- package/dist/routes/pty.js +3 -3
- package/dist/routes/task-context.js +1 -1
- package/dist/routes/terminal.js +28 -28
- package/dist/services/auto-register.d.ts +1 -1
- package/dist/services/auto-register.js +1 -1
- package/dist/services/instance-features.d.ts +10 -0
- package/dist/services/instance-features.js +2 -0
- package/dist/services/sdk-tool-file-change-recorder.d.ts +12 -0
- package/dist/services/sdk-tool-file-change-recorder.js +2 -0
- package/dist/services/workspace-files.js +1 -1
- package/dist/services/workspaceTaskChangeWatcher.d.ts +9 -1
- package/dist/services/workspaceTaskChangeWatcher.js +3 -1
- package/package/acode/dist/built-in-file-tools.d.ts +4 -0
- package/package/acode/dist/built-in-file-tools.js +2 -2
- package/package/acode/dist/runtime.d.ts +13 -0
- package/package/acode/dist/runtime.js +18 -18
- package/package/aws-client-agent-mcp/README.md +2 -2
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +14 -4
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +36 -36
- package/package/aws-client-agent-mcp/dist/config.js +1 -1
- package/package/aws-client-agent-mcp/dist/config.test.js +2 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +21 -21
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +35 -36
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +6 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +25 -5
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +17 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +14 -12
- package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
- package/package.json +3 -2
- package/scripts/ensure-playwright-browser.mjs +118 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{v4 as M}from"uuid";import A from"axios";import m from"node:os";import F from"node:path";import g from"node:fs";import{logger as i}from"../utils/logger.js";import{schedulerBaseUrl as u}from"../config.js";import{clearRuntimeBinding as z,clearScopedRuntimeAccessToken as J,getRuntimeAccessToken as h,getRuntimeBindingPublicState as w,getScopedRuntimeAccessToken as Y,loadRuntimeBinding as v,normalizeSchedulerBaseUrl as R,saveRuntimeBinding as K,saveScopedRuntimeAccessToken as q}from"./runtime-binding.js";const C={enabled:!1,serverUrl:u,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3};let c={registered:!1,registrations:{}},b=[];function Ae(){return[...b]}const H=3e4;function P(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:H}const I=new Map;function W(e){const r=e.serverUrl||u,t=R(r)||r;if(I.has(t))return;const n=setTimeout(()=>{I.delete(t),V(e,t)},P());n.unref?.(),I.set(t,n)}async function V(e,r){try{const t=S().find(n=>(R(n.serverUrl||u)||n.serverUrl||u)===r)||e;await $(t,{backgroundRetry:!0})}catch(t){i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),W(e)}}function Ie(){for(const e of I.values())clearTimeout(e);I.clear()}function d(e){return e==null?void 0:String(e).trim()||void 0}function L(e){return R(e)}function T(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 O(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function N(e){const r=d(e.serverUrl)||d(e.schedulerBaseUrl),t=d(e.connectionKey)||d(e.password);return{enabled:T(e.enabled),serverUrl:r,tenantId:d(e.tenantId),userKey:d(e.userKey),connectionKey:t,instanceName:d(e.instanceName),instanceId:d(e.instanceId),projectName:d(e.projectName),roleName:d(e.roleName),workspacePath:d(e.workspacePath),registerAddr:d(e.registerAddr),registerIp:d(e.registerIp),virtualIp:d(e.virtualIp),tunnel:T(e.tunnel),preferSameLanIp:T(e.preferSameLanIp),maxRetries:O(e.maxRetries),retryInterval:O(e.retryInterval)}}function k(){const e=String(process.env.AWS_RUNTIME_HOME_DIR||"").trim()||m.homedir();return F.join(e,".aws-bridge","config.json")}function x(){const e=k();try{if(!g.existsSync(e))return i.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r);return i.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),N(t)}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function X(){const e=k();try{if(!g.existsSync(e))return[];const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r),n=N(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey?[n]:[]:o.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const a=N(s);return{...n,...a,connectionKey:a.connectionKey||n.connectionKey}})}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function j(){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_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.projectName=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=T(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 _(){const e=x(),r=j(),t={...C,...e||{},...r},n=t.instanceName||m.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||u,tenantId:t.tenantId||"",userKey:t.userKey||"",connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,projectName:t.projectName,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 S(){const e=X(),r=j(),n=(e.length>0?e:[x()||{}]).map(o=>{const s={...C,...o,...r};let a=!1;return process.env.AWS_AUTO_REGISTER!==void 0?a=process.env.AWS_AUTO_REGISTER==="true":a=e.length>0||!!s.userKey,{enabled:a,serverUrl:s.serverUrl||u,tenantId:s.tenantId||"",userKey:s.userKey||"",connectionKey:s.connectionKey,instanceName:s.instanceName||m.hostname(),instanceId:s.instanceId,projectName:s.projectName,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 b=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function Q(){const e=m.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 B(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Z(e,r,t){const n=B(e),o=B(r),s=B(t);return(n&s)===(o&s)}function ee(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 re(e){return e==="127.0.0.1"}function te(e){const r=Q();if(r.length===0)return i.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=ee(e);if(t){if(re(t))return"127.0.0.1";for(const n of r)if(Z(n.address,t,n.netmask))return i.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;i.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 i.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function ne(e=u){return te(e)}function D(e){const r=e||_();return!r.enabled||!r.userKey?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function E(e){const r=process.env.AWS_RUNTIME_BRIDGE_PORT||18081;if(D(e)){const s=e.serverUrl||u;try{const a=new URL(s);if(!e.instanceId)i.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${a.protocol}//${a.host}/tunnel/${e.instanceId}`}catch{i.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=e.preferSameLanIp===!1?"127.0.0.1":ne(e.serverUrl||u),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 y(){return S()[0]||_()}function Se(e){const r=String(e||"").trim();if(!r)return;const t=[];for(const o of S())o?.serverUrl&&t.push(o.serverUrl);const n=v();n.schedulerBaseUrl&&t.push(n.schedulerBaseUrl);for(const o of t){const s=Y(o);if(s&&s===r)return R(o)}}function se(e){return e?Object.entries(c.registrations).find(([,t])=>t===e)?.[0]:void 0}function U(e,r){const t=se(r);if(t)return t;const n=d(e.serverUrl);if(n&&(n!==u||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=v(),s=d(o.schedulerBaseUrl);return o.status==="paired"&&s?s:n||u}async function oe(e){const r=e.instanceName||m.hostname(),t=e.serverUrl||u,n={tenantId:e.tenantId,userKey:e.userKey,instanceId:e.instanceId,instanceName:r,hostname:m.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:E(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectName:e.projectName,roleName:e.roleName};return i.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),i.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),i.info(`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await A.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}let G=Promise.resolve();function ie(e,r){const t=G.then(()=>{const n=k();if(!g.existsSync(n))return;const o=g.readFileSync(n,"utf-8"),s=JSON.parse(o),a=R(e);if(!a)return;let l=!1;const f=p=>{R(String(p.serverUrl||p.schedulerBaseUrl||""))===a&&r&&p.instanceId!==r&&(p.instanceId=r,l=!0)};if(Array.isArray(s.autoRegisterTargets))for(const p of s.autoRegisterTargets)p&&typeof p=="object"&&!Array.isArray(p)&&f(p);else f(s);l&&(g.writeFileSync(n,JSON.stringify(s,null,2),"utf-8"),i.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e})`))});return G=t.catch(()=>{}),t}async function ae(e){const r=U(y(),e),t=v(),n=h(r);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await A.post(`${r}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function ye(e){const r=e?[{..._(),...e}]:S();return r.length>1?(await Promise.all(r.map(n=>$(n)))).every(Boolean):$(r[0]||_())}async function $(e,r){if(!e.enabled)return i.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),i.info("[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true"),!1;if(!e.userKey)return i.error("[AutoRegister] \u7F3A\u5C11\u7528\u6237 Key\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_USER_KEY\uFF09"),!1;const t=D(e);t&&!e.instanceId&&(e={...e,instanceId:`bridge-${M().replace(/-/g,"").slice(0,12)}`},i.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{c.lastAttempt=new Date;const a=await oe(e);if(a.success){if(c.registered=!0,c.instanceId=a.instanceId,a.instanceId&&(c.registrations[e.serverUrl||u]=a.instanceId),c.error=void 0,i.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),i.info(`[AutoRegister] \u5B9E\u4F8B ID: ${a.instanceId}`),i.info(`[AutoRegister] \u6D88\u606F: ${a.message}`),i.info(`[AutoRegister] \u66F4\u65B0: ${a.isUpdate?"\u662F":"\u5426"}`),a.runtimeAccessToken)try{K({accessToken:a.runtimeAccessToken,instanceId:a.instanceId,userId:a.userId,schedulerBaseUrl:a.serverUrl||e.serverUrl||u}),i.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),a.instanceId&&ie(e.serverUrl||u,a.instanceId).catch(l=>{i.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${l.message}`)})}catch(l){const f=l;return i.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${f.message}`),c.error=f.message,!1}else i.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&&a.instanceId&&a.runtimeAccessToken){const{startTunnelClient:l}=await import("./tunnel-client.js"),f=e.serverUrl||u;l(f,a.instanceId),i.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${f}, instanceId=${a.instanceId}`)}return!0}else i.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${a.message}`),c.error=a.message}catch(a){const l=a,f=l.response?.data?JSON.stringify(l.response.data):l.message;i.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${s}/${n}): ${f}`),c.error=f}s<n&&(i.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(a=>setTimeout(a,o)))}return r?.backgroundRetry?i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||u}): ${c.error||"\u672A\u77E5"}`):(i.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),i.error("[AutoRegister] ========================================"),i.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"),i.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),i.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),i.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),i.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||u}`),i.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${c.error||"\u672A\u77E5"}`),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A~/.aws-bridge/config.json \u6216\u73AF\u5883\u53D8\u91CF"),i.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(P()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),W(e),!1}async function ve(){const e=y();if(!e.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const r=w(),t=U(e),n=E(e);try{const o=await A.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.userKey,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 a=h()!==o.data.runtimeAccessToken;return K({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey,schedulerBaseUrl:t}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:a}}catch(o){const s=o;return{success:!1,error:s.response?.data?JSON.stringify(s.response.data):s.message}}}async function Te(e){const r=L(e);if(!r)return{success:!1,error:"serverUrl is required for runtime token refresh"};const t=S().find(s=>L(s.serverUrl)===r)||y();if(!t.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const n=w(),o=E(t);try{const s=await A.post(`${r}/api/instances/runtime-tokens/refresh`,{tenantId:t.tenantId,userKey:t.userKey,instanceId:n.instanceId,instanceName:t.instanceName,runtimeBridgeBaseUrl:o},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!s.data.success||!s.data.runtimeAccessToken)return{success:!1,error:s.data.message||"scheduler did not return runtimeAccessToken"};const l=h(r)!==s.data.runtimeAccessToken;return q({serverBaseUrl:r,accessToken:s.data.runtimeAccessToken}),{success:!0,runtimeAccessToken:s.data.runtimeAccessToken,updated:l}}catch(s){const a=s;return{success:!1,error:a.response?.data?JSON.stringify(a.response.data):a.message}}}async function _e(){if(!c.registered||!c.instanceId)return i.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;try{const e=await ae(c.instanceId);if(e.success){i.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${c.instanceId}`);const r=U(y(),c.instanceId);return v().schedulerBaseUrl&&J(r),z(),c.registered=!1,c.instanceId=void 0,!0}else return i.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${e.message}`),!1}catch(e){const r=e;return i.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${r.message}`),!1}}function Ee(){return{...c}}function Ue(){return c.registered}function he(){return c.instanceId}async function Ne(){const e=y(),r=c.instanceId,t=U(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=E(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),i.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),i.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await A.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(i.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),i.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const s=o,a=s.response?.data?JSON.stringify(s.response.data):s.message;return i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${a}`),{success:!1,error:a}}}export{ye as autoRegister,Ne as bridgeRestartCleanup,Ie as cancelBackgroundRegisterRetries,k as getAutoRegisterConfigFilePath,Ae as getConfiguredConnectionKeys,he as getInstanceId,Ee as getRegistrationState,Ue as isRegistered,D as isTunnelConfig,_ as loadConfig,S as loadConfigs,y as loadPrimaryLifecycleConfig,ve as requestRuntimeAccessTokenRefresh,Te as requestRuntimeAccessTokenRefreshForServer,Se as resolveSchedulerBaseUrlFromToken,_e as unregister};
|
|
1
|
+
import{v4 as M}from"uuid";import R from"axios";import m from"node:os";import F from"node:path";import g from"node:fs";import{logger as i}from"../utils/logger.js";import{schedulerBaseUrl as u}from"../config.js";import{clearRuntimeBinding as z,clearScopedRuntimeAccessToken as J,getRuntimeAccessToken as h,getRuntimeBindingPublicState as w,getScopedRuntimeAccessToken as Y,loadRuntimeBinding as v,normalizeSchedulerBaseUrl as I,saveRuntimeBinding as K,saveScopedRuntimeAccessToken as q}from"./runtime-binding.js";const C={enabled:!1,serverUrl:u,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3};let c={registered:!1,registrations:{}},b=[];function Re(){return[...b]}const H=3e4;function P(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:H}const A=new Map;function W(e){const r=e.serverUrl||u,t=I(r)||r;if(A.has(t))return;const n=setTimeout(()=>{A.delete(t),V(e,t)},P());n.unref?.(),A.set(t,n)}async function V(e,r){try{const t=S().find(n=>(I(n.serverUrl||u)||n.serverUrl||u)===r)||e;await $(t,{backgroundRetry:!0})}catch(t){i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),W(e)}}function Ae(){for(const e of A.values())clearTimeout(e);A.clear()}function d(e){return e==null?void 0:String(e).trim()||void 0}function L(e){return I(e)}function T(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 O(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function N(e){const r=d(e.serverUrl)||d(e.schedulerBaseUrl),t=d(e.connectionKey)||d(e.password);return{enabled:T(e.enabled),serverUrl:r,tenantId:d(e.tenantId),userKey:d(e.userKey),connectionKey:t,instanceName:d(e.instanceName),instanceId:d(e.instanceId),projectId:d(e.projectId),roleName:d(e.roleName),workspacePath:d(e.workspacePath),registerAddr:d(e.registerAddr),registerIp:d(e.registerIp),virtualIp:d(e.virtualIp),tunnel:T(e.tunnel),preferSameLanIp:T(e.preferSameLanIp),maxRetries:O(e.maxRetries),retryInterval:O(e.retryInterval)}}function k(){const e=String(process.env.AWS_RUNTIME_HOME_DIR||"").trim()||m.homedir();return F.join(e,".aws-bridge","config.json")}function x(){const e=k();try{if(!g.existsSync(e))return i.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r);return i.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),N(t)}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function X(){const e=k();try{if(!g.existsSync(e))return[];const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r),n=N(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey?[n]:[]:o.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const a=N(s);return{...n,...a,connectionKey:a.connectionKey||n.connectionKey}})}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function j(){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_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=T(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 _(){const e=x(),r=j(),t={...C,...e||{},...r},n=t.instanceName||m.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||u,tenantId:t.tenantId||"",userKey:t.userKey||"",connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,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 S(){const e=X(),r=j(),n=(e.length>0?e:[x()||{}]).map(o=>{const s={...C,...o,...r};let a=!1;return process.env.AWS_AUTO_REGISTER!==void 0?a=process.env.AWS_AUTO_REGISTER==="true":a=e.length>0||!!s.userKey,{enabled:a,serverUrl:s.serverUrl||u,tenantId:s.tenantId||"",userKey:s.userKey||"",connectionKey:s.connectionKey,instanceName:s.instanceName||m.hostname(),instanceId:s.instanceId,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 b=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function Q(){const e=m.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 B(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Z(e,r,t){const n=B(e),o=B(r),s=B(t);return(n&s)===(o&s)}function ee(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 re(e){return e==="127.0.0.1"}function te(e){const r=Q();if(r.length===0)return i.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=ee(e);if(t){if(re(t))return"127.0.0.1";for(const n of r)if(Z(n.address,t,n.netmask))return i.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;i.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 i.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function ne(e=u){return te(e)}function D(e){const r=e||_();return!r.enabled||!r.userKey?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function E(e){const r=process.env.AWS_RUNTIME_BRIDGE_PORT||18081;if(D(e)){const s=e.serverUrl||u;try{const a=new URL(s);if(!e.instanceId)i.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${a.protocol}//${a.host}/tunnel/${e.instanceId}`}catch{i.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=e.preferSameLanIp===!1?"127.0.0.1":ne(e.serverUrl||u),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 y(){return S()[0]||_()}function Se(e){const r=String(e||"").trim();if(!r)return;const t=[];for(const o of S())o?.serverUrl&&t.push(o.serverUrl);const n=v();n.schedulerBaseUrl&&t.push(n.schedulerBaseUrl);for(const o of t){const s=Y(o);if(s&&s===r)return I(o)}}function se(e){return e?Object.entries(c.registrations).find(([,t])=>t===e)?.[0]:void 0}function U(e,r){const t=se(r);if(t)return t;const n=d(e.serverUrl);if(n&&(n!==u||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=v(),s=d(o.schedulerBaseUrl);return o.status==="paired"&&s?s:n||u}async function oe(e){const r=e.instanceName||m.hostname(),t=e.serverUrl||u,n={tenantId:e.tenantId,userKey:e.userKey,instanceId:e.instanceId,instanceName:r,hostname:m.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:E(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectId:e.projectId,roleName:e.roleName};return i.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),i.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),i.info(`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await R.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}let G=Promise.resolve();function ie(e,r){const t=G.then(()=>{const n=k();if(!g.existsSync(n))return;const o=g.readFileSync(n,"utf-8"),s=JSON.parse(o),a=I(e);if(!a)return;let l=!1;const f=p=>{I(String(p.serverUrl||p.schedulerBaseUrl||""))===a&&r&&p.instanceId!==r&&(p.instanceId=r,l=!0)};if(Array.isArray(s.autoRegisterTargets))for(const p of s.autoRegisterTargets)p&&typeof p=="object"&&!Array.isArray(p)&&f(p);else f(s);l&&(g.writeFileSync(n,JSON.stringify(s,null,2),"utf-8"),i.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e})`))});return G=t.catch(()=>{}),t}async function ae(e){const r=U(y(),e),t=v(),n=h(r);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await R.post(`${r}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function ye(e){const r=e?[{..._(),...e}]:S();return r.length>1?(await Promise.all(r.map(n=>$(n)))).every(Boolean):$(r[0]||_())}async function $(e,r){if(!e.enabled)return i.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),i.info("[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true"),!1;if(!e.userKey)return i.error("[AutoRegister] \u7F3A\u5C11\u7528\u6237 Key\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_USER_KEY\uFF09"),!1;const t=D(e);t&&!e.instanceId&&(e={...e,instanceId:`bridge-${M().replace(/-/g,"").slice(0,12)}`},i.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{c.lastAttempt=new Date;const a=await oe(e);if(a.success){if(c.registered=!0,c.instanceId=a.instanceId,a.instanceId&&(c.registrations[e.serverUrl||u]=a.instanceId),c.error=void 0,i.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),i.info(`[AutoRegister] \u5B9E\u4F8B ID: ${a.instanceId}`),i.info(`[AutoRegister] \u6D88\u606F: ${a.message}`),i.info(`[AutoRegister] \u66F4\u65B0: ${a.isUpdate?"\u662F":"\u5426"}`),a.runtimeAccessToken)try{K({accessToken:a.runtimeAccessToken,instanceId:a.instanceId,userId:a.userId,schedulerBaseUrl:a.serverUrl||e.serverUrl||u}),i.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),a.instanceId&&ie(e.serverUrl||u,a.instanceId).catch(l=>{i.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${l.message}`)})}catch(l){const f=l;return i.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${f.message}`),c.error=f.message,!1}else i.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&&a.instanceId&&a.runtimeAccessToken){const{startTunnelClient:l}=await import("./tunnel-client.js"),f=e.serverUrl||u;l(f,a.instanceId),i.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${f}, instanceId=${a.instanceId}`)}return!0}else i.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${a.message}`),c.error=a.message}catch(a){const l=a,f=l.response?.data?JSON.stringify(l.response.data):l.message;i.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${s}/${n}): ${f}`),c.error=f}s<n&&(i.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(a=>setTimeout(a,o)))}return r?.backgroundRetry?i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||u}): ${c.error||"\u672A\u77E5"}`):(i.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),i.error("[AutoRegister] ========================================"),i.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"),i.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),i.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),i.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),i.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||u}`),i.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${c.error||"\u672A\u77E5"}`),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A~/.aws-bridge/config.json \u6216\u73AF\u5883\u53D8\u91CF"),i.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(P()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),W(e),!1}async function ve(){const e=y();if(!e.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const r=w(),t=U(e),n=E(e);try{const o=await R.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.userKey,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 a=h()!==o.data.runtimeAccessToken;return K({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey,schedulerBaseUrl:t}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:a}}catch(o){const s=o;return{success:!1,error:s.response?.data?JSON.stringify(s.response.data):s.message}}}async function Te(e){const r=L(e);if(!r)return{success:!1,error:"serverUrl is required for runtime token refresh"};const t=S().find(s=>L(s.serverUrl)===r)||y();if(!t.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const n=w(),o=E(t);try{const s=await R.post(`${r}/api/instances/runtime-tokens/refresh`,{tenantId:t.tenantId,userKey:t.userKey,instanceId:n.instanceId,instanceName:t.instanceName,runtimeBridgeBaseUrl:o},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!s.data.success||!s.data.runtimeAccessToken)return{success:!1,error:s.data.message||"scheduler did not return runtimeAccessToken"};const l=h(r)!==s.data.runtimeAccessToken;return q({serverBaseUrl:r,accessToken:s.data.runtimeAccessToken}),{success:!0,runtimeAccessToken:s.data.runtimeAccessToken,updated:l}}catch(s){const a=s;return{success:!1,error:a.response?.data?JSON.stringify(a.response.data):a.message}}}async function _e(){if(!c.registered||!c.instanceId)return i.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;try{const e=await ae(c.instanceId);if(e.success){i.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${c.instanceId}`);const r=U(y(),c.instanceId);return v().schedulerBaseUrl&&J(r),z(),c.registered=!1,c.instanceId=void 0,!0}else return i.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${e.message}`),!1}catch(e){const r=e;return i.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${r.message}`),!1}}function Ee(){return{...c}}function Ue(){return c.registered}function he(){return c.instanceId}async function Ne(){const e=y(),r=c.instanceId,t=U(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=E(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),i.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),i.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await R.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(i.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),i.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const s=o,a=s.response?.data?JSON.stringify(s.response.data):s.message;return i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${a}`),{success:!1,error:a}}}export{ye as autoRegister,Ne as bridgeRestartCleanup,Ae as cancelBackgroundRegisterRetries,k as getAutoRegisterConfigFilePath,Re as getConfiguredConnectionKeys,he as getInstanceId,Ee as getRegistrationState,Ue as isRegistered,D as isTunnelConfig,_ as loadConfig,S as loadConfigs,y as loadPrimaryLifecycleConfig,ve as requestRuntimeAccessTokenRefresh,Te as requestRuntimeAccessTokenRefreshForServer,Se as resolveSchedulerBaseUrlFromToken,_e as unregister};
|
|
2
2
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
export declare function setAgentFeatures(agentId: string, features: unknown): void;
|
|
3
|
+
|
|
4
|
+
export declare function getAgentFeatures(agentId: string): Record<string, boolean>;
|
|
5
|
+
|
|
6
|
+
export declare function isFeatureEnabled(agentId: string, key: string): boolean;
|
|
7
|
+
|
|
8
|
+
export declare function clearAgentFeatures(agentId: string): void;
|
|
9
|
+
|
|
10
|
+
export declare function instanceFeaturesCacheSize(): number;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const r={},i=new Set(["desktop"]),n=new Map;function c(e){if(e==null)return{...r};let t=e;if(typeof e=="string")try{t=JSON.parse(e)}catch{t=null}if(typeof t!="object"||t===null||Array.isArray(t))return{...r};const u={};for(const[o,s]of Object.entries(t))i.has(o)||typeof s=="boolean"&&(u[o]=s);return u}function a(e,t){e&&n.set(e,c(t))}function f(e){return n.get(e)??{...r}}function l(e,t){return f(e)[t]===!0}function p(e){n.delete(e)}function A(){return n.size}export{p as clearAgentFeatures,f as getAgentFeatures,A as instanceFeaturesCacheSize,l as isFeatureEnabled,a as setAgentFeatures};
|
|
2
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ProviderEvent } from "../adapter/types.js";
|
|
2
|
+
|
|
3
|
+
export declare function onSdkToolUseStart(sessionId: string, toolUseId: string | undefined, toolInput: Record<string, unknown> | undefined, toolName?: string, workspacePath?: string): void;
|
|
4
|
+
|
|
5
|
+
export declare function forgetSession(sessionId: string): void;
|
|
6
|
+
|
|
7
|
+
export declare function onSdkToolUseEnd(event: ProviderEvent, entry: {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
workspacePath: string;
|
|
11
|
+
providerId: string;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{promises as A}from"node:fs";import{existsSync as P,readFileSync as L}from"node:fs";import E from"node:path";import{getToolActionInfo as x}from"../adapter/types.js";import{recordAgentFileChange as F}from"./workspaceTaskChangeWatcher.js";import{logger as u}from"../utils/logger.js";const h=new Map,k=new Set(["write","writefile","fswrite","edit","editfile","strreplace","str_replace","editcode","multiedit","apply_patch","applypatch","file_change","write_file","edit_file","delete_file","notebookedit"]),b=new Set(["delete","deletefile","remove","rm","delete_file"]);function y(e){return String(e??"").toLowerCase().replace(/[_-]/g,"")}function w(e){if(!e||typeof e!="object")return;const r=[e.file_path,e.filePath,e.path,e.target_file,e.targetFile,e.file,e.files];for(const t of r){if(typeof t=="string"&&t.trim())return t.trim().replace(/\\/g,"/");if(Array.isArray(t)){const i=t.filter(n=>typeof n=="string"&&n.trim());i.length>1&&u.warn(`[SdkFileChange] \u591A\u6587\u4EF6\u5DE5\u5177\u4EC5\u8BB0\u5F55\u9996\u4E2A\u6587\u4EF6\u8DEF\u5F84\uFF08\u5171 ${i.length} \u4E2A\uFF09`);const o=i[0];if(o)return o.trim().replace(/\\/g,"/")}}}function T(e,r){const t=y(e);if(!t)return;if(b.has(t))return"DELETE";if(k.has(t))return"UPDATE";const i=x(e,r);if(i.actionType==="write_file"||i.actionType==="edit_file")return"UPDATE"}function C(e,r){const t=E.resolve(e).replace(/\\/g,"/"),i=E.resolve(e,r).replace(/\\/g,"/"),o=t+"/";if(!(process.platform==="win32"?i.toLowerCase().startsWith(o.toLowerCase())||i.toLowerCase()===t.toLowerCase():i.startsWith(o)||i===t))return;const s=i.slice(t.length).replace(/^\/+/,"");return s.length>0?s:void 0}function N(e,r,t,i,o){if(!r||!t||typeof t!="object")return;let n=h.get(e);n||(n=new Map,h.set(e,n)),n.set(r,{toolInput:t,startState:D(i,t,o)})}function D(e,r,t){if(!t||!T(e,r))return null;const i=w(r),o=i?C(t,i):void 0;if(!o)return null;const n=E.join(t,o);if(!P(n))return{beforeContent:null,existedAtStart:!1};try{return{beforeContent:L(n,"utf-8"),existedAtStart:!0}}catch{return{beforeContent:null,existedAtStart:!0}}}function O(e){h.delete(e)}async function j(e,r){if(e.type!=="tool_use_end"||r.providerId==="acode")return;const{toolName:t,isError:i}=e.data,o=e.data.toolUseId||e.data.actionId,n=h.get(r.sessionId),s=o&&n?.get(o)||void 0;o&&n&&(n.delete(o),n.size===0&&h.delete(r.sessionId));const S=s?.toolInput;if(i===!0)return;const m=T(t,S);if(!m)return;let l=w(S);if(!l){const c=String(e.data.toolResult??e.data.actionResult??"").match(/(?:path|file)[=:]\s*["']?([^"'\s,]+)["']?/i);c&&(l=c[1].replace(/\\/g,"/"))}if(!l){u.warn(`[SdkFileChange] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u7684\u6587\u4EF6\u5DE5\u5177: session=${r.sessionId}, tool=${t}`);return}const f=C(r.workspacePath,l);if(!f){u.warn(`[SdkFileChange] \u6587\u4EF6\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u5916\u6216\u4E3A\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF0C\u8DF3\u8FC7: ${l} (workspace=${r.workspacePath})`);return}try{const a=E.join(r.workspacePath,f);let c=null,p=!1;try{c=await A.readFile(a,"utf-8"),p=!0}catch{}const g=s?.startState??null,_=g?.beforeContent??null;let d;if(m==="DELETE")d="DELETE";else if(g!=null){if(!g.existedAtStart&&!p){u.warn(`[SdkFileChange] \u6587\u4EF6\u5728\u5DE5\u5177\u6267\u884C\u524D\u540E\u5747\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u8BB0\u5F55: ${f}`);return}d=g.existedAtStart?p?"UPDATE":"DELETE":"CREATE"}else d=p?"UPDATE":"CREATE";await F({filePath:f,operation:d,beforeContent:_,afterContent:d==="DELETE"?null:c,agentId:r.agentId,workspacePath:r.workspacePath})}catch(a){u.warn(`[SdkFileChange] \u8BB0\u5F55\u5931\u8D25: session=${r.sessionId}, tool=${t}, file=${f}, error=${a instanceof Error?a.message:String(a)}`)}}export{O as forgetSession,j as onSdkToolUseEnd,N as onSdkToolUseStart};
|
|
2
|
+
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createReadStream as O,createWriteStream as ot,promises as o}from"node:fs";import d from"node:path";import{Readable as st}from"node:stream";import{pipeline as ct}from"node:stream/promises";import{TextDecoder as lt}from"node:util";import{createGunzip as ht}from"node:zlib";import*as ut from"tar";import dt from"unzipper";const{ZipArchive:wt}=await import("archiver"),y={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},ft=new lt("gbk");function h(t){return String(t||"").replace(/\\/g,"/")}function U(t){const e=d.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function pt(t){const e=U(t),r=await o.realpath(e);if(!(await o.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function k(t){return{requestedRoot:U(t),workspaceRoot:await pt(t)}}function p(t,e){const r=d.resolve(e),i=d.relative(t,r);if(i.startsWith("..")||d.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function x(t,e){const r=await o.realpath(e);return p(t,r)}async function E(t,e){const r=await o.realpath(d.dirname(e));p(t,r);try{if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${e}`)}catch(i){if((typeof i=="object"&&i!==null?Reflect.get(i,"code"):void 0)!=="ENOENT")throw i}}function z(t,e,r){const i=d.resolve(r),a=d.relative(t,i);if(a.startsWith("..")||d.isAbsolute(a))throw new Error(`Target path is outside workspace: ${i}`);return p(e,d.join(e,a))}async function C({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await k(t),a=String(e||"").trim(),n=a?z(r,i,a):i,s=await x(i,n);return{workspaceRoot:i,resolvedTargetPath:s}}function D(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||d.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function q(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function $(t){const e=q(t);return D(d.basename(e.replace(/\\/g,"/")))}function mt(t,e){if(!t)return $(e);const i=q(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(a=>D(a));return i.length===0?$(e):d.join(...i)}function _(t){return(t&4095).toString(8).padStart(4,"0")}function gt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function B(t,e,r,i,a,n,s){const c=t&e?"r":"-",u=t&r?"w":"-",l=(t&i)!==0,w=(t&a)!==0?l?n:s:l?"x":"-";return`${c}${u}${w}`}function yt(t){return[B(t,256,128,64,2048,"s","S"),B(t,32,16,8,1024,"s","S"),B(t,4,2,1,512,"t","T")].join("")}function Pt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function kt(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function xt(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function vt(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function G(t,e,r){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);const a=await o.stat(e);if(!r||!a.isDirectory())return[e];const n=await o.readdir(e),s=await Promise.all(n.map(c=>{const u=p(t,d.join(e,c));return G(t,u,!0)}));return[e,...s.flat()]}async function Et(t,e){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);await x(t,e)}async function St(t){const e=await o.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),a=r.subarray(0,i);return Pt(a)||kt(a)||xt(a)||vt(a)}finally{await e.close()}}async function S({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:a}=await k(t),n=String(e||"").trim();if(!n)throw new Error("targetPath is required");const s=z(i,a,n),c=await x(a,s);if(!r.allowWorkspaceRoot&&c===a)throw new Error("Workspace root cannot be modified");return{workspaceRoot:a,resolvedTargetPath:c}}async function Z(t){const{requestedRoot:e,workspaceRoot:r}=await k(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const a=z(e,r,i);if(await J(a))return{workspaceRoot:r,resolvedTargetPath:await x(r,a)};const n=await o.realpath(d.dirname(a));return p(r,n),{workspaceRoot:r,resolvedTargetPath:a}}function H(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function K(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function Ft(t,e){const r=D(K(e)||"extracted");return d.join(t,r)}function I(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>y.maxPathLength||i.length>y.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return p(t,d.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=ft.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function V(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function A(t){if(t.entries+=1,t.entries>y.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${y.maxEntries}`)}function X(t,e){if(t.totalBytes+=e,e>y.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${y.maxFileBytes} bytes`);if(t.totalBytes>y.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${y.maxTotalBytes} bytes`)}async function Y(t,e,r){let i=0;t.on("data",a=>{const n=Buffer.isBuffer(a)?a.length:Buffer.byteLength(a);i+=n,(i>y.maxFileBytes||r.totalBytes+i>y.maxTotalBytes)&&t instanceof st&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await ct(t,ot(e)),X(r,i)}async function J(t){try{return await o.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function W({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await k(t),a=String(e||"").trim();if(!a)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:z(r,i,a)}}async function Ot(t){const{workspaceRoot:e,resolvedTargetPath:r}=await C(t);if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=await o.readdir(r,{withFileTypes:!0}),n=await Promise.all(a.map(async s=>{const c=d.join(r,s.name),u=s.isFile()?(await o.stat(c)).size:void 0;return{name:s.name,path:h(c),isDirectory:s.isDirectory(),size:u}}));return{currentPath:h(r),workspacePath:h(e),items:n}}async function Ut(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(e,r);if(!(await o.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);const n=await o.readFile(i,"utf-8");return{workspacePath:h(e),filePath:h(i),content:n}}const Q={maxEntries:1e5,maxDepth:32};async function Rt(t,e){let r=0,i=0;async function a(n,s){if(s>=Q.maxDepth)return;let c;try{c=await o.readdir(n,{withFileTypes:!0})}catch{return}for(const u of c){if(i+=1,i>Q.maxEntries)return;if(u.isSymbolicLink())continue;const l=p(t,d.join(n,u.name));try{if(u.isDirectory())await a(l,s+1);else if(u.isFile()){const f=await o.stat(l);r+=f.size}}catch{}}}return await a(e,0),r}async function qt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t,{allowWorkspaceRoot:!0}),[i,a]=await Promise.all([o.stat(r),o.lstat(r)]),n=i.isDirectory(),s=i.isFile()?await St(r):void 0,c=n?await Rt(e,r):i.size;return{workspacePath:h(e),targetPath:h(r),name:d.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:a.isSymbolicLink(),size:c,mode:i.mode,modeOctal:_(i.mode),permissions:yt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...s?{image:s}:{}}}async function _t(t){const e=gt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:a}=await S(t),n=await G(i,a,r);for(const s of n)await Et(i,s),await o.chmod(s,e);return{ok:!0,workspacePath:h(i),targetPath:h(a),mode:e,modeOctal:_(e),recursive:r,changedCount:n.length}}async function Gt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(e,r),a=await o.stat(i);if(!a.isFile())throw new Error(`Path is not a file: ${i}`);const n=d.extname(i).toLowerCase();if(n===".doc")return{workspacePath:h(e),filePath:h(i),kind:"unsupported",content:"",message:"\u6682\u4E0D\u652F\u6301\u65E7\u7248 .doc \u683C\u5F0F\u9884\u89C8\uFF0C\u8BF7\u53E6\u5B58\u4E3A .docx \u540E\u518D\u6253\u5F00\u3002",size:a.size,mtimeMs:a.mtimeMs};if(n!==".docx")throw new Error("Document preview only supports .docx files");return{workspacePath:h(e),filePath:h(i),kind:"html",content:"",size:a.size,mtimeMs:a.mtimeMs}}async function Zt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);return await o.mkdir(d.dirname(r),{recursive:!0}),await E(e,r),await o.writeFile(r,String(t.content??""),"utf-8"),{ok:!0,workspacePath:h(e),filePath:h(r)}}async function Ht(t){const{workspaceRoot:e,resolvedTargetPath:r}=await C(t),i=D(t.entryName),a=p(e,d.join(r,i));if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await o.access(a),new Error(`Path already exists: ${a}`)}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)!=="ENOENT")throw s}return t.isDirectory?await o.mkdir(a,{recursive:!1}):(await o.mkdir(d.dirname(a),{recursive:!0}),await E(e,a),await o.writeFile(a,"","utf-8")),{ok:!0,workspacePath:h(e),targetPath:h(a),isDirectory:t.isDirectory}}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=D(t.newName),a=p(e,d.join(d.dirname(r),i));if(a===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)};await o.stat(r);try{throw await o.access(a),new Error(`Path already exists: ${a}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await o.rename(r,a),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)}}async function Vt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await C({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await o.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await o.stat(r)).isDirectory()){const c=d.relative(r,i.resolvedTargetPath);if(c===""||!c.startsWith("..")&&!d.isAbsolute(c))throw new Error("Cannot move a directory into itself or its descendant")}const s=p(e,d.join(i.resolvedTargetPath,d.basename(r)));if(s===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)};try{throw await o.access(s),new Error(`Path already exists: ${s}`)}catch(c){if((typeof c=="object"&&c!==null?Reflect.get(c,"code"):void 0)!=="ENOENT")throw c}return await o.rename(r,s),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)}}async function Xt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t);return(await o.stat(r)).isDirectory()?await o.rm(r,{recursive:!0,force:!1}):await o.unlink(r),{ok:!0,workspacePath:h(e),targetPath:h(r)}}async function Yt(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await Z(t);if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=[];for(const n of t.files){const s=$(n.originalname),c=mt(n.relativePath,n.originalname),u=p(e,d.join(r,c));await o.mkdir(d.dirname(u),{recursive:!0}),await E(e,u),await o.copyFile(n.path,u);let l;t.extractArchives&&H(u)&&(l=await Ct({workspacePath:e,archivePath:u,outputPath:Ft(r,s)})),a.push({fileName:s,targetPath:h(u),size:n.size,extracted:l})}return{ok:!0,workspacePath:h(e),targetPath:h(r),files:a}}async function Jt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await o.stat(r),a=d.basename(r);return{workspacePath:h(e),targetPath:h(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${a||"workspace"}.zip`:a,contentType:i.isDirectory()?"application/zip":zt(r),isDirectory:i.isDirectory()}}function zt(t){const e=d.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function Qt(t,e){const r=new wt({zlib:{level:9}}),i=new Promise((a,n)=>{e.on("close",a),e.on("finish",a),r.on("error",n),e.on("error",n)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function Dt(t,e,r){let i=0,a;const n=O(t),s=n.pipe(dt.Parse({forceStream:!0}));try{for await(const c of s){if(a){await V(c);continue}const u=Tt(c);try{const l=I(e,u);if(A(r),c.type==="Directory"){await o.mkdir(l,{recursive:!0});continue}if(c.type!=="File")throw new Error(`Unsupported archive entry type: ${u}`);await o.mkdir(d.dirname(l),{recursive:!0}),await E(e,l),await Y(c,l,r),i+=1}catch(l){a=l,await V(c)}}}finally{n.destroy(),s.destroy()}if(a)throw a;return i}async function Lt(t,e,r,i){let a=0,n;const s=(c,u)=>{if(n)return!1;try{I(e,c),A(i);const l=String(u.type||"").toLowerCase();if(l==="symboliclink"||l==="link")throw new Error(`Archive links are not supported: ${c}`);if(l!=="file"&&l!=="oldfile"&&l!=="contiguousfile"&&l!=="directory")throw new Error(`Unsupported archive entry type: ${c}`);return typeof u.size=="number"&&X(i,u.size),l!=="directory"&&(a+=1),!0}catch(l){return n=l,!1}};if(await ut.x({file:t,cwd:e,gzip:r,strict:!0,filter:(c,u)=>s(c,u)}),n)throw n;return a}async function Wt(t,e,r){const i=K(d.basename(t)),a=I(e,i||"decompressed");return A(r),await o.mkdir(d.dirname(a),{recursive:!0}),await E(e,a),await Y(O(t).pipe(ht()),a,r),1}async function Ct(t){const{requestedRoot:e,workspaceRoot:r}=await k(t.workspacePath),{resolvedTargetPath:i}=await Z({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),a=z(e,r,String(t.archivePath||"").trim()),n=await x(r,a);if(!(await o.stat(n)).isFile())throw new Error(`Archive path is not a file: ${n}`);if(await J(i)||await o.mkdir(i,{recursive:!0}),!(await o.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const u=H(n);if(!u)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let l=0;const f={entries:0,totalBytes:0};return u==="zip"?l=await Dt(n,i,f):u==="tar"||u==="tgz"?l=await Lt(n,i,u==="tgz",f):l=await Wt(n,i,f),{ok:!0,workspacePath:h(r),archivePath:h(n),targetPath:h(i),extractedEntries:l}}function tt(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,a]=e;return i.split(",").flatMap(n=>tt(r+n.trim()+a))}function et(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return tt(r.toLowerCase()).some(n=>{if(n==="*")return!0;const s=n.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${s}$`).test(i)})}function rt(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const it=10*1024*1024,$t=1e3;async function te(t){const{workspaceRoot:e}=await k(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,a=String(t.fileMask||"").trim(),n=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:$t,s=i?r.toLowerCase():r,u=(t.mode==="filename"?"filename":"content")==="filename",l=[];let f=0,w=!1;async function m(v){if(w)return;let F;try{F=await o.readdir(v,{withFileTypes:!0})}catch{return}for(const g of F){if(w)return;const T=p(e,d.join(v,g.name));if(g.isSymbolicLink())continue;if(g.isDirectory()){if(rt(g.name))continue;await m(T);continue}if(!g.isFile()||a&&!et(g.name,a))continue;f+=1;let N;try{N=await o.stat(T)}catch{continue}if(N.size>it)continue;if(u){const P=(i?g.name.toLowerCase():g.name).indexOf(s);if(P!==-1&&(l.push({filePath:h(T),lineNumber:0,lineContent:g.name,matchStart:P,matchEnd:P+r.length}),l.length>=n)){w=!0;return}continue}let b;try{b=await o.readFile(T,"utf-8")}catch{continue}const M=b.split(/\r?\n/);for(let R=0;R<M.length;R+=1){const P=M[R],nt=i?P.toLowerCase():P;let j=0;for(;;){const L=nt.indexOf(s,j);if(L===-1)break;if(l.push({filePath:h(T),lineNumber:R+1,lineContent:P,matchStart:L,matchEnd:L+r.length}),l.length>=n){w=!0;return}j=L+1}}}}return await m(e),{workspacePath:h(e),keyword:r,totalFiles:f,totalMatches:l.length,matches:l,truncated:w}}async function at(t,e,r,i,a){let n;try{n=await o.readFile(e,"utf-8")}catch{return{changed:!1,count:0}}const s=n;let c=0;if(a){const u=r.toLowerCase();let l=0;for(;;){const w=n.toLowerCase().indexOf(u,l);if(w===-1)break;n=n.slice(0,w)+i+n.slice(w+r.length),c+=1;const m=i.length||1;l=w+m}}else{let u=0;for(;;){const l=n.indexOf(r,u);if(l===-1)break;n=n.slice(0,l)+i+n.slice(l+r.length),c+=1;const f=i.length||1;u=l+f}}return n===s?{changed:!1,count:0}:(await E(t,e),await o.writeFile(e,n,"utf-8"),{changed:!0,count:c})}async function ee(t){const{workspaceRoot:e}=await k(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),a=!!t.ignoreCase,n=String(t.fileMask||"").trim(),s=String(t.filePath||"").trim();let c=0,u=0;if(s){const{resolvedFilePath:f}=await W({workspacePath:t.workspacePath,filePath:s}),w=await at(e,f,r,i,a);return w.changed&&(c=1,u=w.count),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}async function l(f){let w;try{w=await o.readdir(f,{withFileTypes:!0})}catch{return}for(const m of w){const v=p(e,d.join(f,m.name));if(m.isSymbolicLink())continue;if(m.isDirectory()){if(rt(m.name))continue;await l(v);continue}if(!m.isFile()||n&&!et(m.name,n))continue;try{if((await o.stat(v)).size>it)continue}catch{continue}const F=await at(e,v,r,i,a);F.changed&&(c+=1,u+=F.count)}}return await l(e),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}export{_t as chmodWorkspaceEntry,Ht as createWorkspaceEntry,Xt as deleteWorkspaceEntry,Ct as extractWorkspaceArchive,qt as getWorkspaceEntryProperties,Ot as listWorkspaceDirectory,Vt as moveWorkspaceEntry,Gt as previewWorkspaceDocument,Ut as readWorkspaceFile,Kt as renameWorkspaceEntry,ee as replaceWorkspaceFiles,Jt as resolveWorkspaceDownloadTarget,te as searchWorkspaceFiles,Qt as streamWorkspaceDirectoryZip,Yt as uploadWorkspaceFiles,Zt as writeWorkspaceFile};
|
|
1
|
+
import{createReadStream as q,createWriteStream as st,promises as o}from"node:fs";import d from"node:path";import{Readable as ct}from"node:stream";import{pipeline as lt}from"node:stream/promises";import{TextDecoder as ht}from"node:util";import{createGunzip as ut}from"node:zlib";import*as dt from"tar";import wt from"unzipper";const{ZipArchive:ft}=await import("archiver"),y={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},pt=new ht("gbk");function h(t){return String(t||"").replace(/\\/g,"/")}function O(t){const e=d.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function mt(t){const e=O(t),r=await o.realpath(e);if(!(await o.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function P(t){return{requestedRoot:O(t),workspaceRoot:await mt(t)}}function p(t,e){const r=d.resolve(e),i=d.relative(t,r);if(i.startsWith("..")||d.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function k(t,e){const r=await o.realpath(e);return p(t,r)}async function E(t,e){const r=await o.realpath(d.dirname(e));p(t,r);try{if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${e}`)}catch(i){if((typeof i=="object"&&i!==null?Reflect.get(i,"code"):void 0)!=="ENOENT")throw i}}function z(t,e,r){const i=d.resolve(r),a=d.relative(t,i);if(a.startsWith("..")||d.isAbsolute(a))throw new Error(`Target path is outside workspace: ${i}`);return p(e,d.join(e,a))}async function $({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await P(t),a=String(e||"").trim(),n=a?z(r,i,a):i,s=await k(i,n);return{workspaceRoot:i,resolvedTargetPath:s}}function D(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||d.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function U(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function C(t){const e=U(t);return D(d.basename(e.replace(/\\/g,"/")))}function gt(t,e){if(!t)return C(e);const i=U(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(a=>D(a));return i.length===0?C(e):d.join(...i)}function _(t){return(t&4095).toString(8).padStart(4,"0")}function yt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function B(t,e,r,i,a,n,s){const c=t&e?"r":"-",u=t&r?"w":"-",l=(t&i)!==0,w=(t&a)!==0?l?n:s:l?"x":"-";return`${c}${u}${w}`}function Pt(t){return[B(t,256,128,64,2048,"s","S"),B(t,32,16,8,1024,"s","S"),B(t,4,2,1,512,"t","T")].join("")}function kt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function xt(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function vt(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function Et(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function G(t,e,r){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);const a=await o.stat(e);if(!r||!a.isDirectory())return[e];const n=await o.readdir(e),s=await Promise.all(n.map(c=>{const u=p(t,d.join(e,c));return G(t,u,!0)}));return[e,...s.flat()]}async function St(t,e){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);await k(t,e)}async function Ft(t){const e=await o.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),a=r.subarray(0,i);return kt(a)||xt(a)||vt(a)||Et(a)}finally{await e.close()}}async function S({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:a}=await P(t),n=String(e||"").trim();if(!n)throw new Error("targetPath is required");const s=z(i,a,n),c=await k(a,s);if(!r.allowWorkspaceRoot&&c===a)throw new Error("Workspace root cannot be modified");return{workspaceRoot:a,resolvedTargetPath:c}}async function Z(t){const{requestedRoot:e,workspaceRoot:r}=await P(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const a=z(e,r,i);if(await J(a))return{workspaceRoot:r,resolvedTargetPath:await k(r,a)};const n=await o.realpath(d.dirname(a));return p(r,n),{workspaceRoot:r,resolvedTargetPath:a}}function H(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function K(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function Rt(t,e){const r=D(K(e)||"extracted");return d.join(t,r)}function I(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>y.maxPathLength||i.length>y.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return p(t,d.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=pt.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function V(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function A(t){if(t.entries+=1,t.entries>y.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${y.maxEntries}`)}function X(t,e){if(t.totalBytes+=e,e>y.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${y.maxFileBytes} bytes`);if(t.totalBytes>y.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${y.maxTotalBytes} bytes`)}async function Y(t,e,r){let i=0;t.on("data",a=>{const n=Buffer.isBuffer(a)?a.length:Buffer.byteLength(a);i+=n,(i>y.maxFileBytes||r.totalBytes+i>y.maxTotalBytes)&&t instanceof ct&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await lt(t,st(e)),X(r,i)}async function J(t){try{return await o.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function L({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await P(t),a=String(e||"").trim();if(!a)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:z(r,i,a)}}async function Ot(t){const{workspaceRoot:e}=await P(t.workspacePath),r=String(t.targetPath||"").trim();if(!r){const{resolvedTargetPath:u}=await $(t);return Q(e,u)}const i=d.resolve(r);let a;try{a=await o.realpath(i)}catch{throw new Error(`Path not found: ${i}`)}const n=d.relative(e,a);if(!(n===""||!n.startsWith("..")&&!d.isAbsolute(n))){if(!(await o.stat(a)).isDirectory())throw new Error(`Path is not a directory: ${i}`);const f=(await o.readdir(i,{withFileTypes:!0})).filter(w=>w.isDirectory()).map(w=>({name:w.name,path:h(d.join(i,w.name)),isDirectory:!0}));return{currentPath:h(i),workspacePath:h(e),items:f}}const c=await k(e,i);return Q(e,c)}async function Q(t,e){if(!(await o.stat(e)).isDirectory())throw new Error(`Path is not a directory: ${e}`);const i=await o.readdir(e,{withFileTypes:!0}),a=await Promise.all(i.map(async n=>{const s=d.join(e,n.name),c=n.isFile()?(await o.stat(s)).size:void 0;return{name:n.name,path:h(s),isDirectory:n.isDirectory(),size:c}}));return{currentPath:h(e),workspacePath:h(t),items:a}}async function Ut(t){const{workspaceRoot:e,resolvedFilePath:r}=await L(t),i=await k(e,r);if(!(await o.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);const n=await o.readFile(i,"utf-8");return{workspacePath:h(e),filePath:h(i),content:n}}const tt={maxEntries:1e5,maxDepth:32};async function zt(t,e){let r=0,i=0;async function a(n,s){if(s>=tt.maxDepth)return;let c;try{c=await o.readdir(n,{withFileTypes:!0})}catch{return}for(const u of c){if(i+=1,i>tt.maxEntries)return;if(u.isSymbolicLink())continue;const l=p(t,d.join(n,u.name));try{if(u.isDirectory())await a(l,s+1);else if(u.isFile()){const f=await o.stat(l);r+=f.size}}catch{}}}return await a(e,0),r}async function _t(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t,{allowWorkspaceRoot:!0}),[i,a]=await Promise.all([o.stat(r),o.lstat(r)]),n=i.isDirectory(),s=i.isFile()?await Ft(r):void 0,c=n?await zt(e,r):i.size;return{workspacePath:h(e),targetPath:h(r),name:d.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:a.isSymbolicLink(),size:c,mode:i.mode,modeOctal:_(i.mode),permissions:Pt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...s?{image:s}:{}}}async function Gt(t){const e=yt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:a}=await S(t),n=await G(i,a,r);for(const s of n)await St(i,s),await o.chmod(s,e);return{ok:!0,workspacePath:h(i),targetPath:h(a),mode:e,modeOctal:_(e),recursive:r,changedCount:n.length}}async function Zt(t){const{workspaceRoot:e,resolvedFilePath:r}=await L(t),i=await k(e,r),a=await o.stat(i);if(!a.isFile())throw new Error(`Path is not a file: ${i}`);const n=d.extname(i).toLowerCase();if(n===".doc")return{workspacePath:h(e),filePath:h(i),kind:"unsupported",content:"",message:"\u6682\u4E0D\u652F\u6301\u65E7\u7248 .doc \u683C\u5F0F\u9884\u89C8\uFF0C\u8BF7\u53E6\u5B58\u4E3A .docx \u540E\u518D\u6253\u5F00\u3002",size:a.size,mtimeMs:a.mtimeMs};if(n!==".docx")throw new Error("Document preview only supports .docx files");return{workspacePath:h(e),filePath:h(i),kind:"html",content:"",size:a.size,mtimeMs:a.mtimeMs}}async function Ht(t){const{workspaceRoot:e,resolvedFilePath:r}=await L(t);return await o.mkdir(d.dirname(r),{recursive:!0}),await E(e,r),await o.writeFile(r,String(t.content??""),"utf-8"),{ok:!0,workspacePath:h(e),filePath:h(r)}}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await $(t),i=D(t.entryName),a=p(e,d.join(r,i));if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await o.access(a),new Error(`Path already exists: ${a}`)}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)!=="ENOENT")throw s}return t.isDirectory?await o.mkdir(a,{recursive:!1}):(await o.mkdir(d.dirname(a),{recursive:!0}),await E(e,a),await o.writeFile(a,"","utf-8")),{ok:!0,workspacePath:h(e),targetPath:h(a),isDirectory:t.isDirectory}}async function Vt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=D(t.newName),a=p(e,d.join(d.dirname(r),i));if(a===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)};await o.stat(r);try{throw await o.access(a),new Error(`Path already exists: ${a}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await o.rename(r,a),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)}}async function Xt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await $({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await o.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await o.stat(r)).isDirectory()){const c=d.relative(r,i.resolvedTargetPath);if(c===""||!c.startsWith("..")&&!d.isAbsolute(c))throw new Error("Cannot move a directory into itself or its descendant")}const s=p(e,d.join(i.resolvedTargetPath,d.basename(r)));if(s===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)};try{throw await o.access(s),new Error(`Path already exists: ${s}`)}catch(c){if((typeof c=="object"&&c!==null?Reflect.get(c,"code"):void 0)!=="ENOENT")throw c}return await o.rename(r,s),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)}}async function Yt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t);return(await o.stat(r)).isDirectory()?await o.rm(r,{recursive:!0,force:!1}):await o.unlink(r),{ok:!0,workspacePath:h(e),targetPath:h(r)}}async function Jt(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await Z(t);if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=[];for(const n of t.files){const s=C(n.originalname),c=gt(n.relativePath,n.originalname),u=p(e,d.join(r,c));await o.mkdir(d.dirname(u),{recursive:!0}),await E(e,u),await o.copyFile(n.path,u);let l;t.extractArchives&&H(u)&&(l=await Ct({workspacePath:e,archivePath:u,outputPath:Rt(r,s)})),a.push({fileName:s,targetPath:h(u),size:n.size,extracted:l})}return{ok:!0,workspacePath:h(e),targetPath:h(r),files:a}}async function Qt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await o.stat(r),a=d.basename(r);return{workspacePath:h(e),targetPath:h(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${a||"workspace"}.zip`:a,contentType:i.isDirectory()?"application/zip":Dt(r),isDirectory:i.isDirectory()}}function Dt(t){const e=d.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function te(t,e){const r=new ft({zlib:{level:9}}),i=new Promise((a,n)=>{e.on("close",a),e.on("finish",a),r.on("error",n),e.on("error",n)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function Wt(t,e,r){let i=0,a;const n=q(t),s=n.pipe(wt.Parse({forceStream:!0}));try{for await(const c of s){if(a){await V(c);continue}const u=Tt(c);try{const l=I(e,u);if(A(r),c.type==="Directory"){await o.mkdir(l,{recursive:!0});continue}if(c.type!=="File")throw new Error(`Unsupported archive entry type: ${u}`);await o.mkdir(d.dirname(l),{recursive:!0}),await E(e,l),await Y(c,l,r),i+=1}catch(l){a=l,await V(c)}}}finally{n.destroy(),s.destroy()}if(a)throw a;return i}async function Lt(t,e,r,i){let a=0,n;const s=(c,u)=>{if(n)return!1;try{I(e,c),A(i);const l=String(u.type||"").toLowerCase();if(l==="symboliclink"||l==="link")throw new Error(`Archive links are not supported: ${c}`);if(l!=="file"&&l!=="oldfile"&&l!=="contiguousfile"&&l!=="directory")throw new Error(`Unsupported archive entry type: ${c}`);return typeof u.size=="number"&&X(i,u.size),l!=="directory"&&(a+=1),!0}catch(l){return n=l,!1}};if(await dt.x({file:t,cwd:e,gzip:r,strict:!0,filter:(c,u)=>s(c,u)}),n)throw n;return a}async function $t(t,e,r){const i=K(d.basename(t)),a=I(e,i||"decompressed");return A(r),await o.mkdir(d.dirname(a),{recursive:!0}),await E(e,a),await Y(q(t).pipe(ut()),a,r),1}async function Ct(t){const{requestedRoot:e,workspaceRoot:r}=await P(t.workspacePath),{resolvedTargetPath:i}=await Z({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),a=z(e,r,String(t.archivePath||"").trim()),n=await k(r,a);if(!(await o.stat(n)).isFile())throw new Error(`Archive path is not a file: ${n}`);if(await J(i)||await o.mkdir(i,{recursive:!0}),!(await o.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const u=H(n);if(!u)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let l=0;const f={entries:0,totalBytes:0};return u==="zip"?l=await Wt(n,i,f):u==="tar"||u==="tgz"?l=await Lt(n,i,u==="tgz",f):l=await $t(n,i,f),{ok:!0,workspacePath:h(r),archivePath:h(n),targetPath:h(i),extractedEntries:l}}function et(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,a]=e;return i.split(",").flatMap(n=>et(r+n.trim()+a))}function rt(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return et(r.toLowerCase()).some(n=>{if(n==="*")return!0;const s=n.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${s}$`).test(i)})}function it(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const at=10*1024*1024,Bt=1e3;async function ee(t){const{workspaceRoot:e}=await P(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,a=String(t.fileMask||"").trim(),n=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:Bt,s=i?r.toLowerCase():r,u=(t.mode==="filename"?"filename":"content")==="filename",l=[];let f=0,w=!1;async function m(v){if(w)return;let F;try{F=await o.readdir(v,{withFileTypes:!0})}catch{return}for(const g of F){if(w)return;const R=p(e,d.join(v,g.name));if(g.isSymbolicLink())continue;if(g.isDirectory()){if(it(g.name))continue;await m(R);continue}if(!g.isFile()||a&&!rt(g.name,a))continue;f+=1;let b;try{b=await o.stat(R)}catch{continue}if(b.size>at)continue;if(u){const x=(i?g.name.toLowerCase():g.name).indexOf(s);if(x!==-1&&(l.push({filePath:h(R),lineNumber:0,lineContent:g.name,matchStart:x,matchEnd:x+r.length}),l.length>=n)){w=!0;return}continue}let N;try{N=await o.readFile(R,"utf-8")}catch{continue}const M=N.split(/\r?\n/);for(let T=0;T<M.length;T+=1){const x=M[T],ot=i?x.toLowerCase():x;let j=0;for(;;){const W=ot.indexOf(s,j);if(W===-1)break;if(l.push({filePath:h(R),lineNumber:T+1,lineContent:x,matchStart:W,matchEnd:W+r.length}),l.length>=n){w=!0;return}j=W+1}}}}return await m(e),{workspacePath:h(e),keyword:r,totalFiles:f,totalMatches:l.length,matches:l,truncated:w}}async function nt(t,e,r,i,a){let n;try{n=await o.readFile(e,"utf-8")}catch{return{changed:!1,count:0}}const s=n;let c=0;if(a){const u=r.toLowerCase();let l=0;for(;;){const w=n.toLowerCase().indexOf(u,l);if(w===-1)break;n=n.slice(0,w)+i+n.slice(w+r.length),c+=1;const m=i.length||1;l=w+m}}else{let u=0;for(;;){const l=n.indexOf(r,u);if(l===-1)break;n=n.slice(0,l)+i+n.slice(l+r.length),c+=1;const f=i.length||1;u=l+f}}return n===s?{changed:!1,count:0}:(await E(t,e),await o.writeFile(e,n,"utf-8"),{changed:!0,count:c})}async function re(t){const{workspaceRoot:e}=await P(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),a=!!t.ignoreCase,n=String(t.fileMask||"").trim(),s=String(t.filePath||"").trim();let c=0,u=0;if(s){const{resolvedFilePath:f}=await L({workspacePath:t.workspacePath,filePath:s}),w=await nt(e,f,r,i,a);return w.changed&&(c=1,u=w.count),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}async function l(f){let w;try{w=await o.readdir(f,{withFileTypes:!0})}catch{return}for(const m of w){const v=p(e,d.join(f,m.name));if(m.isSymbolicLink())continue;if(m.isDirectory()){if(it(m.name))continue;await l(v);continue}if(!m.isFile()||n&&!rt(m.name,n))continue;try{if((await o.stat(v)).size>at)continue}catch{continue}const F=await nt(e,v,r,i,a);F.changed&&(c+=1,u+=F.count)}}return await l(e),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}export{Gt as chmodWorkspaceEntry,Kt as createWorkspaceEntry,Yt as deleteWorkspaceEntry,Ct as extractWorkspaceArchive,_t as getWorkspaceEntryProperties,Ot as listWorkspaceDirectory,Xt as moveWorkspaceEntry,Zt as previewWorkspaceDocument,Ut as readWorkspaceFile,Vt as renameWorkspaceEntry,re as replaceWorkspaceFiles,Qt as resolveWorkspaceDownloadTarget,ee as searchWorkspaceFiles,te as streamWorkspaceDirectoryZip,Jt as uploadWorkspaceFiles,Ht as writeWorkspaceFile};
|
|
2
2
|
|
|
@@ -5,7 +5,7 @@ export interface TaskContext {
|
|
|
5
5
|
taskNodeId: string;
|
|
6
6
|
tenantId: string;
|
|
7
7
|
workspacePath: string;
|
|
8
|
-
|
|
8
|
+
projectId: string;
|
|
9
9
|
}
|
|
10
10
|
export interface FileChangeRecord {
|
|
11
11
|
workspacePath: string;
|
|
@@ -16,6 +16,8 @@ export interface FileChangeRecord {
|
|
|
16
16
|
beforeHash: string | null;
|
|
17
17
|
afterHash: string | null;
|
|
18
18
|
timestamp: string;
|
|
19
|
+
|
|
20
|
+
reason: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export declare function setTaskContext(ctx: TaskContext): void;
|
|
@@ -45,4 +47,10 @@ export declare function recordAgentFileChange(record: {
|
|
|
45
47
|
operation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
46
48
|
beforeContent: string | null;
|
|
47
49
|
afterContent: string | null;
|
|
50
|
+
|
|
51
|
+
reason?: string;
|
|
52
|
+
|
|
53
|
+
agentId?: string;
|
|
54
|
+
|
|
55
|
+
workspacePath?: string;
|
|
48
56
|
}): Promise<void>;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import{promises as
|
|
1
|
+
import{promises as u}from"node:fs";import h from"node:path";import{createHash as $}from"node:crypto";import L from"axios";import*as S from"diff";import{createLogger as _}from"../utils/logger.js";import{resolveSchedulerBaseUrl as N}from"../config.js";import{getRuntimeAccessToken as D}from"./runtime-binding.js";const r=_("workspace-task-change-watcher"),H=new Set([".git","node_modules","__pycache__",".cache",".next","target","dist",".idea",".vscode",".agentswork-snapshots",".aws-bridge"]),R=new Set([".lock",".log",".pid",".swp",".swo",".tmp"]),b=500,U=10*1024*1024,W=".agentswork/change-cache";let f=null,E=!1,w="",p=null;const d=new Map,l=new Map;let C=!1;function G(e){f&&f.taskNodeId!==e.taskNodeId&&A().catch(()=>{}),f=e,r.info(`Task context set: agent=${e.agentId}, taskInstance=${e.taskInstanceId}, node=${e.taskNodeId}`)}function J(){f&&(r.info(`Task context cleared: agent=${f.agentId}`),A().catch(()=>{})),f=null}function Z(){return f}function q(){return f!==null}async function Q(e){const t=h.resolve(e.trim());try{if(!(await u.stat(t)).isDirectory())throw new Error(`Not a directory: ${t}`)}catch{throw new Error(`Workspace path does not exist or inaccessible: ${t}`)}if(p){try{p.close()}catch{}p=null}w=t,E=!0,r.info(`Workspace path set (fs.watch disabled, using tool interception only): ${w}`)}async function V(){if(E=!1,p){try{p.close()}catch{}p=null}d.clear(),l.clear(),w="",r.info("Workspace watch stopped")}function x(e,t,n){return $("sha256").update(`${e||"no-task"}:${t}:${n}`,"utf-8").digest("hex").slice(0,32)}function k(e){return h.join(e,W)}async function y(e,t,n){if(n===null)return;const o=k(e);await u.mkdir(o,{recursive:!0});const a=h.join(o,`${t}.before`);await u.writeFile(a,n,"utf-8"),r.debug(`Saved beforeContent cache: ${a}`)}async function P(e,t,n,o){const a=S.createPatch("file",n,o),s=k(e);await u.mkdir(s,{recursive:!0});const i=h.join(s,`${t}.diff`);await u.writeFile(i,a,"utf-8"),r.debug(`Saved diff cache: ${i}`)}async function Y(e,t){try{const n=h.join(k(e),`${t}.before`);return await u.readFile(n,"utf-8")}catch{return null}}async function ee(e,t){try{const n=h.join(k(e),`${t}.diff`);return await u.readFile(n,"utf-8")}catch{return null}}function te(e){const t=e.split(/[/\\]/);for(const o of t.slice(0,-1))if(H.has(o))return!0;const n=h.extname(t[t.length-1]).toLowerCase();return!!R.has(n)}async function T(e){try{return(await u.stat(e)).size>U?null:await u.readFile(e,"utf-8")}catch{return null}}function m(e){return e===null?null:$("sha256").update(e,"utf-8").digest("hex").slice(0,16)}async function ne(e,t){}function ae(e,t,n){const o=d.get(n);if(o)t==="DELETE"||o.operation==="DELETE"?o.operation="DELETE":o.operation==="CREATE"&&t==="UPDATE"||(o.operation=t),o.timer&&clearTimeout(o.timer),o.timer=setTimeout(()=>{I(n).catch(()=>{})},b);else{let a=null;if(t==="UPDATE"||t==="DELETE")if(l.has(n))a=l.get(n)??null;else{T(n).then(i=>{i!==void 0&&l.set(n,i)}).catch(()=>{});return}const s={filePath:n,operation:t,timer:null,beforeContent:a};s.timer=setTimeout(()=>{I(n).catch(()=>{})},b),d.set(n,s)}}async function I(e){const t=d.get(e);if(!t)return;d.delete(e),t.timer&&clearTimeout(t.timer);const n=f;if(!n){if(t.operation==="DELETE")l.delete(e);else{const g=await T(e);g!==null&&l.set(e,g)}return}const o=h.relative(n.workspacePath,e).replace(/\\/g,"/");if(o.startsWith(".."))return;let a=t.beforeContent,s=null;t.operation==="DELETE"?(a=a??l.get(e)??null,s=null):(s=await T(e),t.operation==="UPDATE"&&a===null&&(a=l.get(e)??null)),t.operation==="DELETE"?l.delete(e):l.set(e,s);const i=new Date().toISOString(),c=x(n.taskInstanceId,o,i);t.operation==="UPDATE"&&a!==null&&s!==null?await P(n.workspacePath,c,a,s):t.operation==="DELETE"&&await y(n.workspacePath,c,a),await v(n,{workspacePath:n.workspacePath,filePath:o,operation:t.operation,beforeContent:a,afterContent:s,beforeHash:m(a),afterHash:m(s),timestamp:i,reason:F(t.operation,a,s)},c)}async function v(e,t,n){try{const o=N().url,a=D(o)||D();if(!a){r.warn("Cannot send change record: no auth token");return}const s=JSON.stringify({toolRecorded:!0,cacheKey:n,timestamp:t.timestamp}),c=(await L.post(`${o}/api/runtime/callback/task-file-changed`,{agentId:e.agentId,taskInstanceId:e.taskInstanceId,taskNodeId:e.taskNodeId,tenantId:e.tenantId,operation:t.operation,targetType:"FILE",targetPath:t.filePath,beforeHash:t.beforeHash,afterHash:t.afterHash,metadata:s,reason:t.reason,workspacePath:e.workspacePath},{headers:{"X-Runtime-Token":a},timeout:1e4})).data;c?.ignored===!0?r.warn(`Change record ignored by server: ${t.filePath} (${t.operation}), reason: ${String(c.reason??"unknown")}`):r.info(`Change record sent: ${t.filePath} (${t.operation})`)}catch(o){r.warn(`Failed to send change record: ${o.message}`)}}async function A(){if(C)return;C=!0;const e=Array.from(d.keys());for(const t of e)try{await I(t)}catch{}C=!1}function oe(){return d.size}function se(){return E&&p!==null}function ie(){return w}function F(e,t,n){const o=t?t.split(`
|
|
2
|
+
`).length:0,a=n?n.split(`
|
|
3
|
+
`).length:0;if(e==="CREATE")return`\u521B\u5EFA\u6587\u4EF6\uFF0C\u5171 ${a} \u884C`;if(e==="DELETE")return`\u5220\u9664\u6587\u4EF6\uFF0C\u539F ${o} \u884C`;if(o===a)return`\u4FEE\u6539\u6587\u4EF6\uFF08\u884C\u6570\u4E0D\u53D8\uFF0C${a} \u884C\uFF09`;const s=a-o;return`\u4FEE\u6539\u6587\u4EF6\uFF1A${o} \u884C -> ${a} \u884C\uFF08${s>0?"+":""}${s} \u884C\uFF09`}async function re(e){const t=f,n=e.agentId||t?.agentId,o=e.workspacePath||t?.workspacePath;if(!n||!o){r.warn(`Agent file change skipped (no identity): ${e.filePath} (${e.operation})`);return}r.info(`Recording agent file change: ${e.filePath} (${e.operation}), taskNode=${t?.taskNodeId??"(no task)"}`);const a=t??{agentId:n,taskInstanceId:"",taskNodeId:"",tenantId:"",workspacePath:o,projectId:""},s=h.join(a.workspacePath,e.filePath),i=new Date().toISOString(),c=x(a.taskInstanceId,e.filePath,i);e.operation==="UPDATE"&&e.beforeContent!==null&&e.afterContent!==null?(await P(a.workspacePath,c,e.beforeContent,e.afterContent),await y(a.workspacePath,c,e.beforeContent)):e.operation==="DELETE"&&await y(a.workspacePath,c,e.beforeContent);const g=e.reason?.trim()||F(e.operation,e.beforeContent,e.afterContent);await v(a,{workspacePath:a.workspacePath,filePath:e.filePath,operation:e.operation,beforeContent:e.beforeContent,afterContent:e.afterContent,beforeHash:m(e.beforeContent),afterHash:m(e.afterContent),timestamp:i,reason:g},c)}export{J as clearTaskContext,oe as getPendingChangeCountForTest,Z as getTaskContext,ie as getWorkspaceRoot,q as hasActiveTask,se as isWatching,Y as readBeforeContentFromCache,ee as readDiffFromCache,re as recordAgentFileChange,G as setTaskContext,Q as startWatching,V as stopWatching};
|
|
2
4
|
|
|
@@ -24,6 +24,8 @@ export interface ACodeBuiltInToolExecutionContext {
|
|
|
24
24
|
readonly onOutput?: CommandOutputSink;
|
|
25
25
|
|
|
26
26
|
readonly toolCallId?: string;
|
|
27
|
+
|
|
28
|
+
readonly settleSignal?: Promise<void>;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface AgentFileChangeRecord {
|
|
@@ -31,6 +33,8 @@ export interface AgentFileChangeRecord {
|
|
|
31
33
|
operation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
32
34
|
beforeContent: string | null;
|
|
33
35
|
afterContent: string | null;
|
|
36
|
+
|
|
37
|
+
reason?: string;
|
|
34
38
|
}
|
|
35
39
|
export type OnAgentFileChange = (record: AgentFileChangeRecord) => void | Promise<void>;
|
|
36
40
|
export interface ResolvedWorkspaceTarget {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomUUID as Y}from"node:crypto";import{promises as
|
|
1
|
+
import{randomUUID as Y}from"node:crypto";import{promises as u,createReadStream as N}from"node:fs";import p from"node:path";import U from"node:readline";import{runShellCommand as K}from"./builtins/shellCommandRunner.js";import{symbolsTool as H}from"./built-in-symbols-tool.js";import{createListBackgroundCommandsTool as V,createKillBackgroundCommandTool as Z,createReadBackgroundOutputTool as J}from"./background-command-tools.js";const Q=2e3,M=2e3,tt=50*1024,D=4096,et=.3,ot=new Set([".png",".jpg",".jpeg",".gif",".bmp",".ico",".webp",".tiff",".tif",".zip",".gz",".tar",".rar",".7z",".bz2",".xz",".lz",".exe",".dll",".so",".dylib",".bin",".o",".a",".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".mp3",".mp4",".avi",".mov",".wav",".flv",".mkv",".ogg",".woff",".woff2",".ttf",".otf",".eot",".class",".jar",".war",".ear",".pyc",".pyo",".node",".sqlite",".db",".mdb",".wasm"]),nt=1e3,it=1024*1024,rt=256*1024,at=1e3,st=new Set([".git","node_modules","dist","build",".next",".nuxt",".cache",".turbo",".output","coverage",".nyc_output"]);function b(n){return n.replace(/\\/g,"/")}async function W(n){try{return await u.access(n),!0}catch{return!1}}function ct(n,o){if(!o)return 0;let t=0,e=n.indexOf(o);for(;e>=0;)t+=1,e=n.indexOf(o,e+o.length);return t}function P(n,o,t){for(const e of o){const r=n[e];if(typeof r=="string")return r}throw new Error(`${t} is required`)}function E(n,o){for(const t of o){const e=n[t];if(typeof e=="string"&&e.trim())return e}}function C(n,o,t){for(const e of o){const r=n[e],i=typeof r=="number"?r:typeof r=="string"?Number(r):NaN;if(Number.isFinite(i)&&i>0)return Math.floor(i)}return t}async function q(n){const o=p.extname(n).toLowerCase();if(ot.has(o))return!0;const t=await u.open(n,"r");try{const e=Buffer.alloc(D),{bytesRead:r}=await t.read(e,0,D,0);if(r===0)return!1;const i=e.subarray(0,r);if(i.includes(0))return!0;let c=0;for(let a=0;a<r;a++){const l=i[a];(l<9||l>13&&l<32)&&c++}return c/r>et}finally{await t.close()}}function S(n,o){const t=p.resolve(o),e=p.relative(n,t);if(e.startsWith("..")||p.isAbsolute(e))throw new Error(`Target path is outside workspace: ${t}`);return t}async function lt(n,o){const t=p.resolve(String(n||"").trim()),e=await u.realpath(t);if(!(await u.stat(e)).isDirectory())throw new Error(`Workspace path is not a directory: ${t}`);if(!o?.trim())return{workspaceRoot:e,cwd:e};const i=o.trim(),c=p.isAbsolute(i)?p.resolve(i):p.resolve(t,i),a=p.relative(t,c);if(a.startsWith("..")||p.isAbsolute(a))throw new Error(`Command cwd is outside workspace: ${c}`);const l=S(e,await u.realpath(c));if(!(await u.stat(l)).isDirectory())throw new Error(`Command cwd is not a directory: ${l}`);return{workspaceRoot:e,cwd:l}}async function ut(n,o){const t=await u.realpath(p.dirname(o));S(n,t);try{if((await u.lstat(o)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${o}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}async function O(n,o){const t=p.resolve(String(n||"").trim()),e=await u.realpath(t);if(!(await u.stat(e)).isDirectory())throw new Error(`Workspace path is not a directory: ${t}`);const i=o.trim();if(!i)throw new Error("file_path is required");const c=p.isAbsolute(i)?p.resolve(i):p.resolve(t,i),a=p.relative(t,c);if(a.startsWith("..")||p.isAbsolute(a))throw new Error(`Target path is outside workspace: ${c}`);return{workspaceRoot:e,requestedPath:c,targetPath:S(e,p.join(e,a))}}async function j(n,o,t){const e=await O(n,o),r=p.dirname(e.targetPath),i=!await W(r);await u.mkdir(r,{recursive:!0}),await ut(e.workspaceRoot,e.targetPath);const c=p.join(r,`.${p.basename(e.targetPath)}.${process.pid}.${Y()}.tmp`);try{await u.writeFile(c,t,"utf-8"),await u.rename(c,e.targetPath)}catch(a){throw await u.rm(c,{force:!0}),a}return{workspaceRoot:e.workspaceRoot,targetPath:e.targetPath,bytes:Buffer.byteLength(t,"utf-8"),createdParentDirectory:i}}async function dt(n,o){const t=await O(n,o),e=S(t.workspaceRoot,await u.realpath(t.targetPath));if(!(await u.stat(e)).isFile())throw new Error(`Path is not a file: ${e}`);return{workspaceRoot:t.workspaceRoot,targetPath:e,content:await u.readFile(e,"utf-8")}}async function pt(n,o,t,e){const r=await O(n,o),i=S(r.workspaceRoot,await u.realpath(r.targetPath));if(!(await u.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);if(await q(i))return{workspaceRoot:r.workspaceRoot,targetPath:i,content:"[Binary file, content skipped]",isBinary:!0,truncated:!1,totalLines:0,shownFromLine:0,shownToLine:0};const a=N(i,{encoding:"utf-8"}),l=U.createInterface({input:a,crlfDelay:1/0});try{const s=[];let m=0,f=0,g=0,w=!1,d=!1;const _=t-1;for await(const L of l){if(f++,m<_){m++;continue}if(s.length>=e){w=!0,m++;continue}let T=L;T.length>M&&(T=T.slice(0,M)+`... (line truncated to ${M} chars)`);const v=`${m+1}: ${T}`,R=Buffer.byteLength(v,"utf-8")+1;if(g+R>tt){d=!0;break}s.push(v),g+=R,m++}if(f>0&&s.length===0&&!w&&!d&&_>=f)throw new Error(`offset ${t} exceeds total line count ${f} in file: ${i}`);const h=s.length>0?t:0,y=s.length>0?t+s.length-1:0;let x;d?x=`Output capped at 50 KB. Showing lines ${h}-${y}. Use offset=${y+1} to continue.`:w?x=`Showing lines ${h}-${y} of ${f}. Use offset=${y+1} to continue.`:x=`End of file - total ${f} lines`;const B=s.length>0?s.join(`
|
|
2
2
|
`)+`
|
|
3
|
-
`+P:P;return{workspaceRoot:r.workspaceRoot,targetPath:n,content:B,isBinary:!1,truncated:h||l,totalLines:l?-1:p,shownFromLine:b,shownToLine:w}}finally{a.close(),s.destroy()}}function G(i){let o="",e=0;for(;e<i.length;){const t=i[e];if(t==="*")o+="[^/]*";else if(t==="?")o+="[^/]";else if(t==="{"){const r=i.indexOf("}",e);if(r>=0){const n=i.slice(e+1,r);o+=`(${n.split(",").join("|")})`,e=r}else o+="\\{"}else if(t==="["){const r=i.indexOf("]",e);r>=0?(o+=i.slice(e,r+1),e=r):o+="\\["}else"+^$.()|\\".includes(t)?o+="\\"+t:o+=t;e++}return new RegExp(`^${o}$`)}async function pt(i,o,e,t){const r=[];async function n(c){if(r.length>=t)return;const s=await d.readdir(c,{withFileTypes:!0});for(const a of s){if(r.length>=t)return;if(a.name.startsWith(".")&&a.isDirectory())continue;const u=f.join(c,a.name);if(a.isDirectory()){if(st.has(a.name))continue;await n(u)}else if(a.isFile()){if(o&&!o.test(a.name)||e&&e.test(a.name))continue;r.push(u)}}}return await n(i),r}async function mt(i,o,e){if((await d.stat(i)).size>it)return{matches:[],count:0};if(await W(i))return{matches:[],count:0};const r=f.relative(o,i).replace(/\\/g,"/"),n=[];let c=0;const s=N(i,{encoding:"utf-8"}),a=D.createInterface({input:s,crlfDelay:1/0});try{let u=0;for await(const m of a)if(u++,e.regex.test(m)){if(c++,e.collectContent){let p=m;if(p.length>E&&(p=p.slice(0,E)+`... (line truncated to ${E} chars)`),n.push({file:r,line:u,content:p}),n.length>=e.maxResults)break}if(e.stopOnFirstMatch)break}}finally{a.close(),s.destroy()}return{matches:n,count:c}}const X=10*6e4,ht=X,wt=6e4,gt=10*6e4;function yt(i,o){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",description:"Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to write."},content:{type:"string",description:"Complete UTF-8 file content."}},required:["file_path","content"],additionalProperties:!1},async execute(e){const t=k(e,["file_path","filePath"],"file_path"),r=k(e,["content"],"content"),n=await S(i,t),c=await q(n.targetPath),s=c?await d.readFile(n.targetPath,"utf-8").catch(()=>null):null,a=await j(i,t,r);if(o){const u=f.relative(a.workspaceRoot,a.targetPath).replace(/\\/g,"/");await o({filePath:u,operation:c?"UPDATE":"CREATE",beforeContent:s,afterContent:r})}return{ok:!0,tool:"write_file",workspacePath:_(a.workspaceRoot),filePath:_(a.targetPath),bytes:a.bytes,createdParentDirectory:a.createdParentDirectory}}}}function _t(i,o){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",description:"Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to edit."},old_string:{type:"string",description:"Existing text to replace. Must occur exactly once."},new_string:{type:"string",description:"Replacement text."}},required:["file_path","old_string","new_string"],additionalProperties:!1},async execute(e){const t=k(e,["file_path","filePath"],"file_path"),r=k(e,["old_string","oldString"],"old_string"),n=k(e,["new_string","newString"],"new_string");if(!r)throw new Error("old_string must not be empty");const c=await dt(i,t),s=ct(c.content,r);if(s===0)throw new Error("old_string was not found in file");if(s>1)throw new Error(`old_string occurs ${s} times; provide a unique match`);const a=c.content.replace(r,n),u=await j(i,t,a);if(o){const m=f.relative(u.workspaceRoot,u.targetPath).replace(/\\/g,"/");await o({filePath:m,operation:"UPDATE",beforeContent:c.content,afterContent:a})}return{ok:!0,tool:"edit_file",workspacePath:_(u.workspaceRoot),filePath:_(u.targetPath),replacements:1,bytes:u.bytes}}}}function kt(i,o){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",description:"Delete a file inside the current workspace. Returns an error if the file does not exist.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to delete."}},required:["file_path"],additionalProperties:!1},async execute(e){const t=k(e,["file_path","filePath"],"file_path"),r=await S(i,t),n=R(r.workspaceRoot,await d.realpath(r.targetPath).catch(()=>r.targetPath)),c=await d.stat(n).catch(()=>null);if(!c)throw new Error(`File does not exist: ${n}`);if(!c.isFile())throw new Error(`Path is not a file: ${n}`);const s=await d.readFile(n,"utf-8").catch(()=>null);if(await d.rm(n,{force:!0}),o){const a=f.relative(r.workspaceRoot,n).replace(/\\/g,"/");await o({filePath:a,operation:"DELETE",beforeContent:s,afterContent:null})}return{ok:!0,tool:"delete_file",workspacePath:_(r.workspaceRoot),filePath:_(n),deleted:!0}}}}function bt(i){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",description:"Read a UTF-8 text file inside the current workspace. Returns content with line numbers in the format '<line>: <content>'. Supports offset (1-indexed line number to start from) and limit (max lines to return, default 2000). Output is capped at 2000 lines or 50 KB, whichever is reached first. Individual lines longer than 2000 characters are truncated. Avoid tiny repeated slices (e.g. 30-line chunks); read a larger window if more context is needed.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read."},offset:{type:"number",description:"Line number to start reading from (1-indexed). Defaults to 1."},limit:{type:"number",description:"Maximum number of lines to return. Defaults to 2000."}},required:["file_path"],additionalProperties:!1},async execute(o){const e=k(o,["file_path","filePath"],"file_path"),t=O(o,["offset"],1),r=O(o,["limit"],Q),n=await ft(i,e,t,r);return{ok:!0,tool:"read_file",workspacePath:_(n.workspaceRoot),filePath:_(n.targetPath),content:n.content,isBinary:n.isBinary,truncated:n.truncated,totalLines:n.totalLines,shownFromLine:n.shownFromLine,shownToLine:n.shownToLine}}}}function Pt(i,o,e){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded timeout and returns stdout, stderr, and exit code. Set wait_seconds to 0 or n>0 to move long-running commands to the background and return a task_id immediately (or after n seconds); then use ListBackgroundCommands / ReadBackgroundOutput / KillBackgroundCommand to manage them.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute."},cwd:{type:"string",description:"Optional workspace-relative working directory. Defaults to the workspace root."},timeout_ms:{type:"number",description:"Optional timeout in milliseconds. Defaults to 600000 and is capped at 600000."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000. Only applies to foreground (wait_seconds=-1); ignored once a command moves to background."},wait_seconds:{type:"number",description:"How long to block waiting for the command to finish. -1 (default): block until completion (current behavior, capped at timeout_ms). 0: spawn and immediately move to background, return task_id. n>0: wait up to n seconds; if still running, move to background and return task_id. Background commands remain subject to timeout_ms (total upper bound); inactivity_timeout_ms is ignored once backgrounded.",default:-1,minimum:-1},description:{type:"string",description:"Optional short reason for running the command."}},required:["command"],additionalProperties:!1},async execute(t,r){const n=k(t,["command"],"command").trim();if(!n)throw new Error("command must not be empty");const c=O(t,["timeout_ms","timeoutMs"],ht),s=Math.min(c,X),a=O(t,["inactivity_timeout_ms","inactivityTimeoutMs"],wt),u=Math.min(a,gt),m=A(t,["cwd"]),p=await lt(i,m),g=t.wait_seconds,h=typeof g=="number"&&Number.isFinite(g)?g:-1;if(h!==-1&&e)return await xt({deps:e,command:n,agentId:o,workspaceRoot:p.workspaceRoot,cwd:p.cwd,timeoutMs:s,waitSeconds:h});const l=await K({commandText:n,agentId:o,workspaceRoot:p.workspaceRoot,cwd:p.cwd,timeoutMs:s,inactivityTimeoutMs:u,onOutput:r?.onOutput});return{ok:l.exitCode===0&&!l.timedOut,tool:"Bash",command:n,cwd:_(p.cwd),exitCode:l.exitCode,signal:l.signal,timedOut:l.timedOut,inactivityTimedOut:l.inactivityTimedOut,timeoutMs:s,inactivityTimeoutMs:u,durationMs:l.durationMs,outputSpilled:l.outputSpilled,outputFilePath:l.outputFilePath,outputLineCount:l.outputLineCount,commandFilePath:l.commandFilePath,commandLineCount:l.commandLineCount,stdout:l.stdout,stderr:l.stderr}}}}async function xt(i){const{deps:o,command:e,agentId:t,workspaceRoot:r,cwd:n,timeoutMs:c,waitSeconds:s}=i;try{const{taskId:a,completion:u}=o.manager.startCommand({commandText:e,agentId:t,workspaceRoot:r,cwd:n,timeoutMs:c,onCompleted:o.emitCompleted});if(s===0)return{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:a,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use ListBackgroundCommands to view, ReadBackgroundOutput(task_id="${a}", mode="tail") to read output, KillBackgroundCommand(task_id="${a}") to stop.`};const m=Math.max(0,Math.floor(s*1e3));let p;const g=new Promise(h=>{p=setTimeout(()=>h("__timeout__"),m),p.unref?.()});try{const h=await Promise.race([u,g]);if(h==="__timeout__")return{ok:!0,tool:"Bash",command:e,backgrounded:!0,taskId:a,status:"backgrounded",reason:"timeout",waitedMs:m,message:`Command still running after ${s}s; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${a}", mode="tail") to inspect.`};const l=h;return{ok:l.exitCode===0&&!l.timedOut,tool:"Bash",command:e,backgrounded:!1,taskId:a,exitCode:l.exitCode,signal:l.signal,timedOut:l.timedOut,durationMs:l.durationMs,stdout:l.stdout,stderr:l.stderr}}finally{p&&clearTimeout(p)}}catch(a){if(a?.code==="too_many_tasks"||a?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:e,backgrounded:!1,error:a.code,message:a.message};throw a}}function Tt(i){return{kind:"builtin",toolName:"grep",exposedName:"grep",description:"Search file contents using regular expressions within the workspace. Returns matched lines with file paths and line numbers. Supports include/exclude glob patterns, case-insensitive matching, and three output modes (content, files_with_matches, count). Searches recursively through directories, skipping binary files, hidden directories, and common build/dependency directories (node_modules, .git, dist, etc.).",inputSchema:{type:"object",properties:{pattern:{type:"string",description:"Regular expression pattern to search for in file contents."},path:{type:"string",description:"Workspace-relative file or directory path to search in. Defaults to the workspace root."},include:{type:"string",description:"Glob pattern to filter files by name (e.g. '*.ts', '*.{js,ts}'). Only matching files are searched."},exclude:{type:"string",description:"Glob pattern to exclude files by name (e.g. '*.test.ts'). Matching files are skipped."},case_insensitive:{type:"boolean",description:"Case-insensitive matching. Default false."},output_mode:{type:"string",enum:["content","files_with_matches","count"],description:"Output format: 'content' returns matched lines with line numbers (default), 'files_with_matches' returns only file paths with matches, 'count' returns match counts per file."},max_results:{type:"number",description:"Maximum number of matched lines to return (content mode). Default 1000."}},required:["pattern"],additionalProperties:!1},async execute(o){const e=k(o,["pattern"],"pattern"),t=A(o,["path"])||".",r=A(o,["include"]),n=A(o,["exclude"]),c=o.case_insensitive===!0,s=(()=>{const y=o.output_mode;return y==="files_with_matches"||y==="count"?y:"content"})(),a=O(o,["max_results"],at),u=c?"i":"";let m;try{m=new RegExp(e,u)}catch(y){throw new Error(`Invalid regex pattern: ${y instanceof Error?y.message:String(y)}`)}const p=r?G(r):null,g=n?G(n):null,h=await S(i,t),l=R(h.workspaceRoot,await d.realpath(h.targetPath)),M=await d.stat(l);let b;if(M.isFile())b=[l];else if(M.isDirectory())b=await pt(l,p,g,nt);else throw new Error(`Path is neither a file nor a directory: ${l}`);const w=[],P=[],B=[];let L=0,T=0,x=!1;for(const y of b){if(s==="content"&&w.length>=a){x=!0;break}const z={regex:m,maxResults:s==="content"?a-w.length:Number.MAX_SAFE_INTEGER,collectContent:s==="content",stopOnFirstMatch:s==="files_with_matches"},C=await mt(y,h.workspaceRoot,z);if(C.count>0){const I=f.relative(h.workspaceRoot,y).replace(/\\/g,"/");if(B.push(I),L+=C.count,s==="content"){for(const $ of C.matches){const F=Buffer.byteLength($.content,"utf-8")+64;if(T+F>rt){x=!0;break}T+=F,w.push($)}if(x)break;if(w.length>=a){x=!0;break}}else s==="count"&&P.push({file:I,count:C.count})}}const v={ok:!0,tool:"grep",pattern:e,searchPath:_(h.targetPath),outputMode:s,filesSearched:b.length,filesMatched:B.length,totalMatches:L,truncated:x};return s==="content"?v.matches=w:s==="files_with_matches"?v.matches=B:v.matches=P,v}}}function Lt(i,o,e,t){const r=o||"unknown",n=[bt(i),yt(i,e),_t(i,e),kt(i,e),Tt(i),Pt(i,r,t),H(i)];return t&&n.push(V(r,t),Z(r,t),J(r,t)),n}export{Lt as createBuiltInFileTools,R as ensureInsideWorkspace,S as resolveWorkspaceTarget};
|
|
3
|
+
`+x:x;return{workspaceRoot:r.workspaceRoot,targetPath:i,content:B,isBinary:!1,truncated:w||d,totalLines:d?-1:f,shownFromLine:h,shownToLine:y}}finally{l.close(),a.destroy()}}function G(n){let o="",t=0;for(;t<n.length;){const e=n[t];if(e==="*")o+="[^/]*";else if(e==="?")o+="[^/]";else if(e==="{"){const r=n.indexOf("}",t);if(r>=0){const i=n.slice(t+1,r);o+=`(${i.split(",").join("|")})`,t=r}else o+="\\{"}else if(e==="["){const r=n.indexOf("]",t);r>=0?(o+=n.slice(t,r+1),t=r):o+="\\["}else"+^$.()|\\".includes(e)?o+="\\"+e:o+=e;t++}return new RegExp(`^${o}$`)}async function ft(n,o,t,e){const r=[];async function i(c){if(r.length>=e)return;const a=await u.readdir(c,{withFileTypes:!0});for(const l of a){if(r.length>=e)return;if(l.name.startsWith(".")&&l.isDirectory())continue;const s=p.join(c,l.name);if(l.isDirectory()){if(st.has(l.name))continue;await i(s)}else if(l.isFile()){if(o&&!o.test(l.name)||t&&t.test(l.name))continue;r.push(s)}}}return await i(n),r}async function mt(n,o,t){if((await u.stat(n)).size>it)return{matches:[],count:0};if(await q(n))return{matches:[],count:0};const r=p.relative(o,n).replace(/\\/g,"/"),i=[];let c=0;const a=N(n,{encoding:"utf-8"}),l=U.createInterface({input:a,crlfDelay:1/0});try{let s=0;for await(const m of l)if(s++,t.regex.test(m)){if(c++,t.collectContent){let f=m;if(f.length>M&&(f=f.slice(0,M)+`... (line truncated to ${M} chars)`),i.push({file:r,line:s,content:f}),i.length>=t.maxResults)break}if(t.stopOnFirstMatch)break}}finally{l.close(),a.destroy()}return{matches:i,count:c}}const X=10*6e4,ht=X,wt=6e4,gt=10*6e4;function _t(n,o){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",description:"Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to write."},content:{type:"string",description:"Complete UTF-8 file content."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","content"],additionalProperties:!1},async execute(t){const e=P(t,["file_path","filePath"],"file_path"),r=P(t,["content"],"content"),i=E(t,["reason"]),c=await O(n,e),a=await W(c.targetPath),l=a?await u.readFile(c.targetPath,"utf-8").catch(()=>null):null,s=await j(n,e,r);if(o){const m=p.relative(s.workspaceRoot,s.targetPath).replace(/\\/g,"/");await o({filePath:m,operation:a?"UPDATE":"CREATE",beforeContent:l,afterContent:r,reason:i})}return{ok:!0,tool:"write_file",workspacePath:b(s.workspaceRoot),filePath:b(s.targetPath),bytes:s.bytes,createdParentDirectory:s.createdParentDirectory}}}}function yt(n,o){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",description:"Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to edit."},old_string:{type:"string",description:"Existing text to replace. Must occur exactly once."},new_string:{type:"string",description:"Replacement text."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path","old_string","new_string"],additionalProperties:!1},async execute(t){const e=P(t,["file_path","filePath"],"file_path"),r=P(t,["old_string","oldString"],"old_string"),i=P(t,["new_string","newString"],"new_string"),c=E(t,["reason"]);if(!r)throw new Error("old_string must not be empty");const a=await dt(n,e),l=ct(a.content,r);if(l===0)throw new Error("old_string was not found in file");if(l>1)throw new Error(`old_string occurs ${l} times; provide a unique match`);const s=a.content.replace(r,i),m=await j(n,e,s);if(o){const f=p.relative(m.workspaceRoot,m.targetPath).replace(/\\/g,"/");await o({filePath:f,operation:"UPDATE",beforeContent:a.content,afterContent:s,reason:c})}return{ok:!0,tool:"edit_file",workspacePath:b(m.workspaceRoot),filePath:b(m.targetPath),replacements:1,bytes:m.bytes}}}}function kt(n,o){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",description:"Delete a file inside the current workspace. Returns an error if the file does not exist.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to delete."},reason:{type:"string",description:"Optional reason for this change, e.g. what bug is being fixed. Helps humans understand the change later."}},required:["file_path"],additionalProperties:!1},async execute(t){const e=P(t,["file_path","filePath"],"file_path"),r=E(t,["reason"]),i=await O(n,e),c=S(i.workspaceRoot,await u.realpath(i.targetPath).catch(()=>i.targetPath)),a=await u.stat(c).catch(()=>null);if(!a)throw new Error(`File does not exist: ${c}`);if(!a.isFile())throw new Error(`Path is not a file: ${c}`);const l=await u.readFile(c,"utf-8").catch(()=>null);if(await u.rm(c,{force:!0}),o){const s=p.relative(i.workspaceRoot,c).replace(/\\/g,"/");await o({filePath:s,operation:"DELETE",beforeContent:l,afterContent:null,reason:r})}return{ok:!0,tool:"delete_file",workspacePath:b(i.workspaceRoot),filePath:b(c),deleted:!0}}}}function bt(n){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",description:"Read a UTF-8 text file inside the current workspace. Returns content with line numbers in the format '<line>: <content>'. Supports offset (1-indexed line number to start from) and limit (max lines to return, default 2000). Output is capped at 2000 lines or 50 KB, whichever is reached first. Individual lines longer than 2000 characters are truncated. Avoid tiny repeated slices (e.g. 30-line chunks); read a larger window if more context is needed.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read."},offset:{type:"number",description:"Line number to start reading from (1-indexed). Defaults to 1."},limit:{type:"number",description:"Maximum number of lines to return. Defaults to 2000."}},required:["file_path"],additionalProperties:!1},async execute(o){const t=P(o,["file_path","filePath"],"file_path"),e=C(o,["offset"],1),r=C(o,["limit"],Q),i=await pt(n,t,e,r);return{ok:!0,tool:"read_file",workspacePath:b(i.workspaceRoot),filePath:b(i.targetPath),content:i.content,isBinary:i.isBinary,truncated:i.truncated,totalLines:i.totalLines,shownFromLine:i.shownFromLine,shownToLine:i.shownToLine}}}}function Pt(n,o,t){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded timeout and returns stdout, stderr, and exit code. Set wait_seconds to 0 or n>0 to move long-running commands to the background and return a task_id immediately (or after n seconds); then use ListBackgroundCommands / ReadBackgroundOutput / KillBackgroundCommand to manage them.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Shell command to execute."},cwd:{type:"string",description:"Optional workspace-relative working directory. Defaults to the workspace root."},timeout_ms:{type:"number",description:"Optional timeout in milliseconds. Defaults to 600000 and is capped at 600000."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000. Only applies to foreground (wait_seconds=-1); ignored once a command moves to background."},wait_seconds:{type:"number",description:"How long to block waiting for the command to finish. -1 (default): block until completion (current behavior, capped at timeout_ms). 0: spawn and immediately move to background, return task_id. n>0: wait up to n seconds; if still running, move to background and return task_id. Background commands remain subject to timeout_ms (total upper bound); inactivity_timeout_ms is ignored once backgrounded.",default:-1,minimum:-1},description:{type:"string",description:"Optional short reason for running the command."}},required:["command"],additionalProperties:!1},async execute(e,r){const i=P(e,["command"],"command").trim();if(!i)throw new Error("command must not be empty");const c=C(e,["timeout_ms","timeoutMs"],ht),a=Math.min(c,X),l=C(e,["inactivity_timeout_ms","inactivityTimeoutMs"],wt),s=Math.min(l,gt),m=E(e,["cwd"]),f=await lt(n,m),g=e.wait_seconds,w=typeof g=="number"&&Number.isFinite(g)?g:-1;if(w!==-1&&t)return await xt({deps:t,command:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:a,waitSeconds:w,settleSignal:r?.settleSignal});const d=await K({commandText:i,agentId:o,workspaceRoot:f.workspaceRoot,cwd:f.cwd,timeoutMs:a,inactivityTimeoutMs:s,onOutput:r?.onOutput});return{ok:d.exitCode===0&&!d.timedOut,tool:"Bash",command:i,cwd:b(f.cwd),exitCode:d.exitCode,signal:d.signal,timedOut:d.timedOut,inactivityTimedOut:d.inactivityTimedOut,timeoutMs:a,inactivityTimeoutMs:s,durationMs:d.durationMs,outputSpilled:d.outputSpilled,outputFilePath:d.outputFilePath,outputLineCount:d.outputLineCount,commandFilePath:d.commandFilePath,commandLineCount:d.commandLineCount,stdout:d.stdout,stderr:d.stderr}}}}async function xt(n){const{deps:o,command:t,agentId:e,workspaceRoot:r,cwd:i,timeoutMs:c,waitSeconds:a,settleSignal:l}=n;try{const{taskId:s,completion:m}=o.manager.startCommand({commandText:t,agentId:e,workspaceRoot:r,cwd:i,timeoutMs:c,onCompleted:o.emitCompleted});if(a===0)return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use ListBackgroundCommands to view, ReadBackgroundOutput(task_id="${s}", mode="tail") to read output, KillBackgroundCommand(task_id="${s}") to stop.`};const f=Math.max(0,Math.floor(a*1e3));let g;const w=new Promise(_=>{g=setTimeout(()=>_("__timeout__"),f),g.unref?.()}),d=l?l.then(()=>"__settle__"):Promise.resolve("__never__");try{const _=await Promise.race([m,w,d]);if(_==="__settle__")return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"watchdog_timeout",waitedMs:f,message:`Command still running after watchdog timeout; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${s}", mode="tail") to inspect.`};if(_==="__timeout__")return{ok:!0,tool:"Bash",command:t,backgrounded:!0,taskId:s,status:"backgrounded",reason:"timeout",waitedMs:f,message:`Command still running after ${a}s; moved to background. Use ListBackgroundCommands / ReadBackgroundOutput(task_id="${s}", mode="tail") to inspect.`};const h=_;return{ok:h.exitCode===0&&!h.timedOut,tool:"Bash",command:t,backgrounded:!1,taskId:s,exitCode:h.exitCode,signal:h.signal,timedOut:h.timedOut,durationMs:h.durationMs,stdout:h.stdout,stderr:h.stderr}}finally{g&&clearTimeout(g)}}catch(s){if(s?.code==="too_many_tasks"||s?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:t,backgrounded:!1,error:s.code,message:s.message};throw s}}function vt(n){return{kind:"builtin",toolName:"grep",exposedName:"grep",description:"Search file contents using regular expressions within the workspace. Returns matched lines with file paths and line numbers. Supports include/exclude glob patterns, case-insensitive matching, and three output modes (content, files_with_matches, count). Searches recursively through directories, skipping binary files, hidden directories, and common build/dependency directories (node_modules, .git, dist, etc.).",inputSchema:{type:"object",properties:{pattern:{type:"string",description:"Regular expression pattern to search for in file contents."},path:{type:"string",description:"Workspace-relative file or directory path to search in. Defaults to the workspace root."},include:{type:"string",description:"Glob pattern to filter files by name (e.g. '*.ts', '*.{js,ts}'). Only matching files are searched."},exclude:{type:"string",description:"Glob pattern to exclude files by name (e.g. '*.test.ts'). Matching files are skipped."},case_insensitive:{type:"boolean",description:"Case-insensitive matching. Default false."},output_mode:{type:"string",enum:["content","files_with_matches","count"],description:"Output format: 'content' returns matched lines with line numbers (default), 'files_with_matches' returns only file paths with matches, 'count' returns match counts per file."},max_results:{type:"number",description:"Maximum number of matched lines to return (content mode). Default 1000."}},required:["pattern"],additionalProperties:!1},async execute(o){const t=P(o,["pattern"],"pattern"),e=E(o,["path"])||".",r=E(o,["include"]),i=E(o,["exclude"]),c=o.case_insensitive===!0,a=(()=>{const k=o.output_mode;return k==="files_with_matches"||k==="count"?k:"content"})(),l=C(o,["max_results"],at),s=c?"i":"";let m;try{m=new RegExp(t,s)}catch(k){throw new Error(`Invalid regex pattern: ${k instanceof Error?k.message:String(k)}`)}const f=r?G(r):null,g=i?G(i):null,w=await O(n,e),d=S(w.workspaceRoot,await u.realpath(w.targetPath)),_=await u.stat(d);let h;if(_.isFile())h=[d];else if(_.isDirectory())h=await ft(d,f,g,nt);else throw new Error(`Path is neither a file nor a directory: ${d}`);const y=[],x=[],B=[];let L=0,T=0,v=!1;for(const k of h){if(a==="content"&&y.length>=l){v=!0;break}const z={regex:m,maxResults:a==="content"?l-y.length:Number.MAX_SAFE_INTEGER,collectContent:a==="content",stopOnFirstMatch:a==="files_with_matches"},A=await mt(k,w.workspaceRoot,z);if(A.count>0){const I=p.relative(w.workspaceRoot,k).replace(/\\/g,"/");if(B.push(I),L+=A.count,a==="content"){for(const $ of A.matches){const F=Buffer.byteLength($.content,"utf-8")+64;if(T+F>rt){v=!0;break}T+=F,y.push($)}if(v)break;if(y.length>=l){v=!0;break}}else a==="count"&&x.push({file:I,count:A.count})}}const R={ok:!0,tool:"grep",pattern:t,searchPath:b(w.targetPath),outputMode:a,filesSearched:h.length,filesMatched:B.length,totalMatches:L,truncated:v};return a==="content"?R.matches=y:a==="files_with_matches"?R.matches=B:R.matches=x,R}}}function Ct(n,o,t,e){const r=o||"unknown",i=[bt(n),_t(n,t),yt(n,t),kt(n,t),vt(n),Pt(n,r,e),H(n)];return e&&i.push(V(r,e),Z(r,e),J(r,e)),i}export{Ct as createBuiltInFileTools,S as ensureInsideWorkspace,O as resolveWorkspaceTarget};
|
|
4
4
|
|
|
@@ -24,6 +24,8 @@ export declare class ACodeRuntime extends EventEmitter {
|
|
|
24
24
|
private state;
|
|
25
25
|
private abortController;
|
|
26
26
|
|
|
27
|
+
private exposedToolsInitialized;
|
|
28
|
+
|
|
27
29
|
private readonly pendingMemorySubAgents;
|
|
28
30
|
|
|
29
31
|
private readonly memoryDelegateCooldownUntil;
|
|
@@ -31,6 +33,9 @@ export declare class ACodeRuntime extends EventEmitter {
|
|
|
31
33
|
private activeTurn;
|
|
32
34
|
private abortRequested;
|
|
33
35
|
|
|
36
|
+
private toolSettleSlot;
|
|
37
|
+
private toolSettleSeq;
|
|
38
|
+
|
|
34
39
|
private pendingAwaitedSubIds;
|
|
35
40
|
|
|
36
41
|
private pendingAwaitedBgTaskIds;
|
|
@@ -96,6 +101,12 @@ export declare class ACodeRuntime extends EventEmitter {
|
|
|
96
101
|
|
|
97
102
|
private cancelPendingAwaitedBgTasks;
|
|
98
103
|
|
|
104
|
+
detachCurrentToolExecution(): boolean;
|
|
105
|
+
|
|
106
|
+
private beginToolSettle;
|
|
107
|
+
|
|
108
|
+
private clearToolSettle;
|
|
109
|
+
|
|
99
110
|
stop(): Promise<void>;
|
|
100
111
|
private resolveCommandDirs;
|
|
101
112
|
private runModelTurn;
|
|
@@ -138,6 +149,8 @@ export declare class ACodeRuntime extends EventEmitter {
|
|
|
138
149
|
|
|
139
150
|
private filterSharedMcpConnectionsForSubAgent;
|
|
140
151
|
private connectMcpServers;
|
|
152
|
+
|
|
153
|
+
private refreshMcpConnectionTools;
|
|
141
154
|
private createMcpTransport;
|
|
142
155
|
|
|
143
156
|
private fetchProfilePrompt;
|