aws-runtime-bridge 1.9.182 → 1.9.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts +30 -1
- package/dist/adapter/AcodeSdkAdapter.js +3 -3
- package/dist/adapter/ClaudeSdkAdapter.d.ts +3 -1
- package/dist/adapter/ClaudeSdkAdapter.js +3 -3
- package/dist/adapter/CodexSdkAdapter.d.ts +3 -1
- package/dist/adapter/CodexSdkAdapter.js +2 -2
- package/dist/adapter/OpencodeSdkAdapter.d.ts +3 -1
- package/dist/adapter/OpencodeSdkAdapter.js +3 -3
- package/dist/adapter/types.d.ts +7 -1
- package/dist/index.js +1 -1
- package/dist/routes/terminal.d.ts +13 -0
- package/dist/routes/terminal.js +28 -28
- package/dist/services/agent-ownership.d.ts +16 -0
- package/dist/services/agent-ownership.js +2 -2
- package/dist/services/plugin-deployment.d.ts +14 -0
- package/dist/services/plugin-deployment.js +2 -2
- package/dist/services/plugin-manager.d.ts +3 -0
- package/dist/services/plugin-manager.js +4 -4
- package/dist/services/plugin-runtime.d.ts +10 -2
- package/dist/services/user-scope.d.ts +1 -0
- package/dist/services/user-scope.js +1 -1
- package/package/acode/dist/background-command-manager.d.ts +22 -0
- package/package/acode/dist/background-command-manager.js +5 -5
- package/package/acode/dist/built-in-file-tools.js +2 -2
- package/package/acode/dist/hibernation-journal.d.ts +67 -0
- package/package/acode/dist/hibernation-journal.js +2 -0
- package/package/acode/dist/index.d.ts +1 -0
- package/package/acode/dist/index.js +1 -1
- package/package/acode/dist/runtime-environment.d.ts +4 -1
- package/package/acode/dist/runtime-environment.js +3 -3
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +7 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +4 -4
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +8 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +20 -20
- package/package/aws-client-agent-mcp/dist/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{existsSync as y,mkdirSync as $,readFileSync as S,readdirSync as N,rmSync as M,writeFileSync as R}from"node:fs";import{dirname as C,join as f}from"node:path";import{parseAcodeCommandMarkdown as j}from"@agentswork/acode";import{getBridgeTemplatePluginsDir as k}from"../config.js";import{sanitizeAgentId as P,downloadPluginZip as T,sanitizeArchiveFileName as x}from"../utils/file-utils.js";import{createLogger as z}from"../utils/logger.js";import{getAgentOwner as w}from"./agent-ownership.js";import{setDeployedDefaultsProvider as q,reconcileDefaultsFromDeployed as _}from"./capability-config.js";import{isCapabilityEnabled as V,getAgentPluginSources as H}from"./instance-features.js";import{PluginDeploymentStore as A}from"./plugin-deployment.js";import{PluginEventBuffer as J}from"./plugin-event-buffer.js";import{movePluginDir as L,rmWithRetry as I}from"./plugin-fs.js";import{executeHandler as K}from"./plugin-handler-adapter.js";import{ListenerRunner as W}from"./plugin-listener-runner.js";import{PluginLoader as U,toSummary as D}from"./plugin-loader.js";import{isRecord as F,isValidPluginName as Z,validateAgainstSchema as Q}from"./plugin-manifest.js";import{extractPluginZip as O}from"./plugin-package.js";const d=z("plugin-manager");import{isValidPluginName as De,validateManifest as ve,validateAgainstSchema as ke,renderCommandArgv as Pe}from"./plugin-manifest.js";import{getUserPluginsDir as E,isLegacyFallbackDisabled as B}from"./user-scope.js";class G{constructor(e={}){this.agents=new Map,this.deployStores=new Map,this.listeners=new W,this.loader=new U,this.templatePullInFlight=new Set,this.backgroundTemplatePulls=new Set,this.rootDir=e.rootDir,this.runtimeRootDir=e.runtimeRootDir}deployForScope(e){const t=this.rootDir??E(e),s=`scope:${t}`;let n=this.deployStores.get(s);return n||(n=new A(t,this.runtimeRootDir,this.loader,B()?void 0:k()),this.deployStores.set(s,n)),n}deployForAgent(e){if(this.rootDir){const r="__injected__";let i=this.deployStores.get(r);return i||(i=new A(this.rootDir,this.runtimeRootDir,this.loader),this.deployStores.set(r,i)),i}const t=w(e);if(t)return this.deployForScope(t);const s="__legacy__";let n=this.deployStores.get(s);return n||(n=new A(k(),this.runtimeRootDir,this.loader),this.deployStores.set(s,n)),n}get deploy(){const e="__legacy__";let t=this.deployStores.get(e);return t||(t=new A(this.rootDir??k(),this.runtimeRootDir,this.loader),this.deployStores.set(e,t)),t}dataRootForAgent(e){if(this.rootDir)return this.rootDir;const t=w(e);return t?E(t):k()}forAgent(e){return{listPlugins:()=>this.list(e),getPluginTools:t=>this.getPluginTools(e,t),invokeTool:(t,s,n,r)=>this.invokeTool(e,t,s,n,r),manage:t=>this.manage(e,t),getCommandDirs:()=>this.getCommandDirs(e)}}setOnPluginsChanged(e){this.onPluginsChanged=e}notifyPluginsChanged(e){try{this.onPluginsChanged?.(e)}catch(t){d.warn("[agent:%s] onPluginsChanged \u56DE\u8C03\u5931\u8D25: %s",e,t instanceof Error?t.message:String(t))}}async getCommandDirs(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.enabled&&s.commandDir).map(s=>s.commandDir)}async getPluginCommands(e){const t=await this.hydrate(e),s=[];for(const n of t.plugins.values())!n.enabled||!n.commandDir||s.push(...this.parsePluginCommandDir(n));return s}parsePluginCommandDir(e){return this.parseCommandDir(e.name,e.commandDir)}parseCommandDir(e,t){const s=[];if(!t)return s;try{for(const n of N(t)){if(!n.endsWith(".md"))continue;const r=f(t,n);try{const i=j(r,S(r,"utf8"));i.plugin=e,s.push(i)}catch(i){d.warn("[plugin:%s] \u547D\u4EE4\u89E3\u6790\u5931\u8D25 %s: %s",e,n,i instanceof Error?i.message:String(i))}}}catch{}return s}async list(e){const t=await this.hydrate(e),s=new Map;for(const r of t.plugins.values()){const i=D(r);s.set(i.name,i)}const n=this.deployForAgent(e);for(const r of n.readSharedManifest()){const i=r.pluginName;if(!n.deployedPluginHealthy(i))continue;const o=s.get(i);if(o){String(o.source||"").startsWith("shared:")&&(o.enabled=this.effectiveSharedEnabled(e,r));continue}s.set(i,{name:i,version:this.deployedPluginVersion(e,i),status:"enabled",enabled:this.effectiveSharedEnabled(e,r),source:`shared:${r.fileName??`${i}.zip`}`,installedAt:new Date().toISOString(),toolCount:0,listeners:[]})}return[...s.values()].sort((r,i)=>r.name.localeCompare(i.name))}deployedPluginVersion(e,t){try{const s=this.deployForAgent(e).resolveDeployedSourceDir(t);if(!s)return;const n=JSON.parse(S(f(s,"plugin.json"),"utf8"));return typeof n.version=="string"?n.version:void 0}catch{return}}async getPluginTools(e,t){const n=(await this.hydrate(e)).plugins.get(t);return!n||!n.enabled?[]:n.tools.map(r=>({plugin:n.name,name:r.name,title:r.title,description:r.description,inputSchema:r.inputSchema??{type:"object",properties:{}}}))}async debugMountPlugin(e,t,s={}){const n=String(t??"").trim();if(!n)return{ok:!1,error:"\u63D2\u4EF6\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A"};if(!y(n))return{ok:!1,error:`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728: ${n}`};const r=this.loader.loadManifestOnly(n);if(!r.ok)return r;const i=r.manifest.name,l=this.agentState(e),o=l.queue.then(async()=>{const a=await this.hydrate(e),u=a.plugins.get(i);u&&this.stopAllListeners(u);const g=this.loader.loadPluginFromDir(n,`debug:${n}`,new Date().toISOString(),s);if(!g.ok)return u&&this.startAllListeners(a,u),{ok:!1,error:`\u8C03\u8BD5\u6302\u8F7D\u5931\u8D25: ${g.error}`};const c=g.plugin;return c.debug=!0,a.plugins.set(c.name,c),this.startAllListeners(a,c),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u8C03\u8BD5\u6302\u8F7D: %s@%s\uFF08dir=%s\uFF09",a.agentId,c.name,c.version??"?",n),this.notifyPluginsChanged(a.agentId),{ok:!0,summary:D(c)}});return l.queue=o.catch(()=>{}),o}async listDebugPlugins(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.debug===!0).map(s=>D(s))}async debugPluginNames(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.debug===!0).map(s=>s.name)}async invokeTool(e,t,s,n,r){const i=await this.hydrate(e),l=i.plugins.get(t);if(!l)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5\uFF08\u53EF\u7528\u63D2\u4EF6: ${[...i.plugins.keys()].join(", ")||"\u65E0"}\uFF09`};if(!l.enabled)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u5DF2\u505C\u7528\uFF0C\u8BF7\u8BA9\u7528\u6237\u5728\u9762\u677F\u542F\u7528`};const o=l.tools.find(a=>a.name===s);if(!o)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u65E0\u5DE5\u5177 ${s}\uFF08\u53EF\u7528: ${l.tools.map(a=>a.name).join(", ")||"\u65E0"}\uFF09`};if(o.inputSchema){const a=Q(o.inputSchema,n);if(a.length>0)return{ok:!1,error:`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${a.join("; ")}`,inputSchema:o.inputSchema}}return K(e,l,o,n,r)}async manage(e,t){const s=this.agentState(e),n=s.queue.then(async()=>{const r=await this.hydrate(e);return this.doManage(r,t)});return s.queue=n.catch(()=>{}),n}async doManage(e,t){switch(t.action){case"list":return{ok:!0,plugins:await this.list(e.agentId)};case"install":return this.installPlugin(e,t);case"uninstall":return this.uninstallPlugin(e,t);case"reload":return this.reloadPlugin(e,t);case"enable":case"disable":return this.setEnabled(e,t,t.action==="enable");default:return{ok:!1,error:`\u672A\u77E5 action: ${String(t.action)}`}}}async installPlugin(e,t){const s=(t.source??"").trim();if(!s)return{ok:!1,error:"install \u9700\u8981 source\uFF08\u5F53\u524D\u652F\u6301 local:<\u63D2\u4EF6\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84>\uFF09"};const n=s.split(":",1)[0];if(n!=="local")return{ok:!1,error:`\u6682\u4E0D\u652F\u6301\u63D2\u4EF6\u6765\u6E90 "${n}:"\uFF08npm:/git: \u5728\u8DEF\u7EBF\u56FE\u4E2D\uFF1Bzip \u5305\u8BF7\u7528\u9762\u677F\u5BFC\u5165\u6216 POST /runtime/plugins/:agentId/upload\uFF09`};const r=s.slice(6).trim();return!r||!y(f(r,"plugin.json"))?{ok:!1,error:`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u7F3A\u5C11 plugin.json: ${r}`}:this.installFromDirectory(e,r,s,t.config)}async installFromZip(e,t,s,n){if(!t||t.length===0)return{ok:!1,error:"zip \u5305\u5185\u5BB9\u4E3A\u7A7A"};const r=this.agentState(e),i=r.queue.then(async()=>{await this.hydrate(e);const l=f(this.dataRootForAgent(e),"tmp",`upload-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{const o=await O(t,l);return y(f(o.pluginDir,"plugin.json"))?(d.info("[agent:%s] zip \u5BFC\u5165: %s\uFF08%d \u4E2A\u6587\u4EF6\uFF09",e,s,o.fileCount),await this.installFromDirectory(r,o.pluginDir,`upload:${s}`,n)):{ok:!1,error:"zip \u5185\u672A\u627E\u5230 plugin.json\uFF08\u8BF7\u6253\u5305\u63D2\u4EF6\u76EE\u5F55\uFF0C\u4E14 zip \u5185\u8DEF\u5F84\u4F7F\u7528\u82F1\u6587\uFF09"}}catch(o){const a=o instanceof Error?o.message:String(o);return d.warn("[agent:%s] zip \u5BFC\u5165\u5931\u8D25: %s",e,a),{ok:!1,error:`zip \u5BFC\u5165\u5931\u8D25: ${a}`}}finally{await I(l,3,100).catch(()=>{})}});return r.queue=i.catch(()=>{}),i}async installFromDirectory(e,t,s,n){const r=this.loader.loadManifestOnly(t);if(!r.ok)return r;const i=r.manifest,l=i.name,o=this.loader.validateUserConfigValues(i,n??{});if(o)return{ok:!1,error:o};const a=this.loader.loadPluginFromDir(t,s,new Date().toISOString(),n??{});if(!a.ok)return d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u6821\u9A8C\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,a.error),a;const u=f(this.dataRootForAgent(e.agentId),"cache",P(e.agentId),l),g=t===u,c=e.plugins.get(l);if(c&&this.stopAllListeners(c),!g)try{await I(u),await L(t,u)}catch(h){const b=h instanceof Error?h.message:String(h);return d.warn("[agent:%s] \u63D2\u4EF6\u79FB\u52A8\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,b),c&&this.startAllListeners(e,c),{ok:!1,error:`\u63D2\u4EF6\u79FB\u52A8\u5931\u8D25: ${b}`}}const m=this.loader.loadPluginFromDir(u,s,new Date().toISOString(),n??{});if(!m.ok){if(d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,m.error),!g)try{await L(u,t)}catch(h){d.warn("[agent:%s] \u88C5\u8F7D\u5931\u8D25\u540E\u65E0\u6CD5\u79FB\u56DE\u6E90\u76EE\u5F55\uFF08%s\uFF09\uFF0C\u5DF2\u4FDD\u7559\u7F13\u5B58\u526F\u672C",e.agentId,h instanceof Error?h.message:String(h))}if(c)try{this.startAllListeners(e,c)}catch(h){d.warn("[agent:%s] \u6062\u590D\u65E7\u5B9E\u4F8B\u76D1\u542C\u5931\u8D25: %s",e.agentId,h instanceof Error?h.message:String(h))}return m}const p=m.plugin;return e.plugins.set(p.name,p),this.persistInstalled(e),this.startAllListeners(e,p),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u5B89\u88C5: %s@%s",e.agentId,p.name,p.version??"?"),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(p)}}async uninstallPlugin(e,t){const s=(t.name??"").trim(),n=e.plugins.get(s);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${s} \u672A\u5B89\u88C5`};this.stopAllListeners(n),e.plugins.delete(s),n.tools.length>0&&this.pushDeprecatedNotice(e,s,"\u5DF2\u88AB\u5378\u8F7D");const r=this.deployForAgent(e.agentId);return r.readSharedManifest().some(l=>l.pluginName===s)&&r.removeRuntimeLink(e.agentId,s),this.persistInstalled(e),n.debug!==!0&&await I(n.dir),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u5378\u8F7D: %s",e.agentId,s),this.notifyPluginsChanged(e.agentId),{ok:!0}}async reloadPlugin(e,t){const s=(t.name??"").trim(),n=e.plugins.get(s);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${s} \u672A\u5B89\u88C5`};const r=this.loader.loadPluginFromDir(n.dir,n.source,n.installedAt,n.configValues);if(!r.ok)return{ok:!1,error:`\u91CD\u8F7D\u5931\u8D25\uFF08\u4FDD\u7559\u65E7\u7248\u672C\u8FD0\u884C\uFF09: ${r.error}`};const i=r.plugin;return i.debug=n.debug,this.stopAllListeners(n),e.plugins.set(s,i),this.persistInstalled(e),this.startAllListeners(e,i),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u91CD\u8F7D: %s",e.agentId,s),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(i)}}async setEnabled(e,t,s){const n=(t.name??"").trim(),r=e.plugins.get(n);return r?(r.enabled=s,s?this.startAllListeners(e,r):(r.tools.length>0&&this.pushDeprecatedNotice(e,n,"\u5DF2\u505C\u7528"),this.stopAllListeners(r)),this.persistInstalled(e),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(r)}):{ok:!1,error:`\u63D2\u4EF6 ${n} \u672A\u5B89\u88C5`}}async saveSharedPlugin(e,t,s=!0,n,r){if(!e||e.length===0)return{ok:!1,error:"zip \u5305\u5185\u5BB9\u4E3A\u7A7A"};const i=r?this.deployForScope(r):this.deploy;await i.migrateLegacySharedIfNeeded();const l=f(r?E(r):this.rootDir??k(),"tmp",`deploy-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{const o=await O(e,l),a=this.loader.loadManifestOnly(o.pluginDir);if(!a.ok)return a;const u=a.manifest.name,g=i.readSharedManifest().find(p=>p.pluginName===u);if(g&&g.sha256===n&&i.deployedPluginHealthy(u))return{ok:!0,pluginName:u};await this.suspendDeployedPluginAcrossAgents(u,r),i.replaceDeployedDir(u,o.pluginDir),await i.updateSharedManifest(p=>{const h=p.filter(b=>b.pluginName!==u);return h.push({pluginName:u,zipFile:g?.zipFile??`${u}.zip`,fileName:t,defaultEnabled:s,sha256:n}),h}),d.info("[shared] \u63D2\u4EF6\u5DF2\u90E8\u7F72\u5230 bridge: %s\uFF08\u6765\u6E90 %s%s\uFF09",u,t,n?`, sha256=${n.slice(0,12)}\u2026`:"");const m=(await this.applySharedToKnownAgents(r)).find(p=>p.installed.includes(u));return m&&d.info("[shared] \u5DF2\u540C\u6B65\u5230\u8FD0\u884C\u4E2D agent: %s",m.agentId),{ok:!0,pluginName:u}}catch(o){const a=o instanceof Error?o.message:String(o);return d.warn("[shared] \u63D2\u4EF6\u90E8\u7F72\u5931\u8D25\uFF08%s\uFF09: %s",t,a),{ok:!1,error:`\u63D2\u4EF6\u90E8\u7F72\u5931\u8D25: ${a}`}}finally{await I(l,3,100).catch(()=>{})}}async suspendDeployedPluginAcrossAgents(e,t){for(const s of[...this.agents.values()]){if(!s.hydrated)continue;if(t){const r=w(s.agentId);if(r&&(r.tenantId!==t.tenantId||r.userId!==t.userId))continue}const n=s.queue.then(async()=>{const r=s.plugins.get(e);r&&(this.stopAllListeners(r),s.plugins.delete(e),d.info("[agent:%s] \u5DF2\u6458\u9664\u63D2\u4EF6\u7B49\u5F85\u90E8\u7F72\u533A\u66F4\u65B0: %s",s.agentId,e))});s.queue=n.catch(()=>{}),await n}}async applySharedToKnownAgents(e){const t=[];for(const s of this.agents.values()){if(!s.hydrated)continue;if(e){const r=w(s.agentId);if(r&&(r.tenantId!==e.tenantId||r.userId!==e.userId))continue}const n=await this.syncSharedIntoStateQueued(s);n.length>0&&t.push({agentId:s.agentId,installed:n})}return t}async syncSharedIntoStateQueued(e){const t=e.queue.then(()=>this.syncSharedIntoState(e));return e.queue=t.catch(()=>[]),t}effectiveSharedEnabled(e,t){return V(e,"plugins",t.pluginName)&&(t.defaultEnabled??!0)}async pullTemplatePluginsFromCapabilities(e){const t=H(e.agentId);if(!t)return;const s=this.deployForAgent(e.agentId),n=s.readSharedManifest();for(const[r,i]of Object.entries(t)){if(!i.url)continue;const l=n.find(a=>a.pluginName===r);if(!l||!l.sha256||l.sha256===i.sha256&&s.deployedPluginHealthy(r)||this.templatePullInFlight.has(r))continue;this.templatePullInFlight.add(r);const o=f(this.dataRootForAgent(e.agentId),"tmp",`pull-${P(e.agentId)}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{$(o,{recursive:!0});let a=`${r}.zip`;try{const m=decodeURIComponent(i.url.split("/").pop()||"");m.toLowerCase().endsWith(".zip")&&(a=x(m,`${r}.zip`))}catch{}const u=await T({downloadUrl:i.url,fileName:a,sha256:i.sha256},o);if(!u)continue;const g=S(u.outputPath),c=await this.saveSharedPlugin(g,u.fileName,l.defaultEnabled??!0,i.sha256,w(e.agentId)??void 0);c.ok?d.info("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u81EA\u52A8\u5BF9\u8D26\u66F4\u65B0\u6210\u529F: %s",e.agentId,c.pluginName??r):d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: %s: %s",e.agentId,r,c.error)}catch(a){d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6 %s \u66F4\u65B0\u5931\u8D25: %s",e.agentId,r,a instanceof Error?a.message:String(a))}finally{this.templatePullInFlight.delete(r),await I(o,3,100).catch(()=>{})}}}queueTemplatePull(e){this.backgroundTemplatePulls.has(e.agentId)||(this.backgroundTemplatePulls.add(e.agentId),this.pullTemplatePluginsFromCapabilities(e).catch(t=>{d.warn("[agent:%s] \u540E\u53F0\u63D2\u4EF6\u5BF9\u8D26\u66F4\u65B0\u5931\u8D25: %s",e.agentId,t instanceof Error?t.message:String(t))}).finally(()=>{this.backgroundTemplatePulls.delete(e.agentId)}))}async syncSharedIntoState(e){this.queueTemplatePull(e);const t=this.deployForAgent(e.agentId),s=t.readSharedManifest(),n=new Set,r=[];for(const i of s)if(n.add(i.pluginName),this.effectiveSharedEnabled(e.agentId,i)){const o=e.plugins.get(i.pluginName);if(!t.ensureRuntimeLink(e.agentId,i.pluginName))continue;const a=t.runtimeLinkPath(e.agentId,i.pluginName);if(!o){const u=this.loader.loadPluginFromDir(a,`shared:${i.fileName}`,new Date().toISOString(),{});u.ok?(e.plugins.set(u.plugin.name,u.plugin),r.push(u.plugin.name),d.info("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u5DF2\u6309\u5F00\u542F\u6001\u52A0\u8F7D: %s",e.agentId,i.pluginName)):d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25: %s: %s",e.agentId,i.pluginName,u.error)}}else{const o=e.plugins.get(i.pluginName);o&&(this.stopAllListeners(o),e.plugins.delete(i.pluginName)),t.removeRuntimeLink(e.agentId,i.pluginName)}return this.cleanupRuntimeDir(e,n),this.persistInstalled(e),r}cleanupRuntimeDir(e,t){const s=this.deployForAgent(e.agentId).runtimeDirFor(e.agentId);try{if(!y(s))return;const n=new Set(t),r=this.deployForAgent(e.agentId);for(const i of N(s))!n.has(i)&&!e.plugins.has(i)&&r.removeRuntimeLink(e.agentId,i)}catch{}}async applyCapabilities(e){const t=await this.hydrate(e);await this.pullTemplatePluginsFromCapabilities(t),await this.syncSharedIntoState(t)}async drainEvents(e,t={}){const s=await this.hydrate(e);this.ensureListenersRunning(s);const n=s.buffer.drain(t);return{revision:n.revision,events:n.events}}pushDeprecatedNotice(e,t,s){e.buffer.push({plugin:t,event:"plugin-deprecated",priority:"urgent",receivedAt:Date.now(),message:{msgId:`plugin-deprecated-${t}-${Date.now()}`,channel:"plugin-event",senderId:`plugin:${t}`,senderName:`\u63D2\u4EF6[${t}]`,receiverId:e.agentId||void 0,content:`[plugin:${t}] plugin-deprecated
|
|
2
|
-
\u3010\u7CFB\u7EDF\u63D0\u793A\u3011\u63D2\u4EF6 ${t} ${s}\uFF0C\u5176\u63D0\u4F9B\u7684\u5DE5\u5177\u4E0D\u518D\u53EF\u8C03\u7528\uFF08\u5176 plugin:${t} \u6536\u7EB3\u7EC4\u5DF2\u5931\u6548\uFF09\u3002\u5982\u4ECD\u9700\u4F7F\u7528\u8BF7\u91CD\u65B0\u5B89\u88C5/\u542F\u7528\u8BE5\u63D2\u4EF6\u3002`,requireReply:!1,role:"system",timestamp:new Date().toISOString()}})}async enqueueExternalEvent(e,t,s){const n=await this.hydrate(e),r=n.plugins.get(t);if(!r||!r.enabled)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5\u6216\u5DF2\u505C\u7528`};const i=typeof s.event=="string"?s.event:"",l=r.listeners.find(o=>o.transport.type==="http-callback"&&(!i||o.event===i));return l?(this.enqueueEvent(n,r,l,s),{ok:!0}):{ok:!1,error:`\u63D2\u4EF6 ${t} \u65E0\u5339\u914D\u7684 http-callback \u76D1\u542C\u5668\uFF08event=${i||"(\u7A7A)"}\uFF09`}}agentState(e){let t=this.agents.get(e);return t||(t={agentId:e,plugins:new Map,buffer:new J(e),hydrated:!1,queue:Promise.resolve(),eventSeq:0},this.agents.set(e,t)),t}async hydrate(e){const t=this.agentState(e);return t.hydratePromise||(t.hydratePromise=this.doHydrate(t).catch(s=>{throw t.hydratePromise=void 0,s})),await t.hydratePromise,t}async doHydrate(e){const t=e.agentId;await this.deployForAgent(t).migrateLegacySharedIfNeeded();const s=this.installedFile(t);if(!y(s)){await this.syncSharedIntoState(e).catch(()=>{}),e.hydrated=!0;return}try{const n=JSON.parse(S(s,"utf8")),r=this.readSecrets(t);for(const i of n.plugins??[]){if(String(i.source||"").startsWith("shared:"))continue;const o=f(this.dataRootForAgent(t),"cache",P(t),i.name);if(!y(f(o,"plugin.json"))){d.warn("[agent:%s] \u5DF2\u5B89\u88C5\u63D2\u4EF6\u76EE\u5F55\u7F3A\u5931\uFF0C\u8DF3\u8FC7: %s",t,i.name);continue}const a={...i.config,...r[i.name]??{}},u=this.loader.loadPluginFromDir(o,i.source,i.installedAt,a);if(u.ok){const g=u.plugin;g.enabled=i.enabled,e.plugins.set(g.name,g)}else d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u5931\u8D25: %s: %s",t,i.name,u.error)}}catch(n){const r=n instanceof Error?n.message:String(n);d.error("[agent:%s] installed.json \u89E3\u6790\u5931\u8D25: %s",t,r)}await this.syncSharedIntoState(e).catch(n=>{d.warn("[agent:%s] \u5171\u4EAB\u63D2\u4EF6\u540C\u6B65\u5931\u8D25: %s",t,n instanceof Error?n.message:String(n))}),e.hydrated=!0}installedFile(e){return f(this.dataRootForAgent(e),"agents",P(e),"installed.json")}persistInstalled(e){this.persistSecrets(e);const t=this.installedFile(e.agentId);$(
|
|
3
|
-
`,"utf8")}sensitiveKeysOf(e){return(e.userConfig??[]).filter(t=>t?.sensitive===!0&&typeof t.key=="string").map(t=>t.key)}stripSensitiveValues(e){const t=new Set(this.sensitiveKeysOf(e)),s={};for(const[n,r]of Object.entries(e.configValues))t.has(n)||(s[n]=r);return s}secretsFile(e){return f(this.dataRootForAgent(e),"agents",P(e),"secrets.json")}readSecrets(e){try{const t=JSON.parse(S(this.secretsFile(e),"utf8"));if(!F(t)||!F(t.secrets))return{};const s={};for(const[n,r]of Object.entries(t.secrets)){if(!F(r))continue;const i={};for(const[l,o]of Object.entries(r))typeof o=="string"&&(i[l]=o);s[n]=i}return s}catch{return{}}}persistSecrets(e){const t={};for(const n of e.plugins.values()){if(n.debug===!0)continue;const r={};for(const i of this.sensitiveKeysOf(n)){const l=n.configValues[i];typeof l=="string"&&l.length>0&&(r[i]=l)}Object.keys(r).length>0&&(t[n.name]=r)}const s=this.secretsFile(e.agentId);if(Object.keys(t).length===0){try{
|
|
4
|
-
`,{encoding:"utf8",mode:384})}ensureListenersRunning(e){this.listeners.ensureRunning(e)}startAllListeners(e,t){this.listeners.startAll(e,t)}stopAllListeners(e){this.listeners.stopAll(e)}enqueueEvent(e,t,s,n){this.listeners.enqueue(e,t,s,n)}async getLogs(e,t){const n=(await this.hydrate(e)).plugins.get(t);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5`,logs:[]};const r=[];for(const i of n.runtimes)r.push(`\u2500\u2500 listener ${i.def.event} (${i.def.transport?.type}) ${i.lastError?`[error: ${i.lastError}]`:""}`),r.push(...i.logs);return{ok:!0,logs:r}}async stopAgent(e){const t=this.agents.get(e);if(t){for(const s of t.plugins.values())this.stopAllListeners(s);this.agents.delete(e)}}async stopAll(){for(const e of[...this.agents.keys()])await this.stopAgent(e)}async listDeployed(e){const t=e?this.deployForScope(e):this.deploy;await t.migrateLegacySharedIfNeeded();const s=t.readSharedManifest(),n=[];for(const r of s){const i=t.deployedPluginDir(r.pluginName),l=t.deployedPluginHealthy(r.pluginName);let o;if(l)try{const a=JSON.parse(S(f(i,"plugin.json"),"utf8"));typeof a.version=="string"&&(o=a.version)}catch{}n.push({pluginName:r.pluginName,version:o,fileName:r.fileName??`${r.pluginName}.zip`,defaultEnabled:r.defaultEnabled!==!1,sha256:r.sha256,deployed:l})}return n}deployedDefaultEnabled(e){const s=(e?this.deployForScope(e):this.deploy).readSharedManifest(),n={};for(const r of s)n[r.pluginName]=r.defaultEnabled!==!1;return n}async removeDeployedPlugin(e,t){if(!Z(e))return{ok:!1,error:"pluginName \u975E\u6CD5\uFF08\u987B\u4E3A kebab-case\uFF0C1~64 \u5B57\u7B26\uFF09"};const s=t?this.deployForScope(t):this.deploy;await s.migrateLegacySharedIfNeeded();for(const n of[...this.agents.values()]){if(!n.hydrated)continue;if(t){const i=w(n.agentId);if(i&&(i.tenantId!==t.tenantId||i.userId!==t.userId))continue}const r=n.queue.then(async()=>{const i=n.plugins.get(e);i&&(this.stopAllListeners(i),n.plugins.delete(e)),s.removeRuntimeLink(n.agentId,e),this.persistInstalled(n)});n.queue=r.catch(()=>{}),await r}await s.updateSharedManifest(n=>n.filter(
|
|
1
|
+
import{existsSync as y,mkdirSync as $,readFileSync as S,readdirSync as E,rmSync as O,writeFileSync as N}from"node:fs";import{dirname as L,join as f}from"node:path";import{parseAcodeCommandMarkdown as j}from"@agentswork/acode";import{getBridgeTemplatePluginsDir as k}from"../config.js";import{sanitizeAgentId as P,downloadPluginZip as T,sanitizeArchiveFileName as x}from"../utils/file-utils.js";import{createLogger as z}from"../utils/logger.js";import{getAgentOwner as w}from"./agent-ownership.js";import{setDeployedDefaultsProvider as q,reconcileDefaultsFromDeployed as _}from"./capability-config.js";import{isCapabilityEnabled as V,getAgentPluginSources as H}from"./instance-features.js";import{PluginDeploymentStore as A}from"./plugin-deployment.js";import{PluginEventBuffer as J}from"./plugin-event-buffer.js";import{movePluginDir as C,rmWithRetry as I}from"./plugin-fs.js";import{executeHandler as K}from"./plugin-handler-adapter.js";import{ListenerRunner as W}from"./plugin-listener-runner.js";import{PluginLoader as U,toSummary as D}from"./plugin-loader.js";import{isRecord as F,isValidPluginName as Z,validateAgainstSchema as Q}from"./plugin-manifest.js";import{extractPluginZip as M}from"./plugin-package.js";const d=z("plugin-manager");import{isValidPluginName as De,validateManifest as ve,validateAgainstSchema as ke,renderCommandArgv as Pe}from"./plugin-manifest.js";import{getUserPluginsDir as R,isLegacyFallbackDisabled as B}from"./user-scope.js";class G{constructor(e={}){this.agents=new Map,this.deployStores=new Map,this.listeners=new W,this.loader=new U,this.templatePullInFlight=new Set,this.backgroundTemplatePulls=new Set,this.rootDir=e.rootDir,this.runtimeRootDir=e.runtimeRootDir,this.legacyRootDir=e.legacyRootDir}deployForScope(e){const t=this.rootDir??R(e),s=`scope:${t}`;let n=this.deployStores.get(s);return n||(n=new A(t,this.runtimeRootDir,this.loader,this.legacyRootDir??(B()?void 0:k())),this.deployStores.set(s,n)),n}deployForAgent(e){if(this.rootDir){const r="__injected__";let i=this.deployStores.get(r);return i||(i=new A(this.rootDir,this.runtimeRootDir,this.loader,this.legacyRootDir),this.deployStores.set(r,i)),i}const t=w(e);if(t)return this.deployForScope(t);const s="__legacy__";let n=this.deployStores.get(s);return n||(n=new A(k(),this.runtimeRootDir,this.loader),this.deployStores.set(s,n)),n}get deploy(){const e="__legacy__";let t=this.deployStores.get(e);return t||(t=new A(this.rootDir??k(),this.runtimeRootDir,this.loader),this.deployStores.set(e,t)),t}dataRootForAgent(e){if(this.rootDir)return this.rootDir;const t=w(e);return t?R(t):k()}forAgent(e){return{listPlugins:()=>this.list(e),getPluginTools:t=>this.getPluginTools(e,t),invokeTool:(t,s,n,r)=>this.invokeTool(e,t,s,n,r),manage:t=>this.manage(e,t),getCommandDirs:()=>this.getCommandDirs(e)}}setOnPluginsChanged(e){this.onPluginsChanged=e}notifyPluginsChanged(e){try{this.onPluginsChanged?.(e)}catch(t){d.warn("[agent:%s] onPluginsChanged \u56DE\u8C03\u5931\u8D25: %s",e,t instanceof Error?t.message:String(t))}}async getCommandDirs(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.enabled&&s.commandDir).map(s=>s.commandDir)}async getPluginCommands(e){const t=await this.hydrate(e),s=[];for(const n of t.plugins.values())!n.enabled||!n.commandDir||s.push(...this.parsePluginCommandDir(n));return s}parsePluginCommandDir(e){return this.parseCommandDir(e.name,e.commandDir)}parseCommandDir(e,t){const s=[];if(!t)return s;try{for(const n of E(t)){if(!n.endsWith(".md"))continue;const r=f(t,n);try{const i=j(r,S(r,"utf8"));i.plugin=e,s.push(i)}catch(i){d.warn("[plugin:%s] \u547D\u4EE4\u89E3\u6790\u5931\u8D25 %s: %s",e,n,i instanceof Error?i.message:String(i))}}}catch{}return s}async list(e){const t=await this.hydrate(e),s=new Map;for(const r of t.plugins.values()){const i=D(r);s.set(i.name,i)}const n=this.deployForAgent(e);for(const r of n.readSharedManifest()){const i=r.pluginName;if(!n.deployedPluginHealthy(i))continue;const o=s.get(i);if(o){String(o.source||"").startsWith("shared:")&&(o.enabled=this.effectiveSharedEnabled(e,r));continue}s.set(i,{name:i,version:this.deployedPluginVersion(e,i),status:"enabled",enabled:this.effectiveSharedEnabled(e,r),source:`shared:${r.fileName??`${i}.zip`}`,installedAt:new Date().toISOString(),toolCount:0,listeners:[]})}return[...s.values()].sort((r,i)=>r.name.localeCompare(i.name))}deployedPluginVersion(e,t){try{const s=this.deployForAgent(e).resolveDeployedSourceDir(t);if(!s)return;const n=JSON.parse(S(f(s,"plugin.json"),"utf8"));return typeof n.version=="string"?n.version:void 0}catch{return}}async getPluginTools(e,t){const n=(await this.hydrate(e)).plugins.get(t);return!n||!n.enabled?[]:n.tools.map(r=>({plugin:n.name,name:r.name,title:r.title,description:r.description,inputSchema:r.inputSchema??{type:"object",properties:{}}}))}async debugMountPlugin(e,t,s={}){const n=String(t??"").trim();if(!n)return{ok:!1,error:"\u63D2\u4EF6\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A"};if(!y(n))return{ok:!1,error:`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728: ${n}`};const r=this.loader.loadManifestOnly(n);if(!r.ok)return r;const i=r.manifest.name,l=this.agentState(e),o=l.queue.then(async()=>{const a=await this.hydrate(e),u=a.plugins.get(i);u&&this.stopAllListeners(u);const g=this.loader.loadPluginFromDir(n,`debug:${n}`,new Date().toISOString(),s);if(!g.ok)return u&&this.startAllListeners(a,u),{ok:!1,error:`\u8C03\u8BD5\u6302\u8F7D\u5931\u8D25: ${g.error}`};const c=g.plugin;return c.debug=!0,a.plugins.set(c.name,c),this.startAllListeners(a,c),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u8C03\u8BD5\u6302\u8F7D: %s@%s\uFF08dir=%s\uFF09",a.agentId,c.name,c.version??"?",n),this.notifyPluginsChanged(a.agentId),{ok:!0,summary:D(c)}});return l.queue=o.catch(()=>{}),o}async listDebugPlugins(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.debug===!0).map(s=>D(s))}async debugPluginNames(e){return[...(await this.hydrate(e)).plugins.values()].filter(s=>s.debug===!0).map(s=>s.name)}async invokeTool(e,t,s,n,r){const i=await this.hydrate(e),l=i.plugins.get(t);if(!l)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5\uFF08\u53EF\u7528\u63D2\u4EF6: ${[...i.plugins.keys()].join(", ")||"\u65E0"}\uFF09`};if(!l.enabled)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u5DF2\u505C\u7528\uFF0C\u8BF7\u8BA9\u7528\u6237\u5728\u9762\u677F\u542F\u7528`};const o=l.tools.find(a=>a.name===s);if(!o)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u65E0\u5DE5\u5177 ${s}\uFF08\u53EF\u7528: ${l.tools.map(a=>a.name).join(", ")||"\u65E0"}\uFF09`};if(o.inputSchema){const a=Q(o.inputSchema,n);if(a.length>0)return{ok:!1,error:`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${a.join("; ")}`,inputSchema:o.inputSchema}}return K(e,l,o,n,r)}async manage(e,t){const s=this.agentState(e),n=s.queue.then(async()=>{const r=await this.hydrate(e);return this.doManage(r,t)});return s.queue=n.catch(()=>{}),n}async doManage(e,t){switch(t.action){case"list":return{ok:!0,plugins:await this.list(e.agentId)};case"install":return this.installPlugin(e,t);case"uninstall":return this.uninstallPlugin(e,t);case"reload":return this.reloadPlugin(e,t);case"enable":case"disable":return this.setEnabled(e,t,t.action==="enable");default:return{ok:!1,error:`\u672A\u77E5 action: ${String(t.action)}`}}}async installPlugin(e,t){const s=(t.source??"").trim();if(!s)return{ok:!1,error:"install \u9700\u8981 source\uFF08\u5F53\u524D\u652F\u6301 local:<\u63D2\u4EF6\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84>\uFF09"};const n=s.split(":",1)[0];if(n!=="local")return{ok:!1,error:`\u6682\u4E0D\u652F\u6301\u63D2\u4EF6\u6765\u6E90 "${n}:"\uFF08npm:/git: \u5728\u8DEF\u7EBF\u56FE\u4E2D\uFF1Bzip \u5305\u8BF7\u7528\u9762\u677F\u5BFC\u5165\u6216 POST /runtime/plugins/:agentId/upload\uFF09`};const r=s.slice(6).trim();return!r||!y(f(r,"plugin.json"))?{ok:!1,error:`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u7F3A\u5C11 plugin.json: ${r}`}:this.installFromDirectory(e,r,s,t.config)}async installFromZip(e,t,s,n){if(!t||t.length===0)return{ok:!1,error:"zip \u5305\u5185\u5BB9\u4E3A\u7A7A"};const r=this.agentState(e),i=r.queue.then(async()=>{await this.hydrate(e);const l=f(this.dataRootForAgent(e),"tmp",`upload-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{const o=await M(t,l);return y(f(o.pluginDir,"plugin.json"))?(d.info("[agent:%s] zip \u5BFC\u5165: %s\uFF08%d \u4E2A\u6587\u4EF6\uFF09",e,s,o.fileCount),await this.installFromDirectory(r,o.pluginDir,`upload:${s}`,n)):{ok:!1,error:"zip \u5185\u672A\u627E\u5230 plugin.json\uFF08\u8BF7\u6253\u5305\u63D2\u4EF6\u76EE\u5F55\uFF0C\u4E14 zip \u5185\u8DEF\u5F84\u4F7F\u7528\u82F1\u6587\uFF09"}}catch(o){const a=o instanceof Error?o.message:String(o);return d.warn("[agent:%s] zip \u5BFC\u5165\u5931\u8D25: %s",e,a),{ok:!1,error:`zip \u5BFC\u5165\u5931\u8D25: ${a}`}}finally{await I(l,3,100).catch(()=>{})}});return r.queue=i.catch(()=>{}),i}async installFromDirectory(e,t,s,n){const r=this.loader.loadManifestOnly(t);if(!r.ok)return r;const i=r.manifest,l=i.name,o=this.loader.validateUserConfigValues(i,n??{});if(o)return{ok:!1,error:o};const a=this.loader.loadPluginFromDir(t,s,new Date().toISOString(),n??{});if(!a.ok)return d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u6821\u9A8C\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,a.error),a;const u=f(this.dataRootForAgent(e.agentId),"cache",P(e.agentId),l),g=t===u,c=e.plugins.get(l);if(c&&this.stopAllListeners(c),!g)try{await I(u),await C(t,u)}catch(h){const b=h instanceof Error?h.message:String(h);return d.warn("[agent:%s] \u63D2\u4EF6\u79FB\u52A8\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,b),c&&this.startAllListeners(e,c),{ok:!1,error:`\u63D2\u4EF6\u79FB\u52A8\u5931\u8D25: ${b}`}}const m=this.loader.loadPluginFromDir(u,s,new Date().toISOString(),n??{});if(!m.ok){if(d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u5931\u8D25\uFF08%s\uFF09: %s",e.agentId,s,m.error),!g)try{await C(u,t)}catch(h){d.warn("[agent:%s] \u88C5\u8F7D\u5931\u8D25\u540E\u65E0\u6CD5\u79FB\u56DE\u6E90\u76EE\u5F55\uFF08%s\uFF09\uFF0C\u5DF2\u4FDD\u7559\u7F13\u5B58\u526F\u672C",e.agentId,h instanceof Error?h.message:String(h))}if(c)try{this.startAllListeners(e,c)}catch(h){d.warn("[agent:%s] \u6062\u590D\u65E7\u5B9E\u4F8B\u76D1\u542C\u5931\u8D25: %s",e.agentId,h instanceof Error?h.message:String(h))}return m}const p=m.plugin;return e.plugins.set(p.name,p),this.persistInstalled(e),this.startAllListeners(e,p),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u5B89\u88C5: %s@%s",e.agentId,p.name,p.version??"?"),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(p)}}async uninstallPlugin(e,t){const s=(t.name??"").trim(),n=e.plugins.get(s);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${s} \u672A\u5B89\u88C5`};this.stopAllListeners(n),e.plugins.delete(s),n.tools.length>0&&this.pushDeprecatedNotice(e,s,"\u5DF2\u88AB\u5378\u8F7D");const r=this.deployForAgent(e.agentId);return r.readSharedManifest().some(l=>l.pluginName===s)&&r.removeRuntimeLink(e.agentId,s),this.persistInstalled(e),n.debug!==!0&&await I(n.dir),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u5378\u8F7D: %s",e.agentId,s),this.notifyPluginsChanged(e.agentId),{ok:!0}}async reloadPlugin(e,t){const s=(t.name??"").trim(),n=e.plugins.get(s);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${s} \u672A\u5B89\u88C5`};const r=this.loader.loadPluginFromDir(n.dir,n.source,n.installedAt,n.configValues);if(!r.ok)return{ok:!1,error:`\u91CD\u8F7D\u5931\u8D25\uFF08\u4FDD\u7559\u65E7\u7248\u672C\u8FD0\u884C\uFF09: ${r.error}`};const i=r.plugin;return i.debug=n.debug,this.stopAllListeners(n),e.plugins.set(s,i),this.persistInstalled(e),this.startAllListeners(e,i),d.info("[agent:%s] \u63D2\u4EF6\u5DF2\u91CD\u8F7D: %s",e.agentId,s),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(i)}}async setEnabled(e,t,s){const n=(t.name??"").trim(),r=e.plugins.get(n);return r?(r.enabled=s,s?this.startAllListeners(e,r):(r.tools.length>0&&this.pushDeprecatedNotice(e,n,"\u5DF2\u505C\u7528"),this.stopAllListeners(r)),this.persistInstalled(e),this.notifyPluginsChanged(e.agentId),{ok:!0,summary:D(r)}):{ok:!1,error:`\u63D2\u4EF6 ${n} \u672A\u5B89\u88C5`}}async saveSharedPlugin(e,t,s=!0,n,r){if(!e||e.length===0)return{ok:!1,error:"zip \u5305\u5185\u5BB9\u4E3A\u7A7A"};const i=r?this.deployForScope(r):this.deploy;await i.migrateLegacySharedIfNeeded();const l=f(r?R(r):this.rootDir??k(),"tmp",`deploy-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{const o=await M(e,l),a=this.loader.loadManifestOnly(o.pluginDir);if(!a.ok)return a;const u=a.manifest.name,g=i.readSharedManifest().find(p=>p.pluginName===u);if(g&&g.sha256===n&&i.deployedPluginHealthy(u))return{ok:!0,pluginName:u};await this.suspendDeployedPluginAcrossAgents(u,r),i.replaceDeployedDir(u,o.pluginDir),await i.updateSharedManifest(p=>{const h=p.filter(b=>b.pluginName!==u);return h.push({pluginName:u,zipFile:g?.zipFile??`${u}.zip`,fileName:t,defaultEnabled:s,sha256:n}),h}),d.info("[shared] \u63D2\u4EF6\u5DF2\u90E8\u7F72\u5230 bridge: %s\uFF08\u6765\u6E90 %s%s\uFF09",u,t,n?`, sha256=${n.slice(0,12)}\u2026`:"");const m=(await this.applySharedToKnownAgents(r)).find(p=>p.installed.includes(u));return m&&d.info("[shared] \u5DF2\u540C\u6B65\u5230\u8FD0\u884C\u4E2D agent: %s",m.agentId),{ok:!0,pluginName:u}}catch(o){const a=o instanceof Error?o.message:String(o);return d.warn("[shared] \u63D2\u4EF6\u90E8\u7F72\u5931\u8D25\uFF08%s\uFF09: %s",t,a),{ok:!1,error:`\u63D2\u4EF6\u90E8\u7F72\u5931\u8D25: ${a}`}}finally{await I(l,3,100).catch(()=>{})}}async suspendDeployedPluginAcrossAgents(e,t){for(const s of[...this.agents.values()]){if(!s.hydrated)continue;if(t){const r=w(s.agentId);if(r&&(r.tenantId!==t.tenantId||r.userId!==t.userId))continue}const n=s.queue.then(async()=>{const r=s.plugins.get(e);r&&(this.stopAllListeners(r),s.plugins.delete(e),d.info("[agent:%s] \u5DF2\u6458\u9664\u63D2\u4EF6\u7B49\u5F85\u90E8\u7F72\u533A\u66F4\u65B0: %s",s.agentId,e))});s.queue=n.catch(()=>{}),await n}}async applySharedToKnownAgents(e){const t=[];for(const s of this.agents.values()){if(!s.hydrated)continue;if(e){const r=w(s.agentId);if(r&&(r.tenantId!==e.tenantId||r.userId!==e.userId))continue}const n=await this.syncSharedIntoStateQueued(s);n.length>0&&t.push({agentId:s.agentId,installed:n})}return t}async syncSharedIntoStateQueued(e){const t=e.queue.then(()=>this.syncSharedIntoState(e));return e.queue=t.catch(()=>[]),t}effectiveSharedEnabled(e,t){return V(e,"plugins",t.pluginName)&&(t.defaultEnabled??!0)}async pullTemplatePluginsFromCapabilities(e){const t=H(e.agentId);if(!t)return;const s=this.deployForAgent(e.agentId),n=s.readSharedManifest();for(const[r,i]of Object.entries(t)){if(!i.url)continue;const l=n.find(a=>a.pluginName===r);if(!l||!l.sha256||l.sha256===i.sha256&&s.deployedPluginHealthy(r)||this.templatePullInFlight.has(r))continue;this.templatePullInFlight.add(r);const o=f(this.dataRootForAgent(e.agentId),"tmp",`pull-${P(e.agentId)}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);try{$(o,{recursive:!0});let a=`${r}.zip`;try{const m=decodeURIComponent(i.url.split("/").pop()||"");m.toLowerCase().endsWith(".zip")&&(a=x(m,`${r}.zip`))}catch{}const u=await T({downloadUrl:i.url,fileName:a,sha256:i.sha256},o);if(!u)continue;const g=S(u.outputPath),c=await this.saveSharedPlugin(g,u.fileName,l.defaultEnabled??!0,i.sha256,w(e.agentId)??void 0);c.ok?d.info("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u81EA\u52A8\u5BF9\u8D26\u66F4\u65B0\u6210\u529F: %s",e.agentId,c.pluginName??r):d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: %s: %s",e.agentId,r,c.error)}catch(a){d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6 %s \u66F4\u65B0\u5931\u8D25: %s",e.agentId,r,a instanceof Error?a.message:String(a))}finally{this.templatePullInFlight.delete(r),await I(o,3,100).catch(()=>{})}}}queueTemplatePull(e){this.backgroundTemplatePulls.has(e.agentId)||(this.backgroundTemplatePulls.add(e.agentId),this.pullTemplatePluginsFromCapabilities(e).catch(t=>{d.warn("[agent:%s] \u540E\u53F0\u63D2\u4EF6\u5BF9\u8D26\u66F4\u65B0\u5931\u8D25: %s",e.agentId,t instanceof Error?t.message:String(t))}).finally(()=>{this.backgroundTemplatePulls.delete(e.agentId)}))}async syncSharedIntoState(e){this.queueTemplatePull(e);const t=this.deployForAgent(e.agentId),s=t.readSharedManifest(),n=new Set,r=[];for(const i of s)if(n.add(i.pluginName),this.effectiveSharedEnabled(e.agentId,i)){const o=e.plugins.get(i.pluginName);if(!t.ensureRuntimeLink(e.agentId,i.pluginName))continue;const a=t.runtimeLinkPath(e.agentId,i.pluginName);if(!o){const u=this.loader.loadPluginFromDir(a,`shared:${i.fileName??`${i.pluginName}.zip`}`,new Date().toISOString(),{});u.ok?(e.plugins.set(u.plugin.name,u.plugin),r.push(u.plugin.name),d.info("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u5DF2\u6309\u5F00\u542F\u6001\u52A0\u8F7D: %s",e.agentId,i.pluginName)):d.warn("[agent:%s] \u5DF2\u90E8\u7F72\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25: %s: %s",e.agentId,i.pluginName,u.error)}}else{const o=e.plugins.get(i.pluginName);o&&(this.stopAllListeners(o),e.plugins.delete(i.pluginName)),t.removeRuntimeLink(e.agentId,i.pluginName)}return this.cleanupRuntimeDir(e,n),this.persistInstalled(e),r}cleanupRuntimeDir(e,t){const s=this.deployForAgent(e.agentId).runtimeDirFor(e.agentId);try{if(!y(s))return;const n=new Set(t),r=this.deployForAgent(e.agentId);for(const i of E(s))!n.has(i)&&!e.plugins.has(i)&&r.removeRuntimeLink(e.agentId,i)}catch{}}async applyCapabilities(e){const t=await this.hydrate(e);await this.pullTemplatePluginsFromCapabilities(t),await this.syncSharedIntoState(t)}async drainEvents(e,t={}){const s=await this.hydrate(e);this.ensureListenersRunning(s);const n=s.buffer.drain(t);return{revision:n.revision,events:n.events}}pushDeprecatedNotice(e,t,s){e.buffer.push({plugin:t,event:"plugin-deprecated",priority:"urgent",receivedAt:Date.now(),message:{msgId:`plugin-deprecated-${t}-${Date.now()}`,channel:"plugin-event",senderId:`plugin:${t}`,senderName:`\u63D2\u4EF6[${t}]`,receiverId:e.agentId||void 0,content:`[plugin:${t}] plugin-deprecated
|
|
2
|
+
\u3010\u7CFB\u7EDF\u63D0\u793A\u3011\u63D2\u4EF6 ${t} ${s}\uFF0C\u5176\u63D0\u4F9B\u7684\u5DE5\u5177\u4E0D\u518D\u53EF\u8C03\u7528\uFF08\u5176 plugin:${t} \u6536\u7EB3\u7EC4\u5DF2\u5931\u6548\uFF09\u3002\u5982\u4ECD\u9700\u4F7F\u7528\u8BF7\u91CD\u65B0\u5B89\u88C5/\u542F\u7528\u8BE5\u63D2\u4EF6\u3002`,requireReply:!1,role:"system",timestamp:new Date().toISOString()}})}async enqueueExternalEvent(e,t,s){const n=await this.hydrate(e),r=n.plugins.get(t);if(!r||!r.enabled)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5\u6216\u5DF2\u505C\u7528`};const i=typeof s.event=="string"?s.event:"",l=r.listeners.find(o=>o.transport.type==="http-callback"&&(!i||o.event===i));return l?(this.enqueueEvent(n,r,l,s),{ok:!0}):{ok:!1,error:`\u63D2\u4EF6 ${t} \u65E0\u5339\u914D\u7684 http-callback \u76D1\u542C\u5668\uFF08event=${i||"(\u7A7A)"}\uFF09`}}agentState(e){let t=this.agents.get(e);return t||(t={agentId:e,plugins:new Map,buffer:new J(e),hydrated:!1,queue:Promise.resolve(),eventSeq:0},this.agents.set(e,t)),t}async hydrate(e){const t=this.agentState(e);return t.hydratePromise||(t.hydratePromise=this.doHydrate(t).catch(s=>{throw t.hydratePromise=void 0,s})),await t.hydratePromise,t}async doHydrate(e){const t=e.agentId;await this.deployForAgent(t).migrateLegacySharedIfNeeded();const s=this.installedFile(t);if(!y(s)){await this.syncSharedIntoState(e).catch(()=>{}),e.hydrated=!0;return}try{const n=JSON.parse(S(s,"utf8")),r=this.readSecrets(t);for(const i of n.plugins??[]){if(String(i.source||"").startsWith("shared:"))continue;const o=f(this.dataRootForAgent(t),"cache",P(t),i.name);if(!y(f(o,"plugin.json"))){d.warn("[agent:%s] \u5DF2\u5B89\u88C5\u63D2\u4EF6\u76EE\u5F55\u7F3A\u5931\uFF0C\u8DF3\u8FC7: %s",t,i.name);continue}const a={...i.config,...r[i.name]??{}},u=this.loader.loadPluginFromDir(o,i.source,i.installedAt,a);if(u.ok){const g=u.plugin;g.enabled=i.enabled,e.plugins.set(g.name,g)}else d.warn("[agent:%s] \u63D2\u4EF6\u88C5\u8F7D\u5931\u8D25: %s: %s",t,i.name,u.error)}}catch(n){const r=n instanceof Error?n.message:String(n);d.error("[agent:%s] installed.json \u89E3\u6790\u5931\u8D25: %s",t,r)}await this.syncSharedIntoState(e).catch(n=>{d.warn("[agent:%s] \u5171\u4EAB\u63D2\u4EF6\u540C\u6B65\u5931\u8D25: %s",t,n instanceof Error?n.message:String(n))}),e.hydrated=!0}installedFile(e){return f(this.dataRootForAgent(e),"agents",P(e),"installed.json")}persistInstalled(e){this.persistSecrets(e);const t=this.installedFile(e.agentId);$(L(t),{recursive:!0});const s=[...e.plugins.values()].filter(n=>n.debug!==!0).map(n=>({name:n.name,source:n.source,installedAt:n.installedAt,enabled:n.enabled,config:this.stripSensitiveValues(n)}));N(t,`${JSON.stringify({version:1,plugins:s},null,2)}
|
|
3
|
+
`,"utf8")}sensitiveKeysOf(e){return(e.userConfig??[]).filter(t=>t?.sensitive===!0&&typeof t.key=="string").map(t=>t.key)}stripSensitiveValues(e){const t=new Set(this.sensitiveKeysOf(e)),s={};for(const[n,r]of Object.entries(e.configValues))t.has(n)||(s[n]=r);return s}secretsFile(e){return f(this.dataRootForAgent(e),"agents",P(e),"secrets.json")}readSecrets(e){try{const t=JSON.parse(S(this.secretsFile(e),"utf8"));if(!F(t)||!F(t.secrets))return{};const s={};for(const[n,r]of Object.entries(t.secrets)){if(!F(r))continue;const i={};for(const[l,o]of Object.entries(r))typeof o=="string"&&(i[l]=o);s[n]=i}return s}catch{return{}}}persistSecrets(e){const t={};for(const n of e.plugins.values()){if(n.debug===!0)continue;const r={};for(const i of this.sensitiveKeysOf(n)){const l=n.configValues[i];typeof l=="string"&&l.length>0&&(r[i]=l)}Object.keys(r).length>0&&(t[n.name]=r)}const s=this.secretsFile(e.agentId);if(Object.keys(t).length===0){try{O(s,{force:!0})}catch{}return}$(L(s),{recursive:!0}),N(s,`${JSON.stringify({version:1,secrets:t},null,2)}
|
|
4
|
+
`,{encoding:"utf8",mode:384})}ensureListenersRunning(e){this.listeners.ensureRunning(e)}startAllListeners(e,t){this.listeners.startAll(e,t)}stopAllListeners(e){this.listeners.stopAll(e)}enqueueEvent(e,t,s,n){this.listeners.enqueue(e,t,s,n)}async getLogs(e,t){const n=(await this.hydrate(e)).plugins.get(t);if(!n)return{ok:!1,error:`\u63D2\u4EF6 ${t} \u672A\u5B89\u88C5`,logs:[]};const r=[];for(const i of n.runtimes)r.push(`\u2500\u2500 listener ${i.def.event} (${i.def.transport?.type}) ${i.lastError?`[error: ${i.lastError}]`:""}`),r.push(...i.logs);return{ok:!0,logs:r}}async stopAgent(e){const t=this.agents.get(e);if(t){for(const s of t.plugins.values())this.stopAllListeners(s);this.agents.delete(e)}}async stopAll(){for(const e of[...this.agents.keys()])await this.stopAgent(e)}async listDeployed(e){const t=e?this.deployForScope(e):this.deploy;await t.migrateLegacySharedIfNeeded();const s=t.readSharedManifest(),n=[];for(const r of s){const i=t.deployedPluginDir(r.pluginName),l=t.deployedPluginHealthy(r.pluginName);let o;if(l)try{const a=JSON.parse(S(f(i,"plugin.json"),"utf8"));typeof a.version=="string"&&(o=a.version)}catch{}n.push({pluginName:r.pluginName,version:o,fileName:r.fileName??`${r.pluginName}.zip`,defaultEnabled:r.defaultEnabled!==!1,sha256:r.sha256,deployed:l})}return n}deployedDefaultEnabled(e){const s=(e?this.deployForScope(e):this.deploy).readSharedManifest(),n={};for(const r of s)n[r.pluginName]=r.defaultEnabled!==!1;return n}async removeDeployedPlugin(e,t){if(!Z(e))return{ok:!1,error:"pluginName \u975E\u6CD5\uFF08\u987B\u4E3A kebab-case\uFF0C1~64 \u5B57\u7B26\uFF09"};const s=t?this.deployForScope(t):this.deploy;await s.migrateLegacySharedIfNeeded();for(const n of[...this.agents.values()]){if(!n.hydrated)continue;if(t){const i=w(n.agentId);if(i&&(i.tenantId!==t.tenantId||i.userId!==t.userId))continue}const r=n.queue.then(async()=>{const i=n.plugins.get(e);i&&(this.stopAllListeners(i),n.plugins.delete(e)),s.removeRuntimeLink(n.agentId,e),this.persistInstalled(n)});n.queue=r.catch(()=>{}),await r}await s.updateSharedManifest(n=>{const r=n.filter(i=>i.pluginName!==e);return s.hasLegacyManifestEntry(e)&&r.push({pluginName:e,removed:!0}),r});try{s.removeDeployedDir(e)}catch(n){return{ok:!1,error:`\u90E8\u7F72\u76EE\u5F55\u6E05\u7406\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`}}return d.info("[shared] \u63D2\u4EF6\u5DF2\u4ECE bridge \u79FB\u9664\u90E8\u7F72: %s",e),{ok:!0}}}const X=new G;q("plugins",v=>v?X.deployedDefaultEnabled(v):{});const ye=v=>_(v,"plugins");export{G as PluginManager,De as isValidPluginName,X as pluginManager,ye as reconcilePluginsDefaultsFromDeployed,Pe as renderCommandArgv,ke as validateAgainstSchema,ve as validateManifest};
|
|
5
5
|
|
|
@@ -73,10 +73,18 @@ export interface AgentState {
|
|
|
73
73
|
|
|
74
74
|
export interface SharedPluginEntry {
|
|
75
75
|
pluginName: string;
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
|
|
77
|
+
zipFile?: string;
|
|
78
|
+
|
|
79
|
+
fileName?: string;
|
|
78
80
|
|
|
79
81
|
defaultEnabled?: boolean;
|
|
80
82
|
|
|
81
83
|
sha256?: string;
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
removed?: boolean;
|
|
82
90
|
}
|
|
@@ -123,4 +123,5 @@ export declare function requireUserScope(req: Request, res: Response, next: Next
|
|
|
123
123
|
export declare function getUserScopeOrThrow(req: Request): UserScope;
|
|
124
124
|
|
|
125
125
|
export declare function setAgentOwnershipChecker(checker: (agentId: string, scope: UserScope) => boolean): void;
|
|
126
|
+
export declare function setMachineOwnershipSelfHeal(selfHeal: (agentId: string, scope: UserScope) => boolean): void;
|
|
126
127
|
export declare function requireAgentOwnership(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import o from"node:path";import{getBridgeDataHome as x}from"../config.js";import{loadRuntimeBinding as m}from"./runtime-binding.js";const y=/^[a-zA-Z0-9_.-]+$/,p=Object.freeze({tenantId:"_legacy",userId:"_legacy"}),U="x-tenant-id",D="x-user-id";function i(e){const r=String(e??"").replace(/[^a-zA-Z0-9_.-]/g,"_");return/^\.+$/.test(r)?"_":r.replace(/^\.+/g,"")}function b(e){return typeof e=="string"&&e.length>0&&y.test(e)}function a(e,r){const t=String(e??"").trim(),n=String(r??"").trim();return!t||!n?null:{tenantId:t,userId:n}}function _(e,r){return!e||!r?!1:i(e.tenantId)===i(r.tenantId)&&i(e.userId)===i(r.userId)}function L(e){return _(e,p)}function f(){return String(process.env.AWS_BRIDGE_LEGACY_SCOPE||"").trim().toLowerCase()==="1"}function O(){return String(process.env.AWS_BRIDGE_DISABLE_LEGACY_FALLBACK||"").trim().toLowerCase()==="1"}function j(e){return[i(e.tenantId),i(e.userId)]}function s(e){return o.join(x(),"users",...j(e))}function R(e){return o.join(s(e),"ai","acode.json")}function A(e){return o.join(s(e),"skills")}function P(e){return o.join(A(e),"deployed")}function d(e){return o.join(s(e),"mcp")}function B(e){return o.join(d(e),"deployed")}function F(e){return o.join(d(e),"packages")}function E(e){return o.join(s(e),"plugins")}function H(e){return o.join(E(e),"deployed")}function g(e,r){return o.join(s(e),"capability",r)}function T(e,r){return o.join(g(e,r),"config.json")}function z(e,r,t){return o.join(g(e,r),"agents",`${i(t)}.json`)}function h(...e){for(const r of e){const t=String(r??"").trim();if(t)return t}return""}function u(e){const r=a(e.header(U),e.header(D));if(r)return r;const t=e.body??{};return a(t.tenantId,h(t.ownerUserId,t.userId))}function S(){try{const e=m();return e?.userId?a("_local",e.userId):null}catch{return null}}function G(e,r,t){const n=u(e)??S();n?e.userScope=n:f()&&(e.userScope={...p}),t()}function v(e,r,t){if(e.userScope){t();return}const n=u(e)??S();if(n){e.userScope=n,t();return}if(f()){e.userScope={...p},t();return}r.status(400).json({ok:!1,error:"user-scope-required",message:"\u8BF7\u6C42\u7F3A\u5C11 X-Tenant-Id / X-User-Id\uFF08\u6216\u4F53 tenantId + ownerUserId\uFF09\u8EAB\u4EFD"})}function M(e){const r=e.userScope??u(e);if(!r)throw new Error("user-scope-required: \u8BF7\u6C42\u7F3A\u5C11 (tenantId, userId) \u8EAB\u4EFD");return r}let l=null,I=null;function N(e){l=e}function Y(e){I=e}function $(e,r,t){const n=String(e.params?.agentId??e.body?.agentId??"").trim();if(!n){t();return}const c=e.userScope??u(e);if(!c){r.status(400).json({ok:!1,error:"user-scope-required",message:"agent \u7EA7\u64CD\u4F5C\u7F3A\u5C11 (tenantId, userId) \u8EAB\u4EFD"});return}if(l&&!l(n,c)){if(I?.(n,c)){t();return}r.status(403).json({ok:!1,error:"agent-ownership-mismatch",message:"\u8BE5\u5B9E\u4F8B\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237"});return}t()}export{p as LEGACY_SCOPE,y as SCOPE_ID_PATTERN,U as TENANT_HEADER,D as USER_HEADER,u as extractUserScope,R as getUserAiConfigFile,z as getUserCapabilityAgentFile,T as getUserCapabilityDefaultsFile,g as getUserCapabilityDir,B as getUserMcpDeployedDir,d as getUserMcpDir,F as getUserMcpPackagesDir,H as getUserPluginsDeployedDir,E as getUserPluginsDir,M as getUserScopeOrThrow,s as getUserScopeRoot,P as getUserSkillsDeployedDir,A as getUserSkillsDir,O as isLegacyFallbackDisabled,L as isLegacyScope,f as isLegacyScopeAllowed,b as isValidScopeId,a as normalizeUserScope,$ as requireAgentOwnership,v as requireUserScope,G as resolveUserScope,i as sanitizeScopeId,_ as scopeEquals,N as setAgentOwnershipChecker,Y as setMachineOwnershipSelfHeal};
|
|
2
2
|
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
|
|
@@ -223,7 +225,20 @@ export declare class BackgroundCommandManager {
|
|
|
223
225
|
private appendToFile;
|
|
224
226
|
private finalizeTask;
|
|
225
227
|
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
226
232
|
private evictTask;
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
taskCapacity(agentId: string): {
|
|
238
|
+
limit: number;
|
|
239
|
+
used: number;
|
|
240
|
+
remaining: number;
|
|
241
|
+
};
|
|
227
242
|
listTasks(agentId: string, statusFilter?: string): TaskSummary[];
|
|
228
243
|
private toSummary;
|
|
229
244
|
|
|
@@ -275,6 +290,13 @@ export declare class BackgroundCommandManager {
|
|
|
275
290
|
|
|
276
291
|
|
|
277
292
|
interruptAwaits(agentId: string, taskIds: Iterable<string>): void;
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
278
300
|
killTask(agentId: string, taskId: string): Promise<KillResult>;
|
|
279
301
|
|
|
280
302
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`),this.finalizeTask(u,-1,null,"spawn_error")}),this.persistTaskSafe(u),{taskId:n,completion:l}}async spawnAndAttach(t,i){const e=G(),n=await D(i.commandText,i.workspaceRoot,e)
|
|
3
|
-
`),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),
|
|
1
|
+
import{spawn as I,spawnSync as R}from"node:child_process";import{promises as S}from"node:fs";import{createInterface as O}from"node:readline";import{randomBytes as C}from"node:crypto";import{prepareShellCommand as D,shellExecutable as G}from"./builtins/shellCommandPreparation.js";import{createShellOutputDecoder as b,decodeShellOutputChunk as A}from"./builtins/shellOutputDecoder.js";import{BackgroundOutputRingBuffer as z}from"./background-output-ringbuffer.js";import{BackgroundCommandStore as F}from"./background-command-store.js";const N=1e3,k=3*60*1e3,W=24*60*60*1e3,X=5*1e3,$=64*1024,q=2e3,_=50*1024*1024,B=20,P=256,U=256*1024,Y=100,K=5e3,H=256,v=20,x=5*60*1e3,L=1024*1024,M=`pid-${process.pid}-${Date.now().toString(36)}`;class V{constructor(){this.tasksByAgent=new Map,this.store=new F}globalCount(){let t=0;for(const i of this.tasksByAgent.values())t+=i.size;return t}startCommand(t){const i=t.agentId,e=this.getOrCreateBucket(i);if(this.globalCount()>=P)throw new E("global_limit_reached",`Global background task limit ${P} reached. Stop or wait for existing background commands to finish, then retry.`);if(e.size>=B)throw new E("too_many_tasks",`Background command limit exceeded: ${e.size}/${B} slots in use. Stop existing background commands (kill_background_command) to free slots, then retry.`);const n=this.generateTaskId(e),r=t.timeoutMs===void 0?k:t.timeoutMs>0?Math.min(t.timeoutMs,W):0,o=typeof t.inactivityTimeoutMs=="number"&&Number.isFinite(t.inactivityTimeoutMs)&&t.inactivityTimeoutMs>0?t.inactivityTimeoutMs:0,s=this.store.outputPathFor(i,n),a=t.commandText.length>80?`${t.commandText.slice(0,77)}...`:t.commandText,u={taskId:n,agentId:i,command:t.commandText,commandPreview:a,description:t.description,cwd:t.cwd,status:"running",startedAt:Date.now(),finishedAt:null,exitCode:null,signal:null,killedReason:null,timeoutMs:r,inactivityTimeoutMs:o,child:null,ringBuffer:new z,outputPath:s,outputWriteChain:Promise.resolve(),outputTruncated:!1,outputLineCount:0,outputBytes:0,notified:!1,completionWaiters:[],onCompleted:t.onCompleted,onOutput:t.onOutput,outputSeq:0,outputSubscribers:new Set,timeoutTimer:null,killEscalationTimer:null,retentionTimer:null,inactivityTimer:null,exitGraceTimer:null,inactivityTimedOut:!1,finalized:!1,stdoutDecoder:b(),stderrDecoder:b(),stdoutText:"",stderrText:"",commandLineCount:0,bootId:M,lastPulseAt:Date.now(),lastPulseBytes:0,backgrounded:!1},l=new Promise(c=>{u.completionResolve=c});return e.set(n,u),r>0&&(u.timeoutTimer=setTimeout(()=>{u.finalized||(u.killedReason="timeout",u.child?this.killProcessGroup(u.child):this.finalizeTask(u,null,null,"timeout"))},r),u.timeoutTimer.unref?.()),this.spawnAndAttach(u,t).catch(c=>{u.ringBuffer.push(`[ACode] spawn failed: ${c?.message??String(c)}
|
|
2
|
+
`),this.finalizeTask(u,-1,null,"spawn_error")}),this.persistTaskSafe(u),{taskId:n,completion:l}}async spawnAndAttach(t,i){const e=G(),n=await D(i.commandText,i.workspaceRoot,e);if(t.finalized)return;const r=process.platform==="win32",o=I(e.command,[...e.argsPrefix,n.commandText],{cwd:i.cwd,env:process.env,detached:!r,stdio:["ignore","pipe","pipe"],windowsVerbatimArguments:r,windowsHide:!0});t.child=o,t.commandFilePath=n.commandFilePath,t.commandLineCount=n.commandLineCount,t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),o.stdout?.on("data",s=>{const a=A(t.stdoutDecoder,s);this.onOutput(t,"stdout",a)}),o.stderr?.on("data",s=>{const a=A(t.stderrDecoder,s);this.onOutput(t,"stderr",a)}),o.on("error",s=>{this.onOutput(t,"stderr",`[ACode] child error: ${s.message}
|
|
3
|
+
`),t.finalized||this.finalizeTask(t,-1,null,"child_error")}),o.on("exit",(s,a)=>{if(t.finalized)return;const u=()=>{if(t.finalized||!t.child)return;const l=A(t.stdoutDecoder,void 0,!0),c=A(t.stderrDecoder,void 0,!0);l&&this.onOutput(t,"stdout",l),c&&this.onOutput(t,"stderr",c),t.ringBuffer.flush();const p=t.killedReason??null;this.finalizeTask(t,s,a,p??void 0)};t.exitGraceTimer=setTimeout(u,q),t.exitGraceTimer.unref?.()}),o.on("close",(s,a)=>{t.exitGraceTimer&&(clearTimeout(t.exitGraceTimer),t.exitGraceTimer=null);const u=A(t.stdoutDecoder,void 0,!0),l=A(t.stderrDecoder,void 0,!0);if(u&&this.onOutput(t,"stdout",u),l&&this.onOutput(t,"stderr",l),t.ringBuffer.flush(),!t.finalized){const c=t.killedReason??null;this.finalizeTask(t,s,a,c??void 0)}})}disableInactivityTimeout(t,i){const e=this.getTask(t,i);return e?(e.inactivityTimeoutMs=0,e.inactivityTimer&&(clearTimeout(e.inactivityTimer),e.inactivityTimer=null),!0):!1}async markBackgrounded(t,i){const e=this.getTask(t,i);return e?(e.backgrounded=!0,await this.persistTask(e),!0):!1}onOutput(t,i,e){if(!e)return;t.outputSeq++;const n=t.outputSeq;if(t.outputSubscribers.size>0)for(const s of t.outputSubscribers)try{s.onLine({stream:i,text:e,seq:n})}catch{}const r=i==="stdout"?"stdoutText":"stderrText";if(Buffer.byteLength(t[r],"utf-8")<L){const s=L-Buffer.byteLength(t[r],"utf-8");t[r]+=s>0?e.slice(0,s):""}if(t.inactivityTimeoutMs>0&&this.scheduleInactivity(t),t.status==="running"){const s=Date.now(),a=t.outputBytes-t.lastPulseBytes;(s-t.lastPulseAt>=X||a>=$)&&(t.lastPulseAt=s,t.lastPulseBytes=t.outputBytes,this.persistTaskSafe(t))}if(t.outputBytes>=_){if(!t.outputTruncated){t.outputTruncated=!0;const s=`
|
|
4
4
|
[ACode] output truncated at 50MB
|
|
5
|
-
`;t.ringBuffer.push(
|
|
6
|
-
`)}),t.outputWriteChain)}finalizeTask(t,i,e,n){if(t.finalized)return;t.finalized=!0,t.timeoutTimer&&(clearTimeout(t.timeoutTimer),t.timeoutTimer=null),t.killEscalationTimer&&(clearTimeout(t.killEscalationTimer),t.killEscalationTimer=null),t.inactivityTimer&&(clearTimeout(t.inactivityTimer),t.inactivityTimer=null),t.exitGraceTimer&&(clearTimeout(t.exitGraceTimer),t.exitGraceTimer=null),t.child=null,t.finishedAt=Date.now();let r;n?(r="killed",t.killedReason=n):i===0?r="completed":r="failed",t.status=r,t.exitCode=i,t.signal=e;const s=n==="inactivity"||t.inactivityTimedOut,o={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||s,inactivityTimedOut:s,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(o);const a={taskId:t.taskId,status:r,exitCode:i,signal:e,timedOut:n==="timeout"||s,aborted:!1,durationMs:t.finishedAt-t.startedAt},u=t.completionWaiters;t.completionWaiters=[];for(const l of u)l(a);if(this.persistTaskSafe(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:r,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const l=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const c of l)try{c.onDone({status:r,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t.agentId,t.taskId)},B),t.retentionTimer.unref?.()}evictTask(t,i){const e=this.tasksByAgent.get(t);if(!e)return;const n=e.get(i);n&&(S.unlink(n.outputPath).catch(()=>{}),e.delete(i),e.size===0&&this.tasksByAgent.delete(t))}listTasks(t,i){const e=this.tasksByAgent.get(t);if(!e)return[];const n=[];for(const r of e.values())i&&i!=="all"&&r.status!==i||n.push(this.toSummary(r));return n.sort((r,s)=>s.startedAt-r.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const r=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:r,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let r=!0;return()=>{r&&(r=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),r=i.mode==="lines"||i.mode==="grep"?i.mode:"tail",s=Math.min(Math.max(1,i.line_size??100),U),o=Number.isFinite(i.start_line)&&(i.start_line??0)>0?Math.floor(i.start_line??0):0;if(!n)return{taskId:e,status:"killed",mode:r,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,outputTruncated:!1,hasMore:!1,nextStartLine:null,outputFile:""}};let a=[],u=!1,l=null;if(r==="tail"){const m=n.ringBuffer.tail(s),f=Math.max(0,n.outputLineCount-m.length);a=m.map((d,y)=>({line:f+y,text:d})),l=null}else if(r==="lines"){const m=n.ringBuffer.firstLineIndex(),f=m+n.ringBuffer.getLineCount();if(o>=m&&o<f)a=n.ringBuffer.offset(o,s).map((y,T)=>({line:o+T,text:y})),u=o+a.length<f,l=u?o+a.length:null;else if(o<f){const d=await this.readFromFile(n,"lines",o,s,0,0,void 0);a=d.rows,u=d.hasMore,l=d.nextStartLine}}else{const m=String(i.pattern??"");if(!m||m.length>H)return this._emptyResult(e,n,r);let f;try{f=new RegExp(m,"i")}catch{return this._emptyResult(e,n,r)}const d=Math.min(Math.max(0,i.context_before??0),v),y=Math.min(Math.max(0,i.context_after??0),v),T=await this.readFromFile(n,"grep",o,s,d,y,f);a=T.rows,u=T.hasMore,l=T.nextStartLine}const c=[];let p=0,g=!1;for(const m of a){const f=Buffer.byteLength(m.text,"utf-8")+1;if(c.length>=s||p+f>K){g=!0,u=!0;break}c.push(m),p+=f}const w=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:r,lines:c,lineCount:c.length,metadata:w?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:p,truncated:g,outputTruncated:n.outputTruncated,hasMore:u,nextStartLine:u?l:null,outputFile:n.outputPath}:void 0}}_emptyResult(t,i,e){return{taskId:t,status:i.status,mode:e,lines:[],lineCount:0,metadata:{totalLines:i.outputLineCount,totalBytes:i.outputBytes,returnedBytes:0,truncated:!1,outputTruncated:i.outputTruncated,hasMore:!1,nextStartLine:null,outputFile:i.outputPath}}}async readFromFile(t,i,e,n,r,s,o){const a=[],u=new Set;let l=0,c=!1;const p=[];let g=-1/0;try{const w=await S.open(t.outputPath,"r"),m=O({input:w.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const f of m){const d=l;if(l++,i==="grep"&&d-e>=Y&&d>=e){c=!0;break}if(d>=e){if(i==="lines"){if(a.length>=n){c=!0;break}u.has(d)||(u.add(d),a.push({line:d,text:f}))}else if(o.test(f)){for(const T of p)T.line>=e&&!u.has(T.line)&&(u.add(T.line),a.push(T));if(u.has(d)||(u.add(d),a.push({line:d,text:f})),g=d,a.length>=n){c=!0;break}}else if(r>0&&(p.push({line:d,text:f}),p.length>r&&p.shift()),g>=e&&d-g<=s&&!u.has(d)&&(u.add(d),a.push({line:d,text:f}),a.length>=n)){c=!0;break}}}m.close(),await w.close()}catch{return{rows:[],hasMore:!1,nextStartLine:null}}return c?{rows:a,hasMore:c,nextStartLine:l}:{rows:a,hasMore:c,nextStartLine:null}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(o=>{let a=null;const u=l=>{a&&clearTimeout(a),o(l)};n.completionWaiters.push(u),e&&e>0&&(a=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(l=>l!==u),o({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),a.unref?.())});const s=(await this.store.readSession(t))?.tasks.find(o=>o.taskId===i);return s?s.status!=="running"?{taskId:i,status:s.status,exitCode:s.exitCode,signal:s.signal,timedOut:s.killedReason==="timeout",aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt,...s.outputPath?{outputFilePath:s.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(s.finishedAt??Date.now())-s.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const r=e.get(n);if(!r)continue;const s=r.completionWaiters;if(s.length===0)continue;r.completionWaiters=[];const o={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-r.startedAt};for(const a of s)a(o)}}async killTask(t,i){const e=this.getTask(t,i);return e?e.status!=="running"?{taskId:i,status:e.status,exitCode:e.exitCode??void 0}:e.child?(e.killedReason="user_kill",this.killProcessGroup(e.child)?(e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.killEscalationTimer=setTimeout(()=>{e.child&&!e.finalized&&this.killProcessGroup(e.child,!0)},N),e.killEscalationTimer.unref?.(),{taskId:i,status:"killed",signal:"SIGTERM"}):{taskId:i,status:e.status,error:"kill_failed",reason:"permission_denied"}):{taskId:i,status:e.status,error:"kill_failed",reason:"no_child"}:{taskId:i,status:"killed",error:"task_not_found"}}killProcessGroup(t,i=!1){const e=t.pid;if(!e)return!1;try{if(process.platform==="win32")return R("taskkill",["/PID",String(e),"/T","/F"],{windowsHide:!0}),!0;try{process.kill(-e,i?"SIGKILL":"SIGTERM")}catch{process.kill(e,i?"SIGKILL":"SIGTERM")}return!0}catch{return!1}}async disposeAgent(t){const i=this.tasksByAgent.get(t);if(i){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="session_disposed",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);this.tasksByAgent.delete(t)}await this.store.deleteAgent(t)}async disposeAll(){for(const[t,i]of this.tasksByAgent){for(const e of i.values())e.status==="running"&&(e.killedReason="process_exit",e.child&&this.killProcessGroup(e.child,!0),e.finalized||this.finalizeTask(e,null,null,"process_exit")),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);await this.store.updateSession(t,()=>null),this.tasksByAgent.delete(t)}}async reconcileDiskRunning(t){const i=this.tasksByAgent.get(t);let e=0;return await this.store.updateSession(t,n=>{let r=!1;for(const s of n.tasks){if(s.status!=="running")continue;const o=i?.get(s.taskId)?.status==="running",a=s.ownerBootId===x;if(o){a||(s.ownerBootId=x,r=!0);continue}s.status="killed",s.killedReason="runtime_crashed",s.finishedAt=Date.now(),s.notified=!0,e++,r=!0}return r?n.tasks:null}),e}async listTasksWithReconcile(t,i){try{await this.reconcileDiskRunning(t)}catch{}const e=this.tasksByAgent.get(t),n=e?[...e.values()]:[],r=n.filter(o=>o.backgrounded).map(o=>this.toSummary(o)),s=new Set(n.map(o=>o.taskId));try{const o=await this.store.readSession(t);if(o){const a=Date.now();for(const u of o.tasks)s.has(u.taskId)||u.backgrounded===!0&&u.status!=="running"&&(u.finishedAt&&a-u.finishedAt>=B||i&&i!=="all"&&u.status!==i||r.push(Z(u)))}}catch{}return i&&i!=="all"?r.filter(o=>o.status===i).sort((o,a)=>a.startedAt-o.startedAt):r.sort((o,a)=>a.startedAt-o.startedAt)}async reconcileAllAgents(){const t=await this.store.listAgents();let i=0;for(const e of t)try{i+=await this.reconcileDiskRunning(e)}catch{}return i}async reconcileOrphans(t){await this.reconcileDiskRunning(t)}async readSessionState(t){return this.store.readSession(t)}async markNotified(t,i){await this.store.markNotified(t,i)}getOrCreateBucket(t){let i=this.tasksByAgent.get(t);return i||(i=new Map,this.tasksByAgent.set(t,i)),i}getTask(t,i){const e=this.tasksByAgent.get(t);if(e&&/^[A-Za-z0-9]{8}$/.test(i))return e.get(i)}generateTaskId(t){for(let i=0;i<3;i++){const e=M(4).toString("hex").slice(0,8);if(!t.has(e))return e}return M(6).toString("hex").slice(0,8)}async persistTask(t){await this.store.updateSession(t.agentId,i=>{const e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,status:t.status,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputPath:t.outputPath,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,notified:t.notified,killedReason:t.killedReason,ownerBootId:t.bootId,lastPulseAt:t.status==="running"?t.lastPulseAt:void 0,backgrounded:t.backgrounded},n=i.tasks.findIndex(s=>s.taskId===t.taskId);n>=0?i.tasks[n]=e:i.tasks.push(e);const r=Date.now();return i.tasks.filter(s=>s.status==="running"||!s.finishedAt||r-s.finishedAt<B)})}persistTaskSafe(t){this.persistTask(t).catch(i=>{console.error(`[BackgroundCommandManager] persist ${t.agentId}/${t.taskId} failed:`,i)})}}class I extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}function Z(h){return{taskId:h.taskId,status:h.status,command:h.command,commandPreview:h.commandPreview,description:h.description,cwd:h.cwd,startedAt:h.startedAt,finishedAt:h.finishedAt,exitCode:h.exitCode,signal:h.signal,outputLineCount:h.outputLineCount,outputBytes:h.outputBytes}}const st=new V;export{V as BackgroundCommandManager,I as BackgroundTaskError,st as backgroundCommandManager};
|
|
5
|
+
`;t.ringBuffer.push(s),this.appendToFile(t,s)}t.ringBuffer.push(e);return}t.ringBuffer.push(e),t.onOutput?.({stream:i,text:e});const o=(e.match(/\n/g)??[]).length;t.outputLineCount+=o,t.outputBytes+=Buffer.byteLength(e,"utf-8"),this.appendToFile(t,e)}scheduleInactivity(t){t.finalized||t.inactivityTimeoutMs<=0||(t.inactivityTimer&&clearTimeout(t.inactivityTimer),t.inactivityTimer=setTimeout(()=>{t.finalized||!t.child||(t.inactivityTimedOut=!0,t.killedReason="inactivity",this.killProcessGroup(t.child))},t.inactivityTimeoutMs),t.inactivityTimer.unref?.())}appendToFile(t,i){return t.outputBytes>_?Promise.resolve():(t.outputWriteChain=t.outputWriteChain.then(()=>S.appendFile(t.outputPath,i,"utf-8")).catch(e=>{t.ringBuffer.push(`[ACode] output write failed: ${e?.message??e}
|
|
6
|
+
`)}),t.outputWriteChain)}finalizeTask(t,i,e,n){if(t.finalized)return;t.finalized=!0,t.timeoutTimer&&(clearTimeout(t.timeoutTimer),t.timeoutTimer=null),t.killEscalationTimer&&(clearTimeout(t.killEscalationTimer),t.killEscalationTimer=null),t.inactivityTimer&&(clearTimeout(t.inactivityTimer),t.inactivityTimer=null),t.exitGraceTimer&&(clearTimeout(t.exitGraceTimer),t.exitGraceTimer=null),t.child=null,t.finishedAt=Date.now();let r;n?(r="killed",t.killedReason=n):i===0?r="completed":r="failed",t.status=r,t.exitCode=i,t.signal=e;const o=n==="inactivity"||t.inactivityTimedOut,s={taskId:t.taskId,exitCode:i,signal:e,timedOut:n==="timeout"||o,inactivityTimedOut:o,outputFilePath:t.outputPath,outputLineCount:t.outputLineCount,outputSpilled:t.outputTruncated,commandFilePath:t.commandFilePath,commandLineCount:t.commandLineCount,timeoutMs:t.timeoutMs,inactivityTimeoutMs:t.inactivityTimeoutMs,stdout:t.stdoutText,stderr:t.stderrText,durationMs:t.finishedAt-t.startedAt};t.completionResolve?.(s);const a={taskId:t.taskId,status:r,exitCode:i,signal:e,timedOut:n==="timeout"||o,aborted:!1,durationMs:t.finishedAt-t.startedAt},u=t.completionWaiters;t.completionWaiters=[];for(const l of u)l(a);if(this.persistTaskSafe(t),t.onCompleted?.({taskId:t.taskId,agentId:t.agentId,status:r,exitCode:i,signal:e,commandPreview:t.commandPreview,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,outputPath:t.outputPath,finishedAt:t.finishedAt,killedReason:t.killedReason}),t.outputSubscribers.size>0){const l=Array.from(t.outputSubscribers);t.outputSubscribers.clear();for(const c of l)try{c.onDone({status:r,exitCode:i,signal:e})}catch{}}t.retentionTimer=setTimeout(()=>{this.evictTask(t)},x),t.retentionTimer.unref?.()}evictTask(t){S.unlink(t.outputPath).catch(()=>{});const i=this.tasksByAgent.get(t.agentId);i&&(i.delete(t.taskId),i.size===0&&this.tasksByAgent.delete(t.agentId))}taskCapacity(t){const i=this.tasksByAgent.get(t)?.size??0;return{limit:B,used:i,remaining:Math.max(0,B-i)}}listTasks(t,i){const e=this.tasksByAgent.get(t);if(!e)return[];const n=[];for(const r of e.values())i&&i!=="all"&&r.status!==i||n.push(this.toSummary(r));return n.sort((r,o)=>o.startedAt-r.startedAt)}toSummary(t){return{taskId:t.taskId,status:t.status,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes}}getTaskOutputSnapshot(t,i,e=500){const n=this.getTask(t,i);if(!n)return null;const r=n.ringBuffer.tail(e);return{taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:r,nextSeq:n.outputSeq+1,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:n.outputTruncated}}subscribeTaskOutput(t,i,e){const n=this.getTask(t,i);if(!n)return null;n.outputSubscribers.add(e);let r=!0;return()=>{r&&(r=!1,n.outputSubscribers.delete(e))}}async readOutput(t,i){const e=String(i.task_id??""),n=this.getTask(t,e),r=i.mode==="lines"||i.mode==="grep"?i.mode:"tail",o=Math.min(Math.max(1,i.line_size??100),Y),s=Number.isFinite(i.start_line)&&(i.start_line??0)>0?Math.floor(i.start_line??0):0;if(!n)return{taskId:e,status:"killed",mode:r,lines:[],lineCount:0,metadata:{totalLines:0,totalBytes:0,returnedBytes:0,truncated:!1,outputTruncated:!1,hasMore:!1,nextStartLine:null,outputFile:""}};let a=[],u=!1,l=null;if(r==="tail"){const m=n.ringBuffer.tail(o),f=Math.max(0,n.outputLineCount-m.length);a=m.map((d,y)=>({line:f+y,text:d})),l=null}else if(r==="lines"){const m=n.ringBuffer.firstLineIndex(),f=m+n.ringBuffer.getLineCount();if(s>=m&&s<f)a=n.ringBuffer.offset(s,o).map((y,T)=>({line:s+T,text:y})),u=s+a.length<f,l=u?s+a.length:null;else if(s<f){const d=await this.readFromFile(n,"lines",s,o,0,0,void 0);a=d.rows,u=d.hasMore,l=d.nextStartLine}}else{const m=String(i.pattern??"");if(!m||m.length>H)return this._emptyResult(e,n,r);let f;try{f=new RegExp(m,"i")}catch{return this._emptyResult(e,n,r)}const d=Math.min(Math.max(0,i.context_before??0),v),y=Math.min(Math.max(0,i.context_after??0),v),T=await this.readFromFile(n,"grep",s,o,d,y,f);a=T.rows,u=T.hasMore,l=T.nextStartLine}const c=[];let p=0,g=!1;for(const m of a){const f=Buffer.byteLength(m.text,"utf-8")+1;if(c.length>=o||p+f>U){g=!0,u=!0;break}c.push(m),p+=f}const w=i.include_metadata!==!1;return{taskId:e,status:n.status,mode:r,lines:c,lineCount:c.length,metadata:w?{totalLines:n.outputLineCount,totalBytes:n.outputBytes,returnedBytes:p,truncated:g,outputTruncated:n.outputTruncated,hasMore:u,nextStartLine:u?l:null,outputFile:n.outputPath}:void 0}}_emptyResult(t,i,e){return{taskId:t,status:i.status,mode:e,lines:[],lineCount:0,metadata:{totalLines:i.outputLineCount,totalBytes:i.outputBytes,returnedBytes:0,truncated:!1,outputTruncated:i.outputTruncated,hasMore:!1,nextStartLine:null,outputFile:i.outputPath}}}async readFromFile(t,i,e,n,r,o,s){const a=[],u=new Set;let l=0,c=!1;const p=[];let g=-1/0;try{const w=await S.open(t.outputPath,"r"),m=O({input:w.createReadStream({encoding:"utf-8"}),crlfDelay:1/0});for await(const f of m){const d=l;if(l++,i==="grep"&&d-e>=K&&d>=e){c=!0;break}if(d>=e){if(i==="lines"){if(a.length>=n){c=!0;break}u.has(d)||(u.add(d),a.push({line:d,text:f}))}else if(s.test(f)){for(const T of p)T.line>=e&&!u.has(T.line)&&(u.add(T.line),a.push(T));if(u.has(d)||(u.add(d),a.push({line:d,text:f})),g=d,a.length>=n){c=!0;break}}else if(r>0&&(p.push({line:d,text:f}),p.length>r&&p.shift()),g>=e&&d-g<=o&&!u.has(d)&&(u.add(d),a.push({line:d,text:f}),a.length>=n)){c=!0;break}}}m.close(),await w.close()}catch{return{rows:[],hasMore:!1,nextStartLine:null}}return c?{rows:a,hasMore:c,nextStartLine:l}:{rows:a,hasMore:c,nextStartLine:null}}async awaitCompletion(t,i,e){return Promise.all(i.map(n=>this.awaitOne(t,n,e)))}async awaitOne(t,i,e){const n=this.getTask(t,i);if(n)return n.status!=="running"?this.taskToAwaitResult(n):new Promise(s=>{let a=null;const u=l=>{a&&clearTimeout(a),s(l)};n.completionWaiters.push(u),e&&e>0&&(a=setTimeout(()=>{n.completionWaiters=n.completionWaiters.filter(l=>l!==u),s({taskId:i,status:"running",exitCode:null,signal:null,timedOut:!0,aborted:!1,durationMs:Date.now()-n.startedAt})},e),a.unref?.())});const o=(await this.store.readSession(t))?.tasks.find(s=>s.taskId===i);return o?o.status!=="running"?{taskId:i,status:o.status,exitCode:o.exitCode,signal:o.signal,timedOut:o.killedReason==="timeout",aborted:!1,durationMs:(o.finishedAt??Date.now())-o.startedAt,...o.outputPath?{outputFilePath:o.outputPath}:{}}:{taskId:i,status:"killed",exitCode:null,signal:null,timedOut:!1,aborted:!1,durationMs:(o.finishedAt??Date.now())-o.startedAt}:null}taskToAwaitResult(t){return{taskId:t.taskId,status:t.status,exitCode:t.exitCode,signal:t.signal,timedOut:t.killedReason==="timeout",aborted:!1,durationMs:(t.finishedAt??Date.now())-t.startedAt,...t.status!=="running"&&t.outputPath?{outputFilePath:t.outputPath}:{}}}interruptAwaits(t,i){const e=this.tasksByAgent.get(t);if(e)for(const n of i){const r=e.get(n);if(!r)continue;const o=r.completionWaiters;if(o.length===0)continue;r.completionWaiters=[];const s={taskId:n,status:"running",exitCode:null,signal:null,timedOut:!1,aborted:!0,durationMs:Date.now()-r.startedAt};for(const a of o)a(s)}}async killTask(t,i){const e=this.getTask(t,i);if(!e){const o=(await this.store.readSession(t))?.tasks.find(s=>s.taskId===i);return o?{taskId:i,status:o.status==="running"?"killed":o.status,exitCode:o.exitCode??void 0,signal:o.signal??void 0}:{taskId:i,status:"killed",error:"task_not_found"}}return e.status!=="running"?(this.tasksByAgent.get(t)?.delete(i),{taskId:i,status:e.status,exitCode:e.exitCode??void 0}):e.child?(e.killedReason="user_kill",this.killProcessGroup(e.child)?(e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.killEscalationTimer=setTimeout(()=>{e.child&&!e.finalized&&this.killProcessGroup(e.child,!0)},N),e.killEscalationTimer.unref?.(),this.tasksByAgent.get(t)?.delete(i),{taskId:i,status:"killed",signal:"SIGTERM"}):{taskId:i,status:e.status,error:"kill_failed",reason:"permission_denied"}):{taskId:i,status:e.status,error:"kill_failed",reason:"no_child"}}killProcessGroup(t,i=!1){const e=t.pid;if(!e)return!1;try{if(process.platform==="win32")return R("taskkill",["/PID",String(e),"/T","/F"],{windowsHide:!0}),!0;try{process.kill(-e,i?"SIGKILL":"SIGTERM")}catch{process.kill(e,i?"SIGKILL":"SIGTERM")}return!0}catch{return!1}}async disposeAgent(t){const i=this.tasksByAgent.get(t);if(i){for(const e of i.values())e.status==="running"&&e.child&&(e.killedReason="session_disposed",this.killProcessGroup(e.child,!0)),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);this.tasksByAgent.delete(t)}await this.store.deleteAgent(t)}async disposeAll(){for(const[t,i]of this.tasksByAgent){for(const e of i.values())e.status==="running"&&(e.killedReason="process_exit",e.child&&this.killProcessGroup(e.child,!0),e.finalized||this.finalizeTask(e,null,null,"process_exit")),e.timeoutTimer&&clearTimeout(e.timeoutTimer),e.killEscalationTimer&&clearTimeout(e.killEscalationTimer),e.retentionTimer&&clearTimeout(e.retentionTimer),e.exitGraceTimer&&clearTimeout(e.exitGraceTimer);await this.store.updateSession(t,()=>null),this.tasksByAgent.delete(t)}}async reconcileDiskRunning(t){const i=this.tasksByAgent.get(t);let e=0;return await this.store.updateSession(t,n=>{let r=!1;for(const o of n.tasks){if(o.status!=="running")continue;const s=i?.get(o.taskId)?.status==="running",a=o.ownerBootId===M;if(s){a||(o.ownerBootId=M,r=!0);continue}o.status="killed",o.killedReason="runtime_crashed",o.finishedAt=Date.now(),o.notified=!0,e++,r=!0}return r?n.tasks:null}),e}async listTasksWithReconcile(t,i){try{await this.reconcileDiskRunning(t)}catch{}const e=this.tasksByAgent.get(t),n=e?[...e.values()]:[],r=n.filter(s=>s.backgrounded).map(s=>this.toSummary(s)),o=new Set(n.map(s=>s.taskId));try{const s=await this.store.readSession(t);if(s){const a=Date.now();for(const u of s.tasks)o.has(u.taskId)||u.backgrounded===!0&&u.status!=="running"&&(u.finishedAt&&a-u.finishedAt>=x||i&&i!=="all"&&u.status!==i||r.push(Z(u)))}}catch{}return i&&i!=="all"?r.filter(s=>s.status===i).sort((s,a)=>a.startedAt-s.startedAt):r.sort((s,a)=>a.startedAt-s.startedAt)}async reconcileAllAgents(){const t=await this.store.listAgents();let i=0;for(const e of t)try{i+=await this.reconcileDiskRunning(e)}catch{}return i}async reconcileOrphans(t){await this.reconcileDiskRunning(t)}async readSessionState(t){return this.store.readSession(t)}async markNotified(t,i){await this.store.markNotified(t,i)}getOrCreateBucket(t){let i=this.tasksByAgent.get(t);return i||(i=new Map,this.tasksByAgent.set(t,i)),i}getTask(t,i){const e=this.tasksByAgent.get(t);if(e&&/^[A-Za-z0-9]{8}$/.test(i))return e.get(i)}generateTaskId(t){for(let i=0;i<3;i++){const e=C(4).toString("hex").slice(0,8);if(!t.has(e))return e}return C(6).toString("hex").slice(0,8)}async persistTask(t){await this.store.updateSession(t.agentId,i=>{const e={taskId:t.taskId,agentId:t.agentId,command:t.command,commandPreview:t.commandPreview,description:t.description,cwd:t.cwd,status:t.status,startedAt:t.startedAt,finishedAt:t.finishedAt,exitCode:t.exitCode,signal:t.signal,outputPath:t.outputPath,outputLineCount:t.outputLineCount,outputBytes:t.outputBytes,notified:t.notified,killedReason:t.killedReason,ownerBootId:t.bootId,lastPulseAt:t.status==="running"?t.lastPulseAt:void 0,backgrounded:t.backgrounded},n=i.tasks.findIndex(o=>o.taskId===t.taskId);n>=0?i.tasks[n]=e:i.tasks.push(e);const r=Date.now();return i.tasks.filter(o=>o.status==="running"||!o.finishedAt||r-o.finishedAt<x)})}persistTaskSafe(t){this.persistTask(t).catch(i=>{console.error(`[BackgroundCommandManager] persist ${t.agentId}/${t.taskId} failed:`,i)})}}class E extends Error{constructor(t,i){super(i),this.name="BackgroundTaskError",this.code=t}}function Z(h){return{taskId:h.taskId,status:h.status,command:h.command,commandPreview:h.commandPreview,description:h.description,cwd:h.cwd,startedAt:h.startedAt,finishedAt:h.finishedAt,exitCode:h.exitCode,signal:h.signal,outputLineCount:h.outputLineCount,outputBytes:h.outputBytes}}const st=new V;export{V as BackgroundCommandManager,E as BackgroundTaskError,st as backgroundCommandManager};
|
|
7
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomUUID as pt}from"node:crypto";import{promises as p,createReadStream as Q}from"node:fs";import gt from"node:os";import u from"node:path";import tt from"node:readline";import{runShellCommand as wt}from"./builtins/shellCommandRunner.js";import{createOutputEventCollector as yt}from"./builtins/commandOutputEvents.js";import{buildCommandOutputSpilloverContent as _t,COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT as bt,countOutputLines as et,formatCommandOutputSpilloverStdout as kt,writeCommandArtifactFile as
|
|
1
|
+
import{randomUUID as pt}from"node:crypto";import{promises as p,createReadStream as Q}from"node:fs";import gt from"node:os";import u from"node:path";import tt from"node:readline";import{runShellCommand as wt}from"./builtins/shellCommandRunner.js";import{createOutputEventCollector as yt}from"./builtins/commandOutputEvents.js";import{buildCommandOutputSpilloverContent as _t,COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT as bt,countOutputLines as et,formatCommandOutputSpilloverStdout as kt,writeCommandArtifactFile as vt}from"./builtins/commandArtifacts.js";import{symbolsTool as Et}from"./built-in-symbols-tool.js";import{buildRevisionId as D,checkFileFreshForWrite as ot,createReadFileEntry as z,STALE_FILE_WRITE_REASON as it,toStateKey as U}from"./read-file-state.js";import{createListBackgroundCommandsTool as Pt,createKillBackgroundCommandTool as St,createReadBackgroundOutputTool as xt}from"./background-command-tools.js";import{detectImageFormatFromBuffer as Mt,formatBytes as x,imageMediaType as nt,readImageDimensions as Tt,tryCompressImageWithSharp as Rt,tryResizeWithSharp as $t}from"./image-utils.js";const Lt=2e3,B=2e3,at=50*1024,rt=4096,It=.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"]),At=new Set(["png","jpg","jpeg","gif","webp"]),st=20*1024*1024,F=10*1024*1024,G=Math.floor(3.75*1024*1024),j=2e3,Bt=50*1024,Ft=100*1024,ct=15e3,Ct=process.env.ACODE_IMAGE_DEBUG==="1",Nt={Accept:"image/webp,image/png,image/jpeg,image/gif,*/*;q=0.8","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"},Ut=1e3,Wt=1024*1024,Dt=256*1024,zt=1e3,jt=new Set([".git","node_modules","dist","build",".next",".nuxt",".cache",".turbo",".output","coverage",".nyc_output"]),Le={fileReadOutside:"allow",commandExecution:"allow",fileModification:"allow",fileModificationOutside:"allow"};async function C(t,e,i){if(!t)return;const o=t.getLevel(e);if(o==="deny")throw new Error(`Operation blocked by tool permission policy (${e} = deny). Explain this restriction to the user if needed.`);if(o==="ask"&&!await t.requestPermission(e,i))throw new Error(`Operation denied by the user (${e}). Do not retry the same operation without asking the user.`)}function R(t){return t.replace(/\\/g,"/")}async function H(t){try{return await p.access(t),!0}catch{return!1}}function qt(t,e){if(!e)return 0;let i=0,o=t.indexOf(e);for(;o>=0;)i+=1,o=t.indexOf(e,o+e.length);return i}function I(t,e,i){for(const o of e){const n=t[o];if(typeof n=="string")return n}throw new Error(`${i} is required`)}function O(t,e){for(const i of e){const o=t[i];if(typeof o=="string"&&o.trim())return o}}function q(t,e,i){for(const o of e){const n=t[o],a=typeof n=="number"?n:typeof n=="string"?Number(n):NaN;if(Number.isFinite(a)&&a>0)return Math.floor(a)}return i}function Kt(t,e,i){for(const o of e){const n=t[o],a=typeof n=="number"?n:typeof n=="string"?Number(n):NaN;if(Number.isFinite(a)&&a>0)return Math.floor(a);if(Number.isFinite(a)&&a===0)return 0}return i}function Xt(t){const e=t.compress;if(e==null)return"auto";if(typeof e=="boolean")return e;if(e==="auto")return"auto";if(e==="true")return!0;if(e==="false")return!1;throw new Error(`Invalid compress argument: ${JSON.stringify(e)} (expected true, false, or "auto")`)}async function lt(t){const e=u.extname(t).toLowerCase();if(Ot.has(e))return!0;const i=await p.open(t,"r");try{const o=Buffer.alloc(rt),{bytesRead:n}=await i.read(o,0,rt,0);if(n===0)return!1;const a=o.subarray(0,n);if(a.includes(0))return!0;let r=0;for(let c=0;c<n;c++){const s=a[c];(s<9||s>13&&s<32)&&r++}return r/n>It}finally{await i.close()}}function A(t,e){const i=u.resolve(e),o=u.relative(t,i);if(o.startsWith("..")||u.isAbsolute(o))throw new Error(`Target path is outside workspace: ${i}`);return i}function Yt(t,e){const i=u.resolve(String(t||"").trim()),o=W(String(e??"").trim());return`file:${(u.isAbsolute(o)?u.resolve(o):u.resolve(i,o)).replace(/\\/g,"/")}`}function K(t,e,i){return{key:Yt(t,e),access:i}}function X(t){const e=t.file_path??t.filePath;if(typeof e!="string")return null;const i=e.trim();return i.length>0?i:null}function W(t){return t==="~"||t.startsWith("~/")?u.join(gt.homedir(),t.slice(1)):t}async function Gt(t,e){const i=u.resolve(String(t||"").trim()),o=await p.realpath(i);if(!(await p.stat(o)).isDirectory())throw new Error(`Workspace path is not a directory: ${i}`);if(!e?.trim())return{workspaceRoot:o,cwd:o};const a=W(e.trim()),r=u.isAbsolute(a)?u.resolve(a):u.resolve(i,a),c=u.relative(i,r);if(c.startsWith("..")||u.isAbsolute(c))throw new Error(`Command cwd is outside workspace: ${r}`);const s=A(o,await p.realpath(r));if(!(await p.stat(s)).isDirectory())throw new Error(`Command cwd is not a directory: ${s}`);return{workspaceRoot:o,cwd:s}}async function Ht(t){try{if((await p.lstat(t)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${t}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}async function Vt(t,e){const i=await p.realpath(u.dirname(e));A(t,i);try{if((await p.lstat(e)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${e}`)}catch(o){if((typeof o=="object"&&o!==null?Reflect.get(o,"code"):void 0)!=="ENOENT")throw o}}async function ut(t,e){const i=u.resolve(String(t||"").trim()),o=await p.realpath(i);if(!(await p.stat(o)).isDirectory())throw new Error(`Workspace path is not a directory: ${i}`);const a=W(e.trim());if(!a)throw new Error("file_path is required");const r=u.isAbsolute(a)?u.resolve(a):u.resolve(i,a),c=u.relative(i,r);if(c.startsWith("..")||u.isAbsolute(c))throw new Error(`Target path is outside workspace: ${r}`);return{workspaceRoot:o,requestedPath:r,targetPath:A(o,u.join(o,c))}}async function dt(t,e,i){const o=u.resolve(String(t||"").trim()),n=await p.realpath(o);if(!(await p.stat(n)).isDirectory())throw new Error(`Workspace path is not a directory: ${o}`);const r=W(e.trim());if(!r)throw new Error("file_path is required");const c=u.isAbsolute(r)?u.resolve(r):u.resolve(o,r),s=u.relative(o,c),d=await p.realpath(c).catch(()=>c),l=u.relative(n,d);if(s.startsWith("..")||u.isAbsolute(s)||l.startsWith("..")||u.isAbsolute(l)){if(!i)throw new Error(`Target path is outside workspace: ${c}`);return await C(i,"fileReadOutside",{path:c}),{workspaceRoot:n,requestedPath:c,targetPath:d}}return{workspaceRoot:n,requestedPath:c,targetPath:A(n,d)}}async function Jt(t,e){let i=u.dirname(t);for(;;){const o=u.dirname(i);try{return await p.realpath(i)}catch{if(o===i)return e;i=o}}}async function V(t,e,i,o){const n=u.resolve(String(t||"").trim()),a=await p.realpath(n);if(!(await p.stat(a)).isDirectory())throw new Error(`Workspace path is not a directory: ${n}`);const c=W(e.trim());if(!c)throw new Error("file_path is required");const s=u.isAbsolute(c)?u.resolve(c):u.resolve(n,c),d=u.relative(n,s),l=d.startsWith("..")||u.isAbsolute(d),m=await p.realpath(s).catch(async()=>Jt(s,a)),w=u.relative(a,m),g=w.startsWith("..")||u.isAbsolute(w);if(!l&&!g)return{workspaceRoot:a,requestedPath:s,targetPath:A(a,u.join(a,d))};if(!o)throw new Error(`Target path is outside workspace: ${s}`);await C(o,"fileModificationOutside",{operation:i,path:s});const f=await H(s);return{workspaceRoot:a,requestedPath:s,targetPath:f?m:s,outsideWorkspace:!0}}async function mt(t,e){const i=u.dirname(t.targetPath),o=!await H(i);await p.mkdir(i,{recursive:!0}),t.outsideWorkspace?await Ht(t.targetPath):await Vt(t.workspaceRoot,t.targetPath);const n=u.join(i,`.${u.basename(t.targetPath)}.${process.pid}.${pt()}.tmp`);try{await p.writeFile(n,e,"utf-8"),await p.rename(n,t.targetPath)}catch(a){throw await p.rm(n,{force:!0}),a}return{workspaceRoot:t.workspaceRoot,targetPath:t.targetPath,bytes:Buffer.byteLength(e,"utf-8"),createdParentDirectory:o,outsideWorkspace:t.outsideWorkspace===!0}}async function Zt(t,e){const i=await ut(t,e),o=A(i.workspaceRoot,await p.realpath(i.targetPath)),n=await p.stat(o);if(!n.isFile())throw new Error(`Path is not a file: ${o}`);return{workspaceRoot:i.workspaceRoot,targetPath:o,content:await p.readFile(o,"utf-8"),stat:{mtimeMs:n.mtimeMs,size:n.size}}}async function Qt(t){const e=await p.realpath(t).catch(()=>t),i=await p.stat(e);if(!i.isFile())throw new Error(`Path is not a file: ${e}`);return{workspaceRoot:u.dirname(e),targetPath:e,content:await p.readFile(e,"utf-8"),stat:{mtimeMs:i.mtimeMs,size:i.size}}}async function ht(t,e,i){if(t?.tracker)try{const o=await p.realpath(e),n=await p.stat(o);t.tracker.record(U(o),z(n,i,!1))}catch{}}async function te(t,e,i,o,n){const a=await dt(t,e,n),r=a.targetPath,c=await p.stat(r);if(!c.isFile())throw new Error(`Path is not a file: ${r}`);if(await lt(r))return{workspaceRoot:a.workspaceRoot,targetPath:r,content:"[Binary file, content skipped]",isBinary:!0,truncated:!1,totalLines:0,shownFromLine:0,shownToLine:0,mtimeMs:c.mtimeMs,sizeBytes:c.size};const s=Q(r,{encoding:"utf-8"}),d=tt.createInterface({input:s,crlfDelay:1/0});try{const l=[];let m=0,w=0,g=0,f=!1,h=!1;const k=i-1;for await(const P of d){if(w++,m<k){m++;continue}if(l.length>=o){f=!0,m++;continue}let y=P;y.length>B&&(y=y.slice(0,B)+`... (line truncated to ${B} chars)`);const S=`${m+1}: ${y}`,b=Buffer.byteLength(S,"utf-8")+1;if(g+b>at){h=!0;break}l.push(S),g+=b,m++}if(w>0&&l.length===0&&!f&&!h&&k>=w)throw new Error(`offset ${i} exceeds total line count ${w} in file: ${r}`);const _=l.length>0?i:0,v=l.length>0?i+l.length-1:0;let E;h?E=`Output capped at 50 KB. Showing lines ${_}-${v}. Use offset=${v+1} to continue.`:f?E=`Showing lines ${_}-${v} of ${w}. Use offset=${v+1} to continue.`:E=`End of file - total ${w} lines`;const $=l.length>0?l.join(`
|
|
2
2
|
`)+`
|
|
3
|
-
`+v:v;return{workspaceRoot:n.workspaceRoot,targetPath:r,content:S,isBinary:!1,truncated:f||h,totalLines:h?-1:w,shownFromLine:y,shownToLine:E,mtimeMs:Math.trunc(c.mtimeMs),sizeBytes:c.size}}finally{d.close(),s.destroy()}}function X(t){return/^https?:\/\//i.test(t)}function ee(t){try{const e=new URL(t);return`${e.origin}${e.pathname}`}catch{return"(invalid-url)"}}function I(t,e){if(!Nt)return;const o=e?` ${JSON.stringify(e)}`:"";console.error(`[acode:image] ${t}${o}`)}function oe(t){return!t||t.startsWith("image/")||t==="application/octet-stream"||t==="binary/octet-stream"?!1:t.startsWith("text/")||t==="application/json"||t.endsWith("+json")||t==="application/xml"||t.endsWith("+xml")}async function ie(t){let e;try{e=new URL(t)}catch{throw new Error(`Invalid image URL: ${t}`)}if(e.protocol!=="http:"&&e.protocol!=="https:")throw new Error(`Unsupported image URL protocol: ${e.protocol} (only http/https)`);const o=ee(t),i=Date.now();I("url fetch start",{url:o,timeoutMs:ct});let a;try{a=await fetch(t,{headers:Ct,signal:AbortSignal.timeout(ct)})}catch(m){const w=m instanceof Error?m.message:String(m);throw I("url fetch failed",{url:o,reason:w,elapsedMs:Date.now()-i}),new Error(`Image URL fetch failed: ${t} (${w})`)}if(!a.ok)throw I("url fetch failed",{url:o,status:a.status,elapsedMs:Date.now()-i}),new Error(`Image URL fetch failed: ${t} (HTTP ${a.status})`);const n=(a.headers.get("content-type")||"").split(";")[0]?.trim().toLowerCase()||"";if(oe(n))throw I("url fetch rejected: non-image content-type",{url:o,contentType:n,elapsedMs:Date.now()-i}),new Error(`URL did not return an image (content-type: ${n}): ${t}`);const r=Number(a.headers.get("content-length")||"");if(I("url fetch response",{url:o,status:a.status,contentType:n||"(none)",contentLength:Number.isFinite(r)?r:null,elapsedMs:Date.now()-i}),Number.isFinite(r)&&r>B)throw I("url fetch rejected: too large",{url:o,contentLength:r,elapsedMs:Date.now()-i}),new Error(`Image too large to fetch: ${t} (${M(r)} > ${M(B)})`);const c=a.body;if(!c){const m=await a.arrayBuffer();if(m.byteLength>B)throw I("url fetch rejected: too large",{url:o,bytes:m.byteLength,elapsedMs:Date.now()-i}),new Error(`Image too large to fetch: ${t} (${M(m.byteLength)} > ${M(B)})`);return I("url fetch done",{url:o,bytes:m.byteLength,elapsedMs:Date.now()-i}),{buffer:Buffer.from(m),contentType:n}}const s=c.getReader(),d=[];let l=0;for(;;){const{done:m,value:w}=await s.read();if(m)break;if(w){if(l+=w.byteLength,l>B)throw s.cancel().catch(()=>{}),I("url fetch rejected: too large",{url:o,bytes:l,elapsedMs:Date.now()-i}),new Error(`Image too large to fetch: ${t} (${M(l)} > ${M(B)})`);d.push(Buffer.from(w))}}return I("url fetch done",{url:o,bytes:l,elapsedMs:Date.now()-i}),{buffer:Buffer.concat(d,l),contentType:n}}async function ae(t,e,o,i="auto",a){let n,r,c;if(X(e)){const b=await ie(e);n=b.buffer,c=b.contentType,r=e}else{const $=(await dt(t,e,o)).targetPath,T=await p.stat($);if(!T.isFile())throw new Error(`Path is not a file: ${$}`);if(T.size>st)throw new Error(`Image file too large to read: ${R($)} (${M(T.size)} > ${M(st)}). Downscale it first.`);n=await p.readFile($),r=R($),a?.tracker&&a.tracker.record(C($),D(T,null,!0))}if(n.length===0)throw new Error(`Image file is empty: ${r}`);const s=xt(n);if(!s)throw I("image decode failed",{path:r,bytes:n.length,contentType:c||"(local file)"}),new Error(c!==void 0?`URL did not return a supported image (content-type: ${c||"unknown"}, unrecognized format): ${r}`:`Unsupported or corrupt image (unrecognized format): ${r}`);const d=Tt(n);let l=s,m=n,w=d?.width,g=d?.height,f=null;if(i!==!1){const b=await Rt(n,s,{normalMaxBytes:Ft,clearMaxBytes:Bt});b?.compressed&&(m=b.buffer,l=b.mediaType,w=b.width,g=b.height,f={level:b.level,originalSize:b.originalSize})}let h=!1;const k=await $t(m,l,{maxWidth:z,maxHeight:z,maxBytes:Y});if(k)m=k.buffer,l=k.mediaType,w=k.width,g=k.height,h=k.changed;else if(m.length>Y)throw new Error(`Image too large to inline: ${r} (${M(m.length)} > ${M(Y)}). Downscale it first, e.g. via Bash: magick "${e}" -resize ${z}x${z} "${e}". Installing sharp enables automatic downscaling.`);const y=w&&g?`${w}x${g}`:"unknown dimensions",E=h&&d&&d.width!==w?` (resized from ${d.width}x${d.height})`:h?" (resized)":"",v=f!==null?f.level:null,S=f!==null?Math.max(1,Math.round((1-m.length/f.originalSize)*100)):0,_=f!==null?`${M(m.length)} (compressed from ${M(f.originalSize)}, saved ${S}%, ${v} level)`:M(m.length),P=`[Image: ${r}, ${y}${E}, ${_}]`,x=X(e)?u.basename(new URL(e).pathname)||"image":u.basename(r);return{ok:!0,filePath:r,content:P,isBinary:!1,image:{mediaType:at(l),width:w??null,height:g??null,originalWidth:d?.width??null,originalHeight:d?.height??null,resized:h,size:m.length,compressed:f!==null,compressionLevel:v,compressionRatio:f!==null?Math.round((1-m.length/f.originalSize)*100)/100:null,originalSize:n.length},attachments:[{type:"image",name:x,mediaType:at(l),base64Data:m.toString("base64")}]}}function ft(t){let e="",o=0;for(;o<t.length;){const i=t[o];if(i==="*")e+="[^/]*";else if(i==="?")e+="[^/]";else if(i==="{"){const a=t.indexOf("}",o);if(a>=0){const n=t.slice(o+1,a);e+=`(${n.split(",").join("|")})`,o=a}else e+="\\{"}else if(i==="["){const a=t.indexOf("]",o);a>=0?(e+=t.slice(o,a+1),o=a):e+="\\["}else"+^$.()|\\".includes(i)?e+="\\"+i:e+=i;o++}return new RegExp(`^${e}$`)}async function ne(t,e,o,i){const a=[];async function n(r){if(a.length>=i)return;const c=await p.readdir(r,{withFileTypes:!0});for(const s of c){if(a.length>=i)return;if(s.name.startsWith(".")&&s.isDirectory())continue;const d=u.join(r,s.name);if(s.isDirectory()){if(jt.has(s.name))continue;await n(d)}else if(s.isFile()){if(e&&!e.test(s.name)||o&&o.test(s.name))continue;a.push(d)}}}return await n(t),a}async function re(t,e,o){if((await p.stat(t)).size>Wt)return{matches:[],count:0};if(await lt(t))return{matches:[],count:0};const a=u.relative(e,t).replace(/\\/g,"/"),n=[];let r=0;const c=Q(t,{encoding:"utf-8"}),s=tt.createInterface({input:c,crlfDelay:1/0});try{let d=0;for await(const l of s)if(d++,o.regex.test(l)){if(r++,o.collectContent){let m=l;if(m.length>F&&(m=m.slice(0,F)+`... (line truncated to ${F} chars)`),n.push({file:a,line:d,content:m}),n.length>=o.maxResults)break}if(o.stopOnFirstMatch)break}}finally{s.close(),c.destroy()}return{matches:n,count:r}}const se=12*60*6e4,ce=3*6e4,le=6e4,ue=10*6e4;function de(t,e,o,i){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",concurrencyMode:"write",resourceKeys:a=>{const n=K(a);return n?[q(t,n,"write")]:[]},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(a){const n=O(a,["file_path","filePath"],"file_path"),r=O(a,["content"],"content"),c=L(a,["reason"]),s=await H(t,n,"write",o);await N(o,"fileModification",{operation:"write",path:s.targetPath});const d=await G(s.targetPath),l=d?await p.readFile(s.targetPath,"utf-8").catch(()=>null):null,m=d?await p.realpath(s.targetPath).catch(()=>s.targetPath):s.targetPath;let w=null;if(i?.enabled&&d){const f=await p.stat(m);w=W(f.mtimeMs,f.size);const h=ot(i.tracker,C(m),f,l);if(!h.ok)throw new Error(h.reason)}if(i?.enabled&&w){const f=await p.stat(m).catch(()=>null);if(!f||W(f.mtimeMs,f.size)!==w)throw new Error(it)}const g=await mt(s,r);if(await ht(i,g.targetPath,r),e&&!g.outsideWorkspace){const f=u.relative(g.workspaceRoot,g.targetPath).replace(/\\/g,"/");await e({filePath:f,operation:d?"UPDATE":"CREATE",beforeContent:l,afterContent:r,reason:c})}return{ok:!0,filePath:R(g.targetPath),bytes:g.bytes,createdParentDirectory:g.createdParentDirectory}}}}const me=15*1024;function Oe(t){if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"editFailureSnapshot");if(typeof e=="object"&&e!==null&&typeof Reflect.get(e,"content")=="string")return e}function he(t,e,o,i){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",concurrencyMode:"write",resourceKeys:a=>{const n=K(a);return n?[q(t,n,"write")]:[]},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(a){const n=O(a,["file_path","filePath"],"file_path"),r=O(a,["old_string","oldString"],"old_string"),c=O(a,["new_string","newString"],"new_string"),s=L(a,["reason"]);if(!r)throw new Error("old_string must not be empty");const d=await H(t,n,"edit",o);await N(o,"fileModification",{operation:"edit",path:d.targetPath});const l=d.outsideWorkspace?await Qt(d.targetPath):await Zt(t,n),m=C(l.targetPath);let w;try{if(i?.enabled){const h=ot(i.tracker,m,l.stat,l.content);if(!h.ok)throw new Error(h.reason)}const f=qt(l.content,r);if(f===0)throw new Error("old_string was not found in file");if(f>1)throw new Error(`old_string occurs ${f} times; provide a unique match`);if(w=l.content.replace(r,c),i?.enabled){const h=await p.stat(l.targetPath).catch(()=>null);if(!h||W(h.mtimeMs,h.size)!==W(l.stat.mtimeMs,l.stat.size))throw new Error(it)}}catch(f){throw f instanceof Error&&l.stat.size<=me&&(i?.tracker.record(m,D(l.stat,l.content,!1)),f.editFailureSnapshot={path:R(l.targetPath),bytes:l.stat.size,content:l.content}),f}const g=await mt(d,w);if(await ht(i,g.targetPath,w),e&&!g.outsideWorkspace){const f=u.relative(g.workspaceRoot,g.targetPath).replace(/\\/g,"/");await e({filePath:f,operation:"UPDATE",beforeContent:l.content,afterContent:w,reason:s})}return{ok:!0,filePath:R(g.targetPath),bytes:g.bytes}}}}function fe(t,e,o){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",concurrencyMode:"write",resourceKeys:i=>{const a=K(i);return a?[q(t,a,"write")]:[]},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(i){const a=O(i,["file_path","filePath"],"file_path"),n=L(i,["reason"]),r=await H(t,a,"delete",o);await N(o,"fileModification",{operation:"delete",path:r.targetPath});const c=r.outsideWorkspace?await p.realpath(r.targetPath).catch(()=>r.targetPath):A(r.workspaceRoot,await p.realpath(r.targetPath).catch(()=>r.targetPath)),s=await p.stat(c).catch(()=>null);if(!s)throw new Error(`File does not exist: ${c}`);if(!s.isFile())throw new Error(`Path is not a file: ${c}`);const d=await p.readFile(c,"utf-8").catch(()=>null);if(await p.rm(c,{force:!0}),e&&!r.outsideWorkspace){const l=u.relative(r.workspaceRoot,c).replace(/\\/g,"/");await e({filePath:l,operation:"DELETE",beforeContent:d,afterContent:null,reason:n})}return{ok:!0,filePath:R(c),deleted:!0}}}}function pe(t,e,o){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",concurrencyMode:"read",resourceKeys:i=>{const a=K(i);return!a||X(a)?[]:[q(t,a,"read")]},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. Images (.png/.jpg/.jpeg/.gif/.webp) and http(s) image URLs are also supported: the image is inlined into the conversation so you can see it (requires a model with image input). When an image is already available at an http(s) URL (including signed URLs with query strings), pass the URL directly as file_path \u2014 the runtime fetches it in memory and never writes it to disk; do NOT download it with Bash/curl first. offset/limit are ignored for images. By default (compress='auto') images are compressed before inlining using a tiered ladder that judges by the compressed result, not the original size: lossless first (pixel-exact, dimensions preserved), then high-quality lossy downscaling only if the lossless result is still over 50 KB (normal level), then more aggressive downscaling if still over 100 KB (clear level). When compression applies, the result reports the compression level, compressed size, and compression ratio (e.g. saved 80%). Pass compress=true to also attempt compression, or compress=false to disable it (images exceeding the hard 2000px / 3.75MB inline limits are still auto-downscaled when sharp is available). You may issue several read_file calls in ONE message; independent reads run in parallel.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read. For images, also accepts an http(s) URL (including signed URLs with query strings), which is fetched in memory without writing to disk."},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."},compress:{anyOf:[{type:"boolean"},{type:"string",enum:["auto"]}],description:'Image compression mode (ignored for text files). "auto" (default): compress with a tiered ladder driven by the compressed result \u2014 lossless, then high-quality downscale if still >50 KB, then more aggressive if still >100 KB. true: same as auto. false: skip tiered compression entirely (hard inline limits still apply).'}},required:["file_path"],additionalProperties:!1},async execute(i){const a=O(i,["file_path","filePath"],"file_path"),n=u.extname(a).toLowerCase().slice(1);if(X(a)||At.has(n))return await ae(t,a,e,Xt(i),o);const r=j(i,["offset"],1),c=j(i,["limit"],It),s=await te(t,a,r,c,e);if(o?.tracker){let d=null,l=r!==1||s.truncated||s.isBinary;l||(d=await p.readFile(s.targetPath,"utf-8").catch(()=>null),(d==null||d.length>nt)&&(d=null,l=!0)),o.tracker.record(C(s.targetPath),D({mtimeMs:s.mtimeMs,size:s.sizeBytes},d,l))}return{ok:!0,filePath:R(s.targetPath),content:s.content,isBinary:s.isBinary,truncated:s.truncated,totalLines:s.totalLines,shownFromLine:s.shownFromLine,shownToLine:s.shownToLine}}}}function ge(t,e,o,i){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",concurrencyMode:"write",resourceScope:"workspace",resourceKeys:()=>[{key:`workspace:${u.resolve(String(t||"").trim()).replace(/\\/g,"/")}`,access:"write"}],description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded total timeout (timeout_ms) measured from command start \u2014 by default 3 minutes, after which the process is force-terminated; timeout_ms can raise this (up to 12 hours) or set 0 to disable. The tool returns stdout, stderr, exit code, and (if converted to background) a task_id. By default (wait_seconds omitted or -1) the command runs in the FOREGROUND and streams output in real time; if the 5-minute adapter watchdog fires while it is still running, the command is moved to the BACKGROUND and the tool returns a task_id plus an explanation (backgrounded=true, reason=watchdog_timeout). The background command stays bound by the same total timeout (timeout_ms, default 3 minutes, still measured from command start) and can be managed via await_complete / list_background_commands / read_background_output / kill_background_command. For immediate backgrounding use wait_seconds=0, or wait_seconds=n>0 to wait n seconds then background.",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 total command timeout in milliseconds, measured from command start. Defaults to 180000 (3 minutes) and is capped at 43200000 (12 hours). Set 0 to disable the timeout entirely (run until finished or killed)."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000."},wait_seconds:{type:"number",description:"How to wait for the command to finish. Omitted values (or -1, the default): run in the FOREGROUND until completion; if the 5-minute adapter watchdog fires first, the command is moved to the background and a task_id is returned (backgrounded=true, reason=watchdog_timeout). 0: spawn and immediately execute in the background, return task_id. n>0: wait up to n seconds; if still running, move to the background and return task_id. The command remains bound by the total timeout (timeout_ms, default 3 minutes, measured from command start) whether foreground or background; inactivity_timeout_ms is ignored once backgrounded.",minimum:-1},description:{type:"string",description:"Short reason for running this command (what it does and why it is needed), shown to the user in the background commands panel. Always provide a concise reason, e.g. 'Start local dev server for manual verification'."}},required:["command"],additionalProperties:!1},async execute(a,n){const r=O(a,["command"],"command").trim();if(!r)throw new Error("command must not be empty");const c=Kt(a,["timeout_ms","timeoutMs"],ce),s=c===0?0:Math.min(c,se),d=j(a,["inactivity_timeout_ms","inactivityTimeoutMs"],le),l=Math.min(d,ue),m=L(a,["cwd"]),w=L(a,["description"]),g=await Gt(t,m);await N(i,"commandExecution",{command:r,cwd:R(g.cwd)});const f=a.wait_seconds,k=typeof f=="number"&&Number.isFinite(f)?f:-1;if(o)return await we({deps:o,command:r,agentId:e,workspaceRoot:g.workspaceRoot,cwd:g.cwd,timeoutMs:s,inactivityTimeoutMs:l,waitSeconds:k,description:w,onOutput:n?.onOutput,settleSignal:n?.settleSignal});const y=await wt({commandText:r,agentId:e,workspaceRoot:g.workspaceRoot,cwd:g.cwd,timeoutMs:s,inactivityTimeoutMs:l,onOutput:n?.onOutput,watchdogSignal:n?.settleSignal}),E={ok:y.exitCode===0&&!y.timedOut,tool:"Bash",command:r,cwd:R(g.cwd),exitCode:y.exitCode,signal:y.signal,timedOut:y.timedOut,inactivityTimedOut:y.inactivityTimedOut,watchdogInterrupted:y.watchdogInterrupted,timeoutMs:s,inactivityTimeoutMs:l,durationMs:y.durationMs,outputSpilled:y.outputSpilled,outputFilePath:y.outputFilePath,outputLineCount:y.outputLineCount,commandFilePath:y.commandFilePath,commandLineCount:y.commandLineCount,stdout:y.stdout,stderr:y.stderr};return y.watchdogInterrupted?{...E,message:"Command was interrupted by the adapter watchdog after ~5 minutes of synchronous (foreground) execution. Only the partial output produced so far is returned above"+(y.outputFilePath?` (full output spilled to "${y.outputFilePath}"; read it if you need more)`:"")+". For long-running commands, re-run with wait_seconds=0 (or n>0) to execute in the background, then manage via await_complete / list_background_commands / read_background_output / kill_background_command."}:E}}}async function we(t){const{deps:e,command:o,agentId:i,workspaceRoot:a,cwd:n,timeoutMs:r,inactivityTimeoutMs:c,waitSeconds:s,description:d,onOutput:l,settleSignal:m}=t,w=l?yt({cwd:n,commandLineCount:0,onOutput:l}):void 0;let g=!!w;const f=()=>{g&&(g=!1,w?.close())};try{const{taskId:h,completion:k}=e.manager.startCommand({commandText:o,agentId:i,workspaceRoot:a,cwd:n,description:d,timeoutMs:r,inactivityTimeoutMs:c,onOutput:g?S=>{g&&w?.push(S.stream,S.text)}:void 0,onCompleted:e.emitCompleted});if(s===0)return e.manager.disableInactivityTimeout(i,h),await e.manager.markBackgrounded(i,h).catch(()=>{}),f(),{ok:!0,tool:"Bash",command:o,backgrounded:!0,taskId:h,status:"backgrounded",reason:"immediate",message:`Command moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, list_background_commands to view all, or kill_background_command(task_id="${h}") to stop.`};const y=s===-1?0:Math.max(0,Math.floor(s*1e3)),E=[k];let v;s!==-1&&E.push(new Promise(S=>{v=setTimeout(()=>S("__timeout__"),y),v.unref?.()})),m&&E.push(m.then(()=>"__settle__"));try{const S=await Promise.race(E);if(S==="__settle__")return e.manager.disableInactivityTimeout(i,h),await e.manager.markBackgrounded(i,h).catch(()=>{}),{ok:!0,tool:"Bash",command:o,backgrounded:!0,taskId:h,status:"backgrounded",reason:"watchdog_timeout",waitedMs:y,message:`Command still running after watchdog timeout; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, or kill_background_command(task_id="${h}") to stop.`};if(S==="__timeout__")return e.manager.disableInactivityTimeout(i,h),await e.manager.markBackgrounded(i,h).catch(()=>{}),{ok:!0,tool:"Bash",command:o,backgrounded:!0,taskId:h,status:"backgrounded",reason:"timeout",waitedMs:y,message:`Command still running after ${s}s; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, or read_background_output(task_id="${h}", mode="tail") / kill_background_command(task_id="${h}") to inspect.`};const _=S;let P=_.stdout,x=_.stderr,b=_.outputSpilled,$=_.outputFilePath;if(!b&&et(`${_.stdout}${_.stderr}`)>bt){const T=await Et(i,"cmd","out",_t({commandText:o,commandFilePath:_.commandFilePath,commandLineCount:_.commandLineCount,cwd:n,startedAt:new Date(Date.now()-_.durationMs),endedAt:new Date,exitCode:_.exitCode,signal:_.signal??null,timedOut:_.timedOut,inactivityTimedOut:_.inactivityTimedOut??!1,stdout:P,stderr:x}));P=kt(R(T),et(`${_.stdout}${_.stderr}`)),b=!0,$=R(T)}return{ok:_.exitCode===0&&!_.timedOut,tool:"Bash",command:o,backgrounded:!1,taskId:h,exitCode:_.exitCode,signal:_.signal,timedOut:_.timedOut,inactivityTimedOut:_.inactivityTimedOut,timeoutMs:_.timeoutMs,inactivityTimeoutMs:_.inactivityTimeoutMs,durationMs:_.durationMs,outputSpilled:b,outputFilePath:$,outputLineCount:_.outputLineCount,commandFilePath:_.commandFilePath,commandLineCount:_.commandLineCount,stdout:P,stderr:x}}finally{v&&clearTimeout(v),f()}}catch(h){if(h?.code==="too_many_tasks"||h?.code==="global_limit_reached")return{ok:!1,tool:"Bash",command:o,backgrounded:!1,status:"not_executed",error:h.code,message:`Command was NOT executed: ${h.message}. Wait for some background tasks to finish/cleanup (per-agent limit 16, global 256), or reduce concurrent background commands, then retry.`};throw h}}function ye(t){return{kind:"builtin",toolName:"grep",exposedName:"grep",concurrencyMode:"read",resourceScope:"workspace",resourceKeys:()=>[{key:`workspace:${u.resolve(String(t||"").trim()).replace(/\\/g,"/")}`,access:"read"}],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.). You may issue several grep/read_file calls in ONE message; independent searches run in parallel.",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(e){const o=O(e,["pattern"],"pattern"),i=L(e,["path"])||".",a=L(e,["include"]),n=L(e,["exclude"]),r=e.case_insensitive===!0,c=(()=>{const b=e.output_mode;return b==="files_with_matches"||b==="count"?b:"content"})(),s=j(e,["max_results"],zt),d=r?"i":"";let l;try{l=new RegExp(o,d)}catch(b){throw new Error(`Invalid regex pattern: ${b instanceof Error?b.message:String(b)}`)}const m=a?ft(a):null,w=n?ft(n):null,g=await ut(t,i),f=A(g.workspaceRoot,await p.realpath(g.targetPath)),h=await p.stat(f);let k;if(h.isFile())k=[f];else if(h.isDirectory())k=await ne(f,m,w,Ut);else throw new Error(`Path is neither a file nor a directory: ${f}`);const y=[],E=[],v=[];let S=0,_=0,P=!1;for(const b of k){if(c==="content"&&y.length>=s){P=!0;break}const $={regex:l,maxResults:c==="content"?s-y.length:Number.MAX_SAFE_INTEGER,collectContent:c==="content",stopOnFirstMatch:c==="files_with_matches"},T=await re(b,g.workspaceRoot,$);if(T.count>0){const V=u.relative(g.workspaceRoot,b).replace(/\\/g,"/");if(v.push(V),S+=T.count,c==="content"){for(const J of T.matches){const Z=Buffer.byteLength(J.content,"utf-8")+64;if(_+Z>Dt){P=!0;break}_+=Z,y.push(J)}if(P)break;if(y.length>=s){P=!0;break}}else c==="count"&&E.push({file:V,count:T.count})}}const x={ok:!0,tool:"grep",pattern:o,searchPath:R(g.targetPath),outputMode:c,filesSearched:k.length,filesMatched:v.length,totalMatches:S,truncated:P};return c==="content"?x.matches=y:c==="files_with_matches"?x.matches=v:x.matches=E,x}}}function Le(t,e,o,i,a,n){const r=e||"unknown",c=[pe(t,a,n),de(t,o,a,n),he(t,o,a,n),fe(t,o,a),ye(t),ge(t,r,i,a),vt(t)];return i&&c.push(Pt(r,i),St(r,i),Mt(r,i)),c}export{Ie as DEFAULT_TOOL_PERMISSION_POLICY,me as EDIT_FAILURE_SNAPSHOT_MAX_BYTES,Yt as buildFileResourceKey,Le as createBuiltInFileTools,A as ensureInsideWorkspace,q as fileResourceClaim,Oe as getEditFailureSnapshot,ut as resolveWorkspaceTarget};
|
|
3
|
+
`+E:E;return{workspaceRoot:a.workspaceRoot,targetPath:r,content:$,isBinary:!1,truncated:f||h,totalLines:h?-1:w,shownFromLine:_,shownToLine:v,mtimeMs:Math.trunc(c.mtimeMs),sizeBytes:c.size}}finally{d.close(),s.destroy()}}function Y(t){return/^https?:\/\//i.test(t)}function ee(t){try{const e=new URL(t);return`${e.origin}${e.pathname}`}catch{return"(invalid-url)"}}function L(t,e){if(!Ct)return;const i=e?` ${JSON.stringify(e)}`:"";console.error(`[acode:image] ${t}${i}`)}function oe(t){return!t||t.startsWith("image/")||t==="application/octet-stream"||t==="binary/octet-stream"?!1:t.startsWith("text/")||t==="application/json"||t.endsWith("+json")||t==="application/xml"||t.endsWith("+xml")}async function ie(t){let e;try{e=new URL(t)}catch{throw new Error(`Invalid image URL: ${t}`)}if(e.protocol!=="http:"&&e.protocol!=="https:")throw new Error(`Unsupported image URL protocol: ${e.protocol} (only http/https)`);const i=ee(t),o=Date.now();L("url fetch start",{url:i,timeoutMs:ct});let n;try{n=await fetch(t,{headers:Nt,signal:AbortSignal.timeout(ct)})}catch(m){const w=m instanceof Error?m.message:String(m);throw L("url fetch failed",{url:i,reason:w,elapsedMs:Date.now()-o}),new Error(`Image URL fetch failed: ${t} (${w})`)}if(!n.ok)throw L("url fetch failed",{url:i,status:n.status,elapsedMs:Date.now()-o}),new Error(`Image URL fetch failed: ${t} (HTTP ${n.status})`);const a=(n.headers.get("content-type")||"").split(";")[0]?.trim().toLowerCase()||"";if(oe(a))throw L("url fetch rejected: non-image content-type",{url:i,contentType:a,elapsedMs:Date.now()-o}),new Error(`URL did not return an image (content-type: ${a}): ${t}`);const r=Number(n.headers.get("content-length")||"");if(L("url fetch response",{url:i,status:n.status,contentType:a||"(none)",contentLength:Number.isFinite(r)?r:null,elapsedMs:Date.now()-o}),Number.isFinite(r)&&r>F)throw L("url fetch rejected: too large",{url:i,contentLength:r,elapsedMs:Date.now()-o}),new Error(`Image too large to fetch: ${t} (${x(r)} > ${x(F)})`);const c=n.body;if(!c){const m=await n.arrayBuffer();if(m.byteLength>F)throw L("url fetch rejected: too large",{url:i,bytes:m.byteLength,elapsedMs:Date.now()-o}),new Error(`Image too large to fetch: ${t} (${x(m.byteLength)} > ${x(F)})`);return L("url fetch done",{url:i,bytes:m.byteLength,elapsedMs:Date.now()-o}),{buffer:Buffer.from(m),contentType:a}}const s=c.getReader(),d=[];let l=0;for(;;){const{done:m,value:w}=await s.read();if(m)break;if(w){if(l+=w.byteLength,l>F)throw s.cancel().catch(()=>{}),L("url fetch rejected: too large",{url:i,bytes:l,elapsedMs:Date.now()-o}),new Error(`Image too large to fetch: ${t} (${x(l)} > ${x(F)})`);d.push(Buffer.from(w))}}return L("url fetch done",{url:i,bytes:l,elapsedMs:Date.now()-o}),{buffer:Buffer.concat(d,l),contentType:a}}async function ne(t,e,i,o="auto",n){let a,r,c;if(Y(e)){const b=await ie(e);a=b.buffer,c=b.contentType,r=e}else{const M=(await dt(t,e,i)).targetPath,T=await p.stat(M);if(!T.isFile())throw new Error(`Path is not a file: ${M}`);if(T.size>st)throw new Error(`Image file too large to read: ${R(M)} (${x(T.size)} > ${x(st)}). Downscale it first.`);a=await p.readFile(M),r=R(M),n?.tracker&&n.tracker.record(U(M),z(T,null,!0))}if(a.length===0)throw new Error(`Image file is empty: ${r}`);const s=Mt(a);if(!s)throw L("image decode failed",{path:r,bytes:a.length,contentType:c||"(local file)"}),new Error(c!==void 0?`URL did not return a supported image (content-type: ${c||"unknown"}, unrecognized format): ${r}`:`Unsupported or corrupt image (unrecognized format): ${r}`);const d=Tt(a);let l=s,m=a,w=d?.width,g=d?.height,f=null;if(o!==!1){const b=await Rt(a,s,{normalMaxBytes:Bt,clearMaxBytes:Ft});b?.compressed&&(m=b.buffer,l=b.mediaType,w=b.width,g=b.height,f={level:b.level,originalSize:b.originalSize})}let h=!1;const k=await $t(m,l,{maxWidth:j,maxHeight:j,maxBytes:G});if(k)m=k.buffer,l=k.mediaType,w=k.width,g=k.height,h=k.changed;else if(m.length>G)throw new Error(`Image too large to inline: ${r} (${x(m.length)} > ${x(G)}). Downscale it first, e.g. via Bash: magick "${e}" -resize ${j}x${j} "${e}". Installing sharp enables automatic downscaling.`);const _=w&&g?`${w}x${g}`:"unknown dimensions",v=h&&d&&d.width!==w?` (resized from ${d.width}x${d.height})`:h?" (resized)":"",E=f!==null?f.level:null,$=f!==null?Math.max(1,Math.round((1-m.length/f.originalSize)*100)):0,P=f!==null?`${x(m.length)} (compressed from ${x(f.originalSize)}, saved ${$}%, ${E} level)`:x(m.length),y=`[Image: ${r}, ${_}${v}, ${P}]`,S=Y(e)?u.basename(new URL(e).pathname)||"image":u.basename(r);return{ok:!0,filePath:r,content:y,isBinary:!1,image:{mediaType:nt(l),width:w??null,height:g??null,originalWidth:d?.width??null,originalHeight:d?.height??null,resized:h,size:m.length,compressed:f!==null,compressionLevel:E,compressionRatio:f!==null?Math.round((1-m.length/f.originalSize)*100)/100:null,originalSize:a.length},attachments:[{type:"image",name:S,mediaType:nt(l),base64Data:m.toString("base64")}]}}function ft(t){let e="",i=0;for(;i<t.length;){const o=t[i];if(o==="*")e+="[^/]*";else if(o==="?")e+="[^/]";else if(o==="{"){const n=t.indexOf("}",i);if(n>=0){const a=t.slice(i+1,n);e+=`(${a.split(",").join("|")})`,i=n}else e+="\\{"}else if(o==="["){const n=t.indexOf("]",i);n>=0?(e+=t.slice(i,n+1),i=n):e+="\\["}else"+^$.()|\\".includes(o)?e+="\\"+o:e+=o;i++}return new RegExp(`^${e}$`)}async function ae(t,e,i,o){const n=[];async function a(r){if(n.length>=o)return;const c=await p.readdir(r,{withFileTypes:!0});for(const s of c){if(n.length>=o)return;if(s.name.startsWith(".")&&s.isDirectory())continue;const d=u.join(r,s.name);if(s.isDirectory()){if(jt.has(s.name))continue;await a(d)}else if(s.isFile()){if(e&&!e.test(s.name)||i&&i.test(s.name))continue;n.push(d)}}}return await a(t),n}async function re(t,e,i){if((await p.stat(t)).size>Wt)return{matches:[],count:0};if(await lt(t))return{matches:[],count:0};const n=u.relative(e,t).replace(/\\/g,"/"),a=[];let r=0;const c=Q(t,{encoding:"utf-8"}),s=tt.createInterface({input:c,crlfDelay:1/0});try{let d=0;for await(const l of s)if(d++,i.regex.test(l)){if(r++,i.collectContent){let m=l;if(m.length>B&&(m=m.slice(0,B)+`... (line truncated to ${B} chars)`),a.push({file:n,line:d,content:m}),a.length>=i.maxResults)break}if(i.stopOnFirstMatch)break}}finally{s.close(),c.destroy()}return{matches:a,count:r}}const se=12*60*6e4,ce=3*6e4,le=6e4,ue=10*6e4;function de(t,e,i,o){return{kind:"builtin",toolName:"write_file",exposedName:"write_file",concurrencyMode:"write",resourceKeys:n=>{const a=X(n);return a?[K(t,a,"write")]:[]},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(n){const a=I(n,["file_path","filePath"],"file_path"),r=I(n,["content"],"content"),c=O(n,["reason"]),s=await V(t,a,"write",i);await C(i,"fileModification",{operation:"write",path:s.targetPath});const d=await H(s.targetPath),l=d?await p.readFile(s.targetPath,"utf-8").catch(()=>null):null,m=d?await p.realpath(s.targetPath).catch(()=>s.targetPath):s.targetPath;let w=null;if(o?.enabled&&d){const f=await p.stat(m);w=D(f.mtimeMs,f.size);const h=ot(o.tracker,U(m),f,l);if(!h.ok)throw new Error(h.reason)}if(o?.enabled&&w){const f=await p.stat(m).catch(()=>null);if(!f||D(f.mtimeMs,f.size)!==w)throw new Error(it)}const g=await mt(s,r);if(await ht(o,g.targetPath,r),e&&!g.outsideWorkspace){const f=u.relative(g.workspaceRoot,g.targetPath).replace(/\\/g,"/");await e({filePath:f,operation:d?"UPDATE":"CREATE",beforeContent:l,afterContent:r,reason:c})}return{ok:!0,filePath:R(g.targetPath),bytes:g.bytes,createdParentDirectory:g.createdParentDirectory}}}}const me=15*1024;function Ie(t){if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"editFailureSnapshot");if(typeof e=="object"&&e!==null&&typeof Reflect.get(e,"content")=="string")return e}function he(t,e,i,o){return{kind:"builtin",toolName:"edit_file",exposedName:"edit_file",concurrencyMode:"write",resourceKeys:n=>{const a=X(n);return a?[K(t,a,"write")]:[]},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(n){const a=I(n,["file_path","filePath"],"file_path"),r=I(n,["old_string","oldString"],"old_string"),c=I(n,["new_string","newString"],"new_string"),s=O(n,["reason"]);if(!r)throw new Error("old_string must not be empty");const d=await V(t,a,"edit",i);await C(i,"fileModification",{operation:"edit",path:d.targetPath});const l=d.outsideWorkspace?await Qt(d.targetPath):await Zt(t,a),m=U(l.targetPath);let w;try{if(o?.enabled){const h=ot(o.tracker,m,l.stat,l.content);if(!h.ok)throw new Error(h.reason)}const f=qt(l.content,r);if(f===0)throw new Error("old_string was not found in file");if(f>1)throw new Error(`old_string occurs ${f} times; provide a unique match`);if(w=l.content.replace(r,c),o?.enabled){const h=await p.stat(l.targetPath).catch(()=>null);if(!h||D(h.mtimeMs,h.size)!==D(l.stat.mtimeMs,l.stat.size))throw new Error(it)}}catch(f){throw f instanceof Error&&l.stat.size<=me&&(o?.tracker.record(m,z(l.stat,l.content,!1)),f.editFailureSnapshot={path:R(l.targetPath),bytes:l.stat.size,content:l.content}),f}const g=await mt(d,w);if(await ht(o,g.targetPath,w),e&&!g.outsideWorkspace){const f=u.relative(g.workspaceRoot,g.targetPath).replace(/\\/g,"/");await e({filePath:f,operation:"UPDATE",beforeContent:l.content,afterContent:w,reason:s})}return{ok:!0,filePath:R(g.targetPath),bytes:g.bytes}}}}function fe(t,e,i){return{kind:"builtin",toolName:"delete_file",exposedName:"delete_file",concurrencyMode:"write",resourceKeys:o=>{const n=X(o);return n?[K(t,n,"write")]:[]},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(o){const n=I(o,["file_path","filePath"],"file_path"),a=O(o,["reason"]),r=await V(t,n,"delete",i);await C(i,"fileModification",{operation:"delete",path:r.targetPath});const c=r.outsideWorkspace?await p.realpath(r.targetPath).catch(()=>r.targetPath):A(r.workspaceRoot,await p.realpath(r.targetPath).catch(()=>r.targetPath)),s=await p.stat(c).catch(()=>null);if(!s)throw new Error(`File does not exist: ${c}`);if(!s.isFile())throw new Error(`Path is not a file: ${c}`);const d=await p.readFile(c,"utf-8").catch(()=>null);if(await p.rm(c,{force:!0}),e&&!r.outsideWorkspace){const l=u.relative(r.workspaceRoot,c).replace(/\\/g,"/");await e({filePath:l,operation:"DELETE",beforeContent:d,afterContent:null,reason:a})}return{ok:!0,filePath:R(c),deleted:!0}}}}function pe(t,e,i){return{kind:"builtin",toolName:"read_file",exposedName:"read_file",concurrencyMode:"read",resourceKeys:o=>{const n=X(o);return!n||Y(n)?[]:[K(t,n,"read")]},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. Images (.png/.jpg/.jpeg/.gif/.webp) and http(s) image URLs are also supported: the image is inlined into the conversation so you can see it (requires a model with image input). When an image is already available at an http(s) URL (including signed URLs with query strings), pass the URL directly as file_path \u2014 the runtime fetches it in memory and never writes it to disk; do NOT download it with Bash/curl first. offset/limit are ignored for images. By default (compress='auto') images are compressed before inlining using a tiered ladder that judges by the compressed result, not the original size: lossless first (pixel-exact, dimensions preserved), then high-quality lossy downscaling only if the lossless result is still over 50 KB (normal level), then more aggressive downscaling if still over 100 KB (clear level). When compression applies, the result reports the compression level, compressed size, and compression ratio (e.g. saved 80%). Pass compress=true to also attempt compression, or compress=false to disable it (images exceeding the hard 2000px / 3.75MB inline limits are still auto-downscaled when sharp is available). You may issue several read_file calls in ONE message; independent reads run in parallel.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Workspace-relative file path to read. For images, also accepts an http(s) URL (including signed URLs with query strings), which is fetched in memory without writing to disk."},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."},compress:{anyOf:[{type:"boolean"},{type:"string",enum:["auto"]}],description:'Image compression mode (ignored for text files). "auto" (default): compress with a tiered ladder driven by the compressed result \u2014 lossless, then high-quality downscale if still >50 KB, then more aggressive if still >100 KB. true: same as auto. false: skip tiered compression entirely (hard inline limits still apply).'}},required:["file_path"],additionalProperties:!1},async execute(o){const n=I(o,["file_path","filePath"],"file_path"),a=u.extname(n).toLowerCase().slice(1);if(Y(n)||At.has(a))return await ne(t,n,e,Xt(o),i);const r=q(o,["offset"],1),c=q(o,["limit"],Lt),s=await te(t,n,r,c,e);if(i?.tracker){let d=null,l=r!==1||s.truncated||s.isBinary;l||(d=await p.readFile(s.targetPath,"utf-8").catch(()=>null),(d==null||d.length>at)&&(d=null,l=!0)),i.tracker.record(U(s.targetPath),z({mtimeMs:s.mtimeMs,size:s.sizeBytes},d,l))}return{ok:!0,filePath:R(s.targetPath),content:s.content,isBinary:s.isBinary,truncated:s.truncated,totalLines:s.totalLines,shownFromLine:s.shownFromLine,shownToLine:s.shownToLine}}}}function ge(t,e,i,o){return{kind:"builtin",toolName:"Bash",exposedName:"Bash",concurrencyMode:"write",resourceScope:"workspace",resourceKeys:()=>[{key:`workspace:${u.resolve(String(t||"").trim()).replace(/\\/g,"/")}`,access:"write"}],description:"Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded total timeout (timeout_ms) measured from command start \u2014 by default 3 minutes, after which the process is force-terminated; timeout_ms can raise this (up to 12 hours) or set 0 to disable. The tool returns stdout, stderr, exit code, and (if converted to background) a task_id. By default (wait_seconds omitted or -1) the command runs in the FOREGROUND and streams output in real time; if the 5-minute adapter watchdog fires while it is still running, the command is moved to the BACKGROUND and the tool returns a task_id plus an explanation (backgrounded=true, reason=watchdog_timeout). The background command stays bound by the same total timeout (timeout_ms, default 3 minutes, still measured from command start) and can be managed via await_complete / list_background_commands / read_background_output / kill_background_command. For immediate backgrounding use wait_seconds=0, or wait_seconds=n>0 to wait n seconds then background. Every result includes remainingBackgroundCommandSlots and backgroundCommandLimit: a per-agent cap on concurrent background commands (running and recently finished ones both occupy slots). When the cap is reached the command fails as not_executed \u2014 stop existing background commands via kill_background_command to free slots, then retry.",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 total command timeout in milliseconds, measured from command start. Defaults to 180000 (3 minutes) and is capped at 43200000 (12 hours). Set 0 to disable the timeout entirely (run until finished or killed)."},inactivity_timeout_ms:{type:"number",description:"Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000."},wait_seconds:{type:"number",description:"How to wait for the command to finish. Omitted values (or -1, the default): run in the FOREGROUND until completion; if the 5-minute adapter watchdog fires first, the command is moved to the background and a task_id is returned (backgrounded=true, reason=watchdog_timeout). 0: spawn and immediately execute in the background, return task_id. n>0: wait up to n seconds; if still running, move to the background and return task_id. The command remains bound by the total timeout (timeout_ms, default 3 minutes, measured from command start) whether foreground or background; inactivity_timeout_ms is ignored once backgrounded.",minimum:-1},description:{type:"string",description:"Short reason for running this command (what it does and why it is needed), shown to the user in the background commands panel. Always provide a concise reason, e.g. 'Start local dev server for manual verification'."}},required:["command"],additionalProperties:!1},async execute(n,a){const r=I(n,["command"],"command").trim();if(!r)throw new Error("command must not be empty");const c=Kt(n,["timeout_ms","timeoutMs"],ce),s=c===0?0:Math.min(c,se),d=q(n,["inactivity_timeout_ms","inactivityTimeoutMs"],le),l=Math.min(d,ue),m=O(n,["cwd"]),w=O(n,["description"]),g=await Gt(t,m);await C(o,"commandExecution",{command:r,cwd:R(g.cwd)});const f=n.wait_seconds,k=typeof f=="number"&&Number.isFinite(f)?f:-1;if(i)return await we({deps:i,command:r,agentId:e,workspaceRoot:g.workspaceRoot,cwd:g.cwd,timeoutMs:s,inactivityTimeoutMs:l,waitSeconds:k,description:w,onOutput:a?.onOutput,settleSignal:a?.settleSignal});const _=await wt({commandText:r,agentId:e,workspaceRoot:g.workspaceRoot,cwd:g.cwd,timeoutMs:s,inactivityTimeoutMs:l,onOutput:a?.onOutput,watchdogSignal:a?.settleSignal}),v={ok:_.exitCode===0&&!_.timedOut,tool:"Bash",command:r,cwd:R(g.cwd),exitCode:_.exitCode,signal:_.signal,timedOut:_.timedOut,inactivityTimedOut:_.inactivityTimedOut,watchdogInterrupted:_.watchdogInterrupted,timeoutMs:s,inactivityTimeoutMs:l,durationMs:_.durationMs,outputSpilled:_.outputSpilled,outputFilePath:_.outputFilePath,outputLineCount:_.outputLineCount,commandFilePath:_.commandFilePath,commandLineCount:_.commandLineCount,stdout:_.stdout,stderr:_.stderr};return _.watchdogInterrupted?{...v,message:"Command was interrupted by the adapter watchdog after ~5 minutes of synchronous (foreground) execution. Only the partial output produced so far is returned above"+(_.outputFilePath?` (full output spilled to "${_.outputFilePath}"; read it if you need more)`:"")+". For long-running commands, re-run with wait_seconds=0 (or n>0) to execute in the background, then manage via await_complete / list_background_commands / read_background_output / kill_background_command."}:v}}}async function we(t){const{deps:e,command:i,agentId:o,workspaceRoot:n,cwd:a,timeoutMs:r,inactivityTimeoutMs:c,waitSeconds:s,description:d,onOutput:l,settleSignal:m}=t,w=l?yt({cwd:a,commandLineCount:0,onOutput:l}):void 0;let g=!!w;const f=()=>{g&&(g=!1,w?.close())};try{const{taskId:h,completion:k}=e.manager.startCommand({commandText:i,agentId:o,workspaceRoot:n,cwd:a,description:d,timeoutMs:r,inactivityTimeoutMs:c,onOutput:g?P=>{g&&w?.push(P.stream,P.text)}:void 0,onCompleted:e.emitCompleted}),_=e.manager.taskCapacity(o);if(s===0)return e.manager.disableInactivityTimeout(o,h),await e.manager.markBackgrounded(o,h).catch(()=>{}),f(),{ok:!0,tool:"Bash",command:i,backgrounded:!0,taskId:h,status:"backgrounded",reason:"immediate",remainingBackgroundCommandSlots:_.remaining,backgroundCommandLimit:_.limit,message:`Command moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, list_background_commands to view all, or kill_background_command(task_id="${h}") to stop.`};const v=s===-1?0:Math.max(0,Math.floor(s*1e3)),E=[k];let $;s!==-1&&E.push(new Promise(P=>{$=setTimeout(()=>P("__timeout__"),v),$.unref?.()})),m&&E.push(m.then(()=>"__settle__"));try{const P=await Promise.race(E);if(P==="__settle__")return e.manager.disableInactivityTimeout(o,h),await e.manager.markBackgrounded(o,h).catch(()=>{}),{ok:!0,tool:"Bash",command:i,backgrounded:!0,taskId:h,status:"backgrounded",reason:"watchdog_timeout",waitedMs:v,remainingBackgroundCommandSlots:_.remaining,backgroundCommandLimit:_.limit,message:`Command still running after watchdog timeout; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, read_background_output(task_id="${h}", mode="tail") to read output, or kill_background_command(task_id="${h}") to stop.`};if(P==="__timeout__")return e.manager.disableInactivityTimeout(o,h),await e.manager.markBackgrounded(o,h).catch(()=>{}),{ok:!0,tool:"Bash",command:i,backgrounded:!0,taskId:h,status:"backgrounded",reason:"timeout",waitedMs:v,remainingBackgroundCommandSlots:_.remaining,backgroundCommandLimit:_.limit,message:`Command still running after ${s}s; moved to background. Use await_complete(bg_cmd_ids=["${h}"]) to block until it finishes, or read_background_output(task_id="${h}", mode="tail") / kill_background_command(task_id="${h}") to inspect.`};const y=P;let S=y.stdout,b=y.stderr,M=y.outputSpilled,T=y.outputFilePath;if(!M&&et(`${y.stdout}${y.stderr}`)>bt){const N=await vt(o,"cmd","out",_t({commandText:i,commandFilePath:y.commandFilePath,commandLineCount:y.commandLineCount,cwd:a,startedAt:new Date(Date.now()-y.durationMs),endedAt:new Date,exitCode:y.exitCode,signal:y.signal??null,timedOut:y.timedOut,inactivityTimedOut:y.inactivityTimedOut??!1,stdout:S,stderr:b}));S=kt(R(N),et(`${y.stdout}${y.stderr}`)),M=!0,T=R(N)}return{ok:y.exitCode===0&&!y.timedOut,tool:"Bash",command:i,backgrounded:!1,taskId:h,remainingBackgroundCommandSlots:_.remaining,backgroundCommandLimit:_.limit,exitCode:y.exitCode,signal:y.signal,timedOut:y.timedOut,inactivityTimedOut:y.inactivityTimedOut,timeoutMs:y.timeoutMs,inactivityTimeoutMs:y.inactivityTimeoutMs,durationMs:y.durationMs,outputSpilled:M,outputFilePath:T,outputLineCount:y.outputLineCount,commandFilePath:y.commandFilePath,commandLineCount:y.commandLineCount,stdout:S,stderr:b}}finally{$&&clearTimeout($),f()}}catch(h){if(h?.code==="too_many_tasks"||h?.code==="global_limit_reached"){const k=e.manager.taskCapacity(o);return{ok:!1,tool:"Bash",command:i,backgrounded:!1,status:"not_executed",error:h.code,remainingBackgroundCommandSlots:k.remaining,backgroundCommandLimit:k.limit,message:`Command was NOT executed: ${h.message} To run new commands, stop existing background commands via kill_background_command(task_id=...) to free slots, then retry.`}}throw h}}function ye(t){return{kind:"builtin",toolName:"grep",exposedName:"grep",concurrencyMode:"read",resourceScope:"workspace",resourceKeys:()=>[{key:`workspace:${u.resolve(String(t||"").trim()).replace(/\\/g,"/")}`,access:"read"}],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.). You may issue several grep/read_file calls in ONE message; independent searches run in parallel.",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(e){const i=I(e,["pattern"],"pattern"),o=O(e,["path"])||".",n=O(e,["include"]),a=O(e,["exclude"]),r=e.case_insensitive===!0,c=(()=>{const b=e.output_mode;return b==="files_with_matches"||b==="count"?b:"content"})(),s=q(e,["max_results"],zt),d=r?"i":"";let l;try{l=new RegExp(i,d)}catch(b){throw new Error(`Invalid regex pattern: ${b instanceof Error?b.message:String(b)}`)}const m=n?ft(n):null,w=a?ft(a):null,g=await ut(t,o),f=A(g.workspaceRoot,await p.realpath(g.targetPath)),h=await p.stat(f);let k;if(h.isFile())k=[f];else if(h.isDirectory())k=await ae(f,m,w,Ut);else throw new Error(`Path is neither a file nor a directory: ${f}`);const _=[],v=[],E=[];let $=0,P=0,y=!1;for(const b of k){if(c==="content"&&_.length>=s){y=!0;break}const M={regex:l,maxResults:c==="content"?s-_.length:Number.MAX_SAFE_INTEGER,collectContent:c==="content",stopOnFirstMatch:c==="files_with_matches"},T=await re(b,g.workspaceRoot,M);if(T.count>0){const N=u.relative(g.workspaceRoot,b).replace(/\\/g,"/");if(E.push(N),$+=T.count,c==="content"){for(const J of T.matches){const Z=Buffer.byteLength(J.content,"utf-8")+64;if(P+Z>Dt){y=!0;break}P+=Z,_.push(J)}if(y)break;if(_.length>=s){y=!0;break}}else c==="count"&&v.push({file:N,count:T.count})}}const S={ok:!0,tool:"grep",pattern:i,searchPath:R(g.targetPath),outputMode:c,filesSearched:k.length,filesMatched:E.length,totalMatches:$,truncated:y};return c==="content"?S.matches=_:c==="files_with_matches"?S.matches=E:S.matches=v,S}}}function Oe(t,e,i,o,n,a){const r=e||"unknown",c=[pe(t,n,a),de(t,i,n,a),he(t,i,n,a),fe(t,i,n),ye(t),ge(t,r,o,n),Et(t)];return o&&c.push(Pt(r,o),St(r,o),xt(r,o)),c}export{Le as DEFAULT_TOOL_PERMISSION_POLICY,me as EDIT_FAILURE_SNAPSHOT_MAX_BYTES,Yt as buildFileResourceKey,Oe as createBuiltInFileTools,A as ensureInsideWorkspace,K as fileResourceClaim,Ie as getEditFailureSnapshot,ut as resolveWorkspaceTarget};
|
|
4
4
|
|