qlogicagent 2.18.10 → 2.18.11
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/dist/agent.js +17 -17
- package/dist/cli.js +1 -1
- package/dist/index.js +247 -247
- package/dist/skills/mcp/astraclaw-native-mcp-server.js +6 -6
- package/dist/types/protocol/wire/chat-types.d.ts +2 -0
- package/dist/types/runtime/execution/streaming-tool-executor.d.ts +2 -0
- package/dist/types/runtime/ports/agent-runtime-ports.d.ts +1 -0
- package/dist/types/skills/portable-tool.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createInterface as Ze}from"node:readline";import{existsSync as Xe,readFileSync as et}from"node:fs";import{readFileSync as Se}from"node:fs";import{randomUUID as Te}from"node:crypto";import{readdirSync as
|
|
3
|
-
`),r=e.match(/^---\n[\s\S]*?^version:\s*(\S+)/m),o=e.match(/^---\n[\s\S]*?^category:\s*(.+)/m),n=e.match(/^---\n[\s\S]*?^author:\s*(.+)/m),i=e.match(/^---\n[\s\S]*?^requires:\s*(.+)/m),s=e.match(/^---\n[\s\S]*?^environments:\s*(.+)/m),a=e.match(/^---\n([\s\S]*?)\n---/)?.[1]??"";return{version:r?.[1],description:fe(a),category:o?.[1]?.trim()||void 0,author:n?.[1]?.trim()||void 0,requiredTools:
|
|
4
|
-
`),r=e.findIndex(s=>/^description:/.test(s));if(r===-1)return;let o=e[r].replace(/^description:[ \t]*/,"").trim(),n=/^[>|][+-]?$/.test(o),i=[];!n&&o&&i.push(o);for(let s=r+1;s<e.length;s++){let a=e[s];if(!/^[ \t]/.test(a))break;let l=a.trim();l&&i.push(l)}if(i.length!==0)return i.join(" ").replace(/^["']|["']$/g,"").trim()||void 0}var M=new Map,b=new Map;function ge(t,e,r,o){let n=e.has(t.name),i=r.has(t.name),s=!ve(t.meta,o);return{name:t.name,filePath:t.filePath,baseDir:t.baseDir,globallyDisabled:n,projectDisabled:i,active:!n&&!i&&!s,systemGenerated:t.systemGenerated,description:t.meta.description,version:t.meta.version,category:t.meta.category,author:t.meta.author,requiredTools:t.meta.requiredTools,environments:t.meta.environments}}async function he(t){let e;try{e=await ie(t,{withFileTypes:!0})}catch{e=[]}let r=[];for(let s of e){if(!s.isDirectory()||s.name.startsWith("."))continue;let a=d(t,s.name),l=d(a,"SKILL.md");try{let y=await le(l);if(!y.isFile())continue;r.push({name:s.name,filePath:l,baseDir:a,mtimeMs:y.mtimeMs,size:y.size})}catch{continue}}r.sort((s,a)=>s.name.localeCompare(a.name));let o=r.map(s=>`${s.name}:${s.mtimeMs}:${s.size}`).join("|"),n=M.get(t);if(n&&n.fingerprint===o)return n.raw;let i=[];for(let s of r){let a;try{a=await ae(s.filePath,"utf8")}catch{continue}i.push({name:s.name,filePath:s.filePath,baseDir:s.baseDir,systemGenerated:me(s.name),mtimeMs:s.mtimeMs,size:s.size,meta:pe(a)})}return M.set(t,{fingerprint:o,raw:i}),be(t,i),i}async function ye(t,e){let r=v(),o=A(),n=t?R(t):new Set,i=b.get(r);return i||(i=he(r).finally(()=>b.delete(r)),b.set(r,i)),(await i).map(a=>ge(a,o,n,e))}async function D(t,e){return(await ye(t,e)).filter(r=>r.active)}function I(t){let e=v(),r=d(e,t),o=d(r,"SKILL.md");try{if(se(o).isFile())return{baseDir:r,filePath:o}}catch{}return null}function E(t){if(!t)return;let e=t.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(r=>r.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function ve(t,e){if(t.environments&&e?.currentEnvironment&&!t.environments.includes(e.currentEnvironment))return!1;if(t.requiredTools){if(!e?.availableToolNames)return!1;let r=new Set(e.availableToolNames);return t.requiredTools.every(o=>r.has(o))}return!0}async function be(t,e){try{let o={version:1,entries:e.map(n=>({name:n.name,size:n.size,mtimeMs:n.mtimeMs,description:n.meta.description,version:n.meta.version,requiredTools:n.meta.requiredTools,environments:n.meta.environments}))};await ce(d(t,de),JSON.stringify(o,null,2),"utf8")}catch{}}var j="astraclaw_capabilities";async function L(t){if(t.tool==="skills_list")return{handled:!0,result:{skills:(await D(t.projectRoot,{availableToolNames:t.availableToolNames,currentEnvironment:t.currentEnvironment??process.platform})).map(we)}};if(t.tool==="skill_view"){let e=typeof t.args.name=="string"?t.args.name.trim():"";if(!e)throw new Error("skill_view requires a skill name");let r=I(e);if(!r)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:Se(r.filePath,"utf8")}}}if(t.tool==="mcp_connectors_list"){let e=t.mcpServerFailures??[],r=e.length>0?{failedServers:e,note:`${e.length} configured MCP server(s) are unavailable this session; their tools are missing from this list.`}:{};if(!t.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance.",...e.length>0?{failedServers:e}:{}}};let o=t.toolCatalog.getToolManifest().filter(n=>n.function.name.startsWith("mcp__")).map(n=>({toolName:n.function.name,server:Pe(n.function.name),name:_e(n.function.name),description:n.function.description,inputSchema:n.function.parameters}));return{handled:!0,result:{tools:o,count:o.length,...r}}}if(t.tool==="mcp_tool_call"){if(!t.toolCatalog)throw new Error("AstraClaw MCP connector catalog is not attached");let e=typeof t.args.toolName=="string"?t.args.toolName.trim():"";if(!e.startsWith("mcp__"))throw new Error("mcp_tool_call requires a connector toolName returned by mcp_connectors_list");let r=t.toolCatalog.findTool(e);if(!r?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let o=ke(t.args.args),n=`astraclaw_mcp_${Te().slice(0,8)}`,i=await r.execute(n,o);return{handled:!0,result:{toolName:e,content:i.content,details:i.details,imageUrls:i.imageUrls}}}return{handled:!1}}function we(t){return{name:t.name,description:t.description,version:t.version,category:t.category,requiredTools:t.requiredTools,environments:t.environments}}function ke(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function Pe(t){let e=t.split("__");return e.length>=3?e[1]??"":""}function _e(t){let e=t.split("__");return e.length>=3?e.slice(2).join("__"):t}import m from"node:path";import{fileURLToPath as Me}from"node:url";import{createRuntime as Ee}from"mcporter";var O=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),Ce=new Set([...O,"agent"]);function $(t){return t?.riskLevel?t.riskLevel:t?.isDangerous?"system":t?.isReadOnly?"read":t?.isEgress?"external_egress":"write"}var S=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let r of e)this.toolPool.set(r.name,r)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let r of e)this.addTool(r)}removeTool(e){return this.toolPool.delete(e)}findTool(e){return this.toolPool.get(e)}hasTool(e){return this.toolPool.has(e)}getToolNames(){return Array.from(this.toolPool.keys())}getToolCount(){return this.toolPool.size}async executeTool(e,r,o,n){let i=this.toolPool.get(e);if(!i)throw new Error(`Tool not found: ${e}`);return i.execute(r,o,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let r of this.toolPool.values()){if(r.isEnabled?.()===!1)continue;let o=$(r);e.push({type:"function",function:{name:r.name,description:r.description,parameters:r.parameters},meta:{category:r.category??"other",displayName:r.displayName??{key:`capability.tool.${r.name}.name`,fallback:r.label},displayDescription:r.displayDescription??{key:`capability.tool.${r.name}.description`,fallback:""},parallelSafe:r.isConcurrencySafe??!1,riskLevel:o,isReadOnly:o==="read",isDangerous:o==="system",isDelete:r.isDelete??!1,isEgress:r.isEgress??o==="external_egress",egressCarriesData:r.egressCarriesData??!1}})}return e}},N=new S;function F(t){N.addTools(t)}function U(t){return N.removeTool(t)}var xe=new Set(["describe","fetch","find","get","inspect","list","query","read","search","stat"]),Ae=new Set(["append","copy","create","delete","edit","move","patch","remove","rename","set","update","upload","write"]);function q(t){let e=t.annotations,r=Re(t.name),o=r.some(l=>Ae.has(l)),n=r.some(l=>xe.has(l)),i=e?.destructiveHint===!0||o,s=e?.readOnlyHint===!0||!i&&n,a=s||e?.idempotentHint===!0&&!i;return{isReadOnly:s,isConcurrencySafe:a,isDestructive:i}}function Re(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var K=12e4,G=1e3,z=6e5;function De(t){if(t===void 0)return K;if(!Number.isInteger(t)||t<G||t>z)throw new RangeError(`MCP callTimeoutMs must be an integer between ${G} and ${z}`);return t}var g=3,Ie=1e3;async function je(t,e){let r=e.sleep??(i=>new Promise(s=>setTimeout(s,i))),o=Math.max(1,e.attempts),n;for(let i=1;i<=o;i++)try{return await t()}catch(s){n=s,i<o&&(e.onRetry?.(i,s),await r(e.baseDelayMs*i))}throw n}var h=class{runtime=null;definitions=[];toolsByServer=new Map;injectedNames=new Set;log;workspaceRoot;toolCatalog;serverToolFilters=new Map;serverOAuth=new Map;serverCallTimeouts=new Map;failedServers=new Map;constructor(e){this.log=e.log??{info:()=>{},warn:()=>{}},this.workspaceRoot=e.workspaceRoot?m.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(r=>!r.disabled).filter(r=>{let o=Ve(r);for(let n of o)this.log.warn(`[mcp] rejected server "${r.name}": ${n.message}`);return r.tools&&this.serverToolFilters.set(r.name,r.tools),r.oauth===!0&&this.serverOAuth.set(r.name,!0),this.serverCallTimeouts.set(r.name,De(r.callTimeoutMs)),o.length===0}).map(r=>this.toServerDefinition(r)).filter(r=>r!==null)}markServerFailed(e,r,o){this.failedServers.set(e,{name:e,phase:r,error:o}),console.error(`[mcp] server "${e}" unavailable this session (${r}): ${o}`)}toServerDefinition(e){if((e.type??(e.url?"http":"stdio"))==="http"){if(!e.url)return this.log.warn(`[mcp] server "${e.name}" is type "http" but has no url, skipping`),this.markServerFailed(e.name,"config",'type "http" but no url'),null;let o;try{o=new URL(e.url)}catch{return this.log.warn(`[mcp] server "${e.name}" has an invalid url "${e.url}", skipping`),this.markServerFailed(e.name,"config",`invalid url "${e.url}"`),null}return{name:e.name,command:{kind:"http",url:o,headers:e.headers}}}return e.command?{name:e.name,command:{kind:"stdio",command:e.command,args:e.args??[],cwd:e.cwd??this.workspaceRoot??process.cwd()},env:e.env}:(this.log.warn(`[mcp] server "${e.name}" is type "stdio" but has no command, skipping`),this.markServerFailed(e.name,"config",'type "stdio" but no command'),null)}async connectAll(){this.definitions.length!==0&&(this.runtime=await Ee({servers:this.definitions,clientInfo:{name:"qlogicagent",version:"1.0.0"},logger:{info:e=>this.log.info(`[mcp] ${e}`),warn:e=>this.log.warn(`[mcp] ${e}`),error:(e,r)=>this.log.warn(`[mcp] ${e}${r?` (${r instanceof Error?r.message:String(r)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let r=await je(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:g,baseDelayMs:Ie,onRetry:(n,i)=>this.log.warn(`[mcp] connect attempt ${n}/${g} for ${e.name} failed, retrying: ${i instanceof Error?i.message:i}`)}),o=H(r,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,o),this.failedServers.delete(e.name),this.log.info(`[mcp] connected to ${e.name} (${o.length}/${r.length} tools)`)}catch(r){this.log.warn(`[mcp] failed to connect to ${e.name} after ${g} attempts: ${r instanceof Error?r.message:r}`),this.markServerFailed(e.name,"connect",`${r instanceof Error?r.message:String(r)} (after ${g} attempts)`)}})))}injectTools(){for(let[e,r]of this.toolsByServer){let o=this.injectPortableTools(e,r);this.log.info(`[mcp] injected ${o.length} tools from ${e}`)}}async refreshServerTools(e){if(!this.runtime)throw new Error("MCP runtime is not connected");let r=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),o=H(r,this.serverToolFilters.get(e));return this.toolsByServer.set(e,o),this.failedServers.delete(e),this.retractServerTools(e),this.injectPortableTools(e,o),o.length}getConnectedServers(){return Array.from(this.toolsByServer.keys())}getFailedServers(){return Array.from(this.failedServers.values())}getToolCount(){let e=0;for(let r of this.toolsByServer.values())e+=r.length;return e}async disconnectAll(){for(let r of this.injectedNames)this.removeRegisteredTool(r);this.injectedNames.clear(),this.toolsByServer.clear(),this.failedServers.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,r){let o=q({name:r.name,description:r.description});return{name:`mcp__${W(e)}__${r.name}`,label:`[${e}] ${r.name}`,category:"mcp",description:r.description??`MCP tool from ${e}`,parameters:r.inputSchema??{type:"object",properties:{}},isConcurrencySafe:o.isConcurrencySafe,isReadOnly:o.isReadOnly,isDestructive:o.isDestructive,searchHint:`mcp ${e} ${r.name.replace(/[_-]+/g," ")}`,execute:async(n,i)=>{let s=this.runtime;if(!s)return{content:[{type:"text",text:`MCP server "${e}" is not connected.`}],details:{error:"mcp_not_connected"}};try{let a=await s.callTool(e,r.name,{args:i,timeoutMs:this.serverCallTimeouts.get(e)??K});return Le(a)}catch(a){let l=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`MCP tool error: ${l}`}],details:{error:l}}}}}}wrapToolsForWorkspaceBoundary(e){return this.workspaceRoot?e.map(r=>{if(!r.execute)return r;let o=r.execute;return{...r,execute:async(n,i,s)=>{let a=$e(r.name,i,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:o(n,i,s)}}}):e}injectPortableTools(e,r){let o=this.wrapToolsForWorkspaceBoundary(r.map(n=>this.toPortableTool(e,n)));this.registerTools(o);for(let n of o)this.injectedNames.add(n.name);return o}retractServerTools(e){let r=`mcp__${W(e)}__`;for(let o of[...this.injectedNames])o.startsWith(r)&&(this.removeRegisteredTool(o),this.injectedNames.delete(o))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):F(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??U(e)}};function Le(t){let e=t&&typeof t=="object"?t:{},o=(Array.isArray(e.content)?e.content:[]).filter(a=>!!a&&typeof a=="object"),n=o.filter(a=>a.type==="text"&&typeof a.text=="string").map(a=>a.text),i=o.filter(a=>!(a.type==="text"&&typeof a.text=="string")).map(a=>typeof a.type=="string"&&a.type?a.type:"unknown"),s=n.join(`
|
|
2
|
+
import{createInterface as Ze}from"node:readline";import{existsSync as Xe,readFileSync as et}from"node:fs";import{readFileSync as Se}from"node:fs";import{randomUUID as Te}from"node:crypto";import{readdirSync as Mt,readFileSync as Rt,statSync as se,writeFileSync as Et}from"node:fs";import{readdir as ie,readFile as ae,stat as le,writeFile as ce}from"node:fs/promises";import{join as d}from"node:path";import{homedir as X}from"node:os";import{join as u}from"node:path";import{existsSync as yt}from"node:fs";import{createHash as ct}from"node:crypto";import{resolve as dt}from"node:path";var _=".qlogicagent";function p(){return process.env.QLOGICAGENT_HOME||u(X(),_)}function v(){return u(p(),"skills")}function C(){return u(p(),"mcp.json")}function ee(t){if(!t)throw new Error("getProjectAgentDir: cwd is required (must not fall back to process.cwd())");return u(t,_)}function x(t){return u(ee(t),"skills-disabled.json")}import{readFileSync as te,writeFileSync as Tt,mkdirSync as wt,renameSync as kt}from"node:fs";import{dirname as _t,join as re}from"node:path";function f(){return{version:1,disabled:[]}}function oe(t){let e;try{e=te(t,"utf8")}catch(n){return n.code==="ENOENT"?{list:f(),corrupt:!1}:(console.error(`[project-skill-manifest] Cannot read skill mute list at ${t} (${n instanceof Error?n.message:String(n)}); treating it as empty for reads \u2014 muted skills may reactivate.`),{list:f(),corrupt:!0})}let r;try{r=JSON.parse(e)}catch(n){return console.error(`[project-skill-manifest] Skill mute list at ${t} is corrupted (${n instanceof Error?n.message:String(n)}); treating it as empty for reads \u2014 muted skills may reactivate.`),{list:f(),corrupt:!0}}return!r||typeof r!="object"||!Array.isArray(r.disabled)?(console.error(`[project-skill-manifest] Skill mute list at ${t} has an unexpected shape; treating it as empty for reads \u2014 muted skills may reactivate.`),{list:f(),corrupt:!0}):{list:{version:1,disabled:[...new Set(r.disabled.filter(n=>typeof n=="string"&&n.trim().length>0))]},corrupt:!1}}function A(t){return oe(t).list}function ne(){return re(p(),"skills-disabled.json")}function M(){return new Set(A(ne()).disabled)}function R(t){return new Set(A(x(t)).disabled)}var ue=["auto-skill-","test-skill-"],de=".skills_prompt_snapshot.json";function me(t){return ue.some(e=>t.startsWith(e))}function pe(t){let e=t.replace(/\r\n/g,`
|
|
3
|
+
`),r=e.match(/^---\n[\s\S]*?^version:\s*(\S+)/m),o=e.match(/^---\n[\s\S]*?^category:\s*(.+)/m),n=e.match(/^---\n[\s\S]*?^author:\s*(.+)/m),i=e.match(/^---\n[\s\S]*?^requires:\s*(.+)/m),s=e.match(/^---\n[\s\S]*?^environments:\s*(.+)/m),a=e.match(/^---\n([\s\S]*?)\n---/)?.[1]??"";return{version:r?.[1],description:fe(a),category:o?.[1]?.trim()||void 0,author:n?.[1]?.trim()||void 0,requiredTools:D(i?.[1]),environments:D(s?.[1])}}function fe(t){let e=t.split(`
|
|
4
|
+
`),r=e.findIndex(s=>/^description:/.test(s));if(r===-1)return;let o=e[r].replace(/^description:[ \t]*/,"").trim(),n=/^[>|][+-]?$/.test(o),i=[];!n&&o&&i.push(o);for(let s=r+1;s<e.length;s++){let a=e[s];if(!/^[ \t]/.test(a))break;let l=a.trim();l&&i.push(l)}if(i.length!==0)return i.join(" ").replace(/^["']|["']$/g,"").trim()||void 0}var E=new Map,b=new Map;function ge(t,e,r,o){let n=e.has(t.name),i=r.has(t.name),s=!ve(t.meta,o);return{name:t.name,filePath:t.filePath,baseDir:t.baseDir,globallyDisabled:n,projectDisabled:i,active:!n&&!i&&!s,systemGenerated:t.systemGenerated,description:t.meta.description,version:t.meta.version,category:t.meta.category,author:t.meta.author,requiredTools:t.meta.requiredTools,environments:t.meta.environments}}async function he(t){let e;try{e=await ie(t,{withFileTypes:!0})}catch{e=[]}let r=[];for(let s of e){if(!s.isDirectory()||s.name.startsWith("."))continue;let a=d(t,s.name),l=d(a,"SKILL.md");try{let y=await le(l);if(!y.isFile())continue;r.push({name:s.name,filePath:l,baseDir:a,mtimeMs:y.mtimeMs,size:y.size})}catch{continue}}r.sort((s,a)=>s.name.localeCompare(a.name));let o=r.map(s=>`${s.name}:${s.mtimeMs}:${s.size}`).join("|"),n=E.get(t);if(n&&n.fingerprint===o)return n.raw;let i=[];for(let s of r){let a;try{a=await ae(s.filePath,"utf8")}catch{continue}i.push({name:s.name,filePath:s.filePath,baseDir:s.baseDir,systemGenerated:me(s.name),mtimeMs:s.mtimeMs,size:s.size,meta:pe(a)})}return E.set(t,{fingerprint:o,raw:i}),be(t,i),i}async function ye(t,e){let r=v(),o=M(),n=t?R(t):new Set,i=b.get(r);return i||(i=he(r).finally(()=>b.delete(r)),b.set(r,i)),(await i).map(a=>ge(a,o,n,e))}async function I(t,e){return(await ye(t,e)).filter(r=>r.active)}function j(t){let e=v(),r=d(e,t),o=d(r,"SKILL.md");try{if(se(o).isFile())return{baseDir:r,filePath:o}}catch{}return null}function D(t){if(!t)return;let e=t.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(r=>r.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function ve(t,e){if(t.environments&&e?.currentEnvironment&&!t.environments.includes(e.currentEnvironment))return!1;if(t.requiredTools){if(!e?.availableToolNames)return!1;let r=new Set(e.availableToolNames);return t.requiredTools.every(o=>r.has(o))}return!0}async function be(t,e){try{let o={version:1,entries:e.map(n=>({name:n.name,size:n.size,mtimeMs:n.mtimeMs,description:n.meta.description,version:n.meta.version,requiredTools:n.meta.requiredTools,environments:n.meta.environments}))};await ce(d(t,de),JSON.stringify(o,null,2),"utf8")}catch{}}var L="astraclaw_capabilities";async function O(t){if(t.tool==="skills_list")return{handled:!0,result:{skills:(await I(t.projectRoot,{availableToolNames:t.availableToolNames,currentEnvironment:t.currentEnvironment??process.platform})).map(we)}};if(t.tool==="skill_view"){let e=typeof t.args.name=="string"?t.args.name.trim():"";if(!e)throw new Error("skill_view requires a skill name");let r=j(e);if(!r)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:Se(r.filePath,"utf8")}}}if(t.tool==="mcp_connectors_list"){let e=t.mcpServerFailures??[],r=e.length>0?{failedServers:e,note:`${e.length} configured MCP server(s) are unavailable this session; their tools are missing from this list.`}:{};if(!t.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance.",...e.length>0?{failedServers:e}:{}}};let o=t.toolCatalog.getToolManifest().filter(n=>n.function.name.startsWith("mcp__")).map(n=>({toolName:n.function.name,server:Pe(n.function.name),name:_e(n.function.name),description:n.function.description,inputSchema:n.function.parameters}));return{handled:!0,result:{tools:o,count:o.length,...r}}}if(t.tool==="mcp_tool_call"){if(!t.toolCatalog)throw new Error("AstraClaw MCP connector catalog is not attached");let e=typeof t.args.toolName=="string"?t.args.toolName.trim():"";if(!e.startsWith("mcp__"))throw new Error("mcp_tool_call requires a connector toolName returned by mcp_connectors_list");let r=t.toolCatalog.findTool(e);if(!r?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let o=ke(t.args.args),n=`astraclaw_mcp_${Te().slice(0,8)}`,i=await r.execute(n,o);return{handled:!0,result:{toolName:e,content:i.content,details:i.details,imageUrls:i.imageUrls}}}return{handled:!1}}function we(t){return{name:t.name,description:t.description,version:t.version,category:t.category,requiredTools:t.requiredTools,environments:t.environments}}function ke(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function Pe(t){let e=t.split("__");return e.length>=3?e[1]??"":""}function _e(t){let e=t.split("__");return e.length>=3?e.slice(2).join("__"):t}import m from"node:path";import{fileURLToPath as Re}from"node:url";import{createRuntime as Ee}from"mcporter";var $=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),Ce=new Set([...$,"agent"]);function N(t){return t?.riskLevel?t.riskLevel:t?.isDangerous?"system":t?.isReadOnly?"read":t?.isEgress?"external_egress":"write"}var S=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let r of e)this.toolPool.set(r.name,r)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let r of e)this.addTool(r)}removeTool(e){return this.toolPool.delete(e)}findTool(e){return this.toolPool.get(e)}hasTool(e){return this.toolPool.has(e)}getToolNames(){return Array.from(this.toolPool.keys())}getToolCount(){return this.toolPool.size}async executeTool(e,r,o,n){let i=this.toolPool.get(e);if(!i)throw new Error(`Tool not found: ${e}`);return i.execute(r,o,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let r of this.toolPool.values()){if(r.isEnabled?.()===!1)continue;let o=N(r);e.push({type:"function",function:{name:r.name,description:r.description,parameters:r.parameters},meta:{category:r.category??"other",displayName:r.displayName??{key:`capability.tool.${r.name}.name`,fallback:r.label},displayDescription:r.displayDescription??{key:`capability.tool.${r.name}.description`,fallback:""},parallelSafe:r.isConcurrencySafe??!1,riskLevel:o,isReadOnly:o==="read",isDangerous:o==="system",isDelete:r.isDelete??!1,isEgress:r.isEgress??o==="external_egress",egressCarriesData:r.egressCarriesData??!1,...r.executionTimeoutMs!==void 0?{executionTimeoutMs:r.executionTimeoutMs}:{}}})}return e}},F=new S;function U(t){F.addTools(t)}function q(t){return F.removeTool(t)}var xe=new Set(["describe","fetch","find","get","inspect","list","query","read","search","stat"]),Ae=new Set(["append","copy","create","delete","edit","move","patch","remove","rename","set","update","upload","write"]);function G(t){let e=t.annotations,r=Me(t.name),o=r.some(l=>Ae.has(l)),n=r.some(l=>xe.has(l)),i=e?.destructiveHint===!0||o,s=e?.readOnlyHint===!0||!i&&n,a=s||e?.idempotentHint===!0&&!i;return{isReadOnly:s,isConcurrencySafe:a,isDestructive:i}}function Me(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var w=12e4,z=1e3,W=9e5;function De(t){if(t===void 0)return w;if(!Number.isInteger(t)||t<z||t>W)throw new RangeError(`MCP callTimeoutMs must be an integer between ${z} and ${W}`);return t}var g=3,Ie=1e3;async function je(t,e){let r=e.sleep??(i=>new Promise(s=>setTimeout(s,i))),o=Math.max(1,e.attempts),n;for(let i=1;i<=o;i++)try{return await t()}catch(s){n=s,i<o&&(e.onRetry?.(i,s),await r(e.baseDelayMs*i))}throw n}var h=class{runtime=null;definitions=[];toolsByServer=new Map;injectedNames=new Set;log;workspaceRoot;toolCatalog;serverToolFilters=new Map;serverOAuth=new Map;serverCallTimeouts=new Map;failedServers=new Map;constructor(e){this.log=e.log??{info:()=>{},warn:()=>{}},this.workspaceRoot=e.workspaceRoot?m.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(r=>!r.disabled).filter(r=>{let o=Ve(r);for(let n of o)this.log.warn(`[mcp] rejected server "${r.name}": ${n.message}`);return r.tools&&this.serverToolFilters.set(r.name,r.tools),r.oauth===!0&&this.serverOAuth.set(r.name,!0),this.serverCallTimeouts.set(r.name,De(r.callTimeoutMs)),o.length===0}).map(r=>this.toServerDefinition(r)).filter(r=>r!==null)}markServerFailed(e,r,o){this.failedServers.set(e,{name:e,phase:r,error:o}),console.error(`[mcp] server "${e}" unavailable this session (${r}): ${o}`)}toServerDefinition(e){if((e.type??(e.url?"http":"stdio"))==="http"){if(!e.url)return this.log.warn(`[mcp] server "${e.name}" is type "http" but has no url, skipping`),this.markServerFailed(e.name,"config",'type "http" but no url'),null;let o;try{o=new URL(e.url)}catch{return this.log.warn(`[mcp] server "${e.name}" has an invalid url "${e.url}", skipping`),this.markServerFailed(e.name,"config",`invalid url "${e.url}"`),null}return{name:e.name,command:{kind:"http",url:o,headers:e.headers}}}return e.command?{name:e.name,command:{kind:"stdio",command:e.command,args:e.args??[],cwd:e.cwd??this.workspaceRoot??process.cwd()},env:e.env}:(this.log.warn(`[mcp] server "${e.name}" is type "stdio" but has no command, skipping`),this.markServerFailed(e.name,"config",'type "stdio" but no command'),null)}async connectAll(){this.definitions.length!==0&&(this.runtime=await Ee({servers:this.definitions,clientInfo:{name:"qlogicagent",version:"1.0.0"},logger:{info:e=>this.log.info(`[mcp] ${e}`),warn:e=>this.log.warn(`[mcp] ${e}`),error:(e,r)=>this.log.warn(`[mcp] ${e}${r?` (${r instanceof Error?r.message:String(r)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let r=await je(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:g,baseDelayMs:Ie,onRetry:(n,i)=>this.log.warn(`[mcp] connect attempt ${n}/${g} for ${e.name} failed, retrying: ${i instanceof Error?i.message:i}`)}),o=J(r,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,o),this.failedServers.delete(e.name),this.log.info(`[mcp] connected to ${e.name} (${o.length}/${r.length} tools)`)}catch(r){this.log.warn(`[mcp] failed to connect to ${e.name} after ${g} attempts: ${r instanceof Error?r.message:r}`),this.markServerFailed(e.name,"connect",`${r instanceof Error?r.message:String(r)} (after ${g} attempts)`)}})))}injectTools(){for(let[e,r]of this.toolsByServer){let o=this.injectPortableTools(e,r);this.log.info(`[mcp] injected ${o.length} tools from ${e}`)}}async refreshServerTools(e){if(!this.runtime)throw new Error("MCP runtime is not connected");let r=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),o=J(r,this.serverToolFilters.get(e));return this.toolsByServer.set(e,o),this.failedServers.delete(e),this.retractServerTools(e),this.injectPortableTools(e,o),o.length}getConnectedServers(){return Array.from(this.toolsByServer.keys())}getFailedServers(){return Array.from(this.failedServers.values())}getToolCount(){let e=0;for(let r of this.toolsByServer.values())e+=r.length;return e}async disconnectAll(){for(let r of this.injectedNames)this.removeRegisteredTool(r);this.injectedNames.clear(),this.toolsByServer.clear(),this.failedServers.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,r){let o=G({name:r.name,description:r.description});return{name:`mcp__${B(e)}__${r.name}`,label:`[${e}] ${r.name}`,category:"mcp",description:r.description??`MCP tool from ${e}`,parameters:r.inputSchema??{type:"object",properties:{}},isConcurrencySafe:o.isConcurrencySafe,isReadOnly:o.isReadOnly,isDestructive:o.isDestructive,searchHint:`mcp ${e} ${r.name.replace(/[_-]+/g," ")}`,executionTimeoutMs:this.serverCallTimeouts.get(e)??w,execute:async(n,i)=>{let s=this.runtime;if(!s)return{content:[{type:"text",text:`MCP server "${e}" is not connected.`}],details:{error:"mcp_not_connected"}};try{let a=await s.callTool(e,r.name,{args:i,timeoutMs:this.serverCallTimeouts.get(e)??w});return Le(a)}catch(a){let l=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`MCP tool error: ${l}`}],details:{error:l}}}}}}wrapToolsForWorkspaceBoundary(e){return this.workspaceRoot?e.map(r=>{if(!r.execute)return r;let o=r.execute;return{...r,execute:async(n,i,s)=>{let a=$e(r.name,i,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:o(n,i,s)}}}):e}injectPortableTools(e,r){let o=this.wrapToolsForWorkspaceBoundary(r.map(n=>this.toPortableTool(e,n)));this.registerTools(o);for(let n of o)this.injectedNames.add(n.name);return o}retractServerTools(e){let r=`mcp__${B(e)}__`;for(let o of[...this.injectedNames])o.startsWith(r)&&(this.removeRegisteredTool(o),this.injectedNames.delete(o))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):U(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??q(e)}};function Le(t){let e=t&&typeof t=="object"?t:{},o=(Array.isArray(e.content)?e.content:[]).filter(a=>!!a&&typeof a=="object"),n=o.filter(a=>a.type==="text"&&typeof a.text=="string").map(a=>a.text),i=o.filter(a=>!(a.type==="text"&&typeof a.text=="string")).map(a=>typeof a.type=="string"&&a.type?a.type:"unknown"),s=n.join(`
|
|
5
5
|
`);if(i.length>0){let a=[...new Set(i)].join(", ");s=s?`${s}
|
|
6
6
|
|
|
7
|
-
[${i.length} non-text blocks omitted: ${a}]`:`[output contains only non-text blocks: ${a}]`}return{content:[{type:"text",text:s||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function
|
|
8
|
-
`);if(Je.test(r)&&e.push({code:"ioc_public_key",message:"configuration contains an inline SSH public key IOC"}),(t.type??(t.url?"http":"stdio"))!=="stdio"||!Ke(t.command))return e;let o=Qe(t.args??[]);if(!o)return e;let n=Ye(o);return T(n,He)&&e.push({code:"persistence",message:"stdio shell command attempts a persistence write"}),T(n,We)&&T(n,Be)&&e.push({code:"dangerous_egress",message:"stdio shell command combines network egress with likely secret access"}),e}function Ke(t){if(!t)return!1;let e=m.basename(t).toLowerCase();return Ge.has(e)}function Qe(t){for(let e=0;e<t.length;e++){let r=t[e]?.toLowerCase();if(r&&ze.has(r))return t.slice(e+1).join(" ")}return""}function Ye(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"")}function T(t,e){return e.some(r=>t.includes(r))}var tt=[{name:"skills_list",description:"List AstraClaw skills active for the current workspace.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"skill_view",description:"Read the full SKILL.md instructions for one AstraClaw skill.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name from skills_list."}},required:["name"],additionalProperties:!1}},{name:"mcp_connectors_list",description:"List MCP connector tools installed in AstraClaw's Plugins page and shared with this external agent.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"mcp_tool_call",description:"Call one AstraClaw shared MCP connector tool by toolName from mcp_connectors_list.",inputSchema:{type:"object",properties:{toolName:{type:"string",description:"Connector tool name, for example mcp__filesystem__read_text_file."},args:{type:"object",description:"Arguments for the connector tool."}},required:["toolName"],additionalProperties:!1}}],
|
|
7
|
+
[${i.length} non-text blocks omitted: ${a}]`:`[output contains only non-text blocks: ${a}]`}return{content:[{type:"text",text:s||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function B(t){return t.replace(/[^a-zA-Z0-9_]/g,"_").toLowerCase()}var Oe=/(?:^|_)(path|paths|file|files|filename|filenames|dir|dirs|directory|directories|folder|folders|cwd|root|uri)(?:$|_)/i;function $e(t,e,r){if(!r)return null;for(let o of k(e)){let n=Ue(o.value,r);if(n&&!qe(n.candidate,n.root,n.pathApi))return`Blocked: MCP tool "${t}" path "${o.value}" is outside the workspace boundary "${n.root}"`}return null}function k(t,e="",r=0){if(r>8)return[];if(typeof t=="string")return Ne(e,t)?[{key:e,value:t}]:[];if(Array.isArray(t))return t.flatMap(n=>k(n,e,r+1));if(!t||typeof t!="object")return[];let o=[];for(let[n,i]of Object.entries(t))o.push(...k(i,n,r+1));return o}function Ne(t,e){if(!Oe.test(t))return!1;let r=e.trim();return!r||/^https?:\/\//i.test(r)?!1:Fe(r)}function Fe(t){return m.isAbsolute(t)||/^[A-Za-z]:[\\/]/.test(t)||/^\\\\/.test(t)||/^file:\/\//i.test(t)||t.includes("../")||t.includes("..\\")||t.startsWith(".\\")||t.startsWith("./")||t.includes("/")||t.includes("\\")}function Ue(t,e){let r=t.trim();if(/^file:\/\//i.test(r))try{r=Re(r)}catch{return null}let o=H(r)||H(e)?m.win32:m,n=o.resolve(e);return{candidate:o.isAbsolute(r)?o.resolve(r):o.resolve(n,r),root:n,pathApi:o}}function qe(t,e,r){let o=r.relative(e,t);return o===""||!o.startsWith("..")&&!r.isAbsolute(o)}function H(t){return/^[A-Za-z]:[\\/]/.test(t)||/^\\\\/.test(t)}function Q(t){if(!t||typeof t!="object")return[];let e=t,r=[],o=e.mcpServers??e.servers??e;for(let[n,i]of Object.entries(o)){if(!i||typeof i!="object")continue;let s=i;if(typeof s.url=="string"){r.push({name:n,type:"http",url:s.url,headers:s.headers&&typeof s.headers=="object"?s.headers:void 0,disabled:s.disabled===!0,oauth:s.oauth===!0,tools:V(s.tools),callTimeoutMs:typeof s.callTimeoutMs=="number"?s.callTimeoutMs:void 0});continue}typeof s.command=="string"&&r.push({name:n,type:"stdio",command:s.command,args:Array.isArray(s.args)?s.args:void 0,env:s.env&&typeof s.env=="object"?s.env:void 0,cwd:typeof s.cwd=="string"?s.cwd:void 0,disabled:s.disabled===!0,oauth:s.oauth===!0,tools:V(s.tools),callTimeoutMs:typeof s.callTimeoutMs=="number"?s.callTimeoutMs:void 0})}return r}function J(t,e){let r=new Set(e?.include??[]),o=new Set(e?.exclude??[]);return t.filter(n=>r.size>0&&!r.has(n.name)?!1:!o.has(n.name))}function V(t){if(!t||typeof t!="object")return;let e=t,r=K(e.include),o=K(e.exclude);if(!(!r&&!o))return{...r?{include:r}:{},...o?{exclude:o}:{}}}function K(t){if(!Array.isArray(t))return;let e=t.filter(r=>typeof r=="string"&&r.trim().length>0);return e.length>0?e:void 0}var Ge=new Set(["bash","sh","zsh","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]),ze=new Set(["-c","/c","-command","-encodedcommand","-e"]),We=["curl","wget","invokewebrequest","invoke-restmethod","iwr","irm","nc","netcat"],Be=["env","processenv","apikey","api_key","token","secret","credential","credentials","awsaccesskey","awssecret"],He=["authorizedkeys","sshknownhosts","crontab","launchctl","schtasks","startup","runkey","runonce","bashrc","profile"],Je=/\bssh-(?:rsa|ed25519|ecdsa-[^\s]+)\s+[A-Za-z0-9+/=]{40,}/i;function Ve(t){let e=[],r=[t.command??"",...t.args??[],...Object.keys(t.env??{}),...Object.values(t.env??{})].join(`
|
|
8
|
+
`);if(Je.test(r)&&e.push({code:"ioc_public_key",message:"configuration contains an inline SSH public key IOC"}),(t.type??(t.url?"http":"stdio"))!=="stdio"||!Ke(t.command))return e;let o=Qe(t.args??[]);if(!o)return e;let n=Ye(o);return T(n,He)&&e.push({code:"persistence",message:"stdio shell command attempts a persistence write"}),T(n,We)&&T(n,Be)&&e.push({code:"dangerous_egress",message:"stdio shell command combines network egress with likely secret access"}),e}function Ke(t){if(!t)return!1;let e=m.basename(t).toLowerCase();return Ge.has(e)}function Qe(t){for(let e=0;e<t.length;e++){let r=t[e]?.toLowerCase();if(r&&ze.has(r))return t.slice(e+1).join(" ")}return""}function Ye(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"")}function T(t,e){return e.some(r=>t.includes(r))}var tt=[{name:"skills_list",description:"List AstraClaw skills active for the current workspace.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"skill_view",description:"Read the full SKILL.md instructions for one AstraClaw skill.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name from skills_list."}},required:["name"],additionalProperties:!1}},{name:"mcp_connectors_list",description:"List MCP connector tools installed in AstraClaw's Plugins page and shared with this external agent.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"mcp_tool_call",description:"Call one AstraClaw shared MCP connector tool by toolName from mcp_connectors_list.",inputSchema:{type:"object",properties:{toolName:{type:"string",description:"Connector tool name, for example mcp__filesystem__read_text_file."},args:{type:"object",description:"Arguments for the connector tool."}},required:["toolName"],additionalProperties:!1}}],P=class{tools=new Map;findTool(e){return this.tools.get(e)}getToolManifest(){return Array.from(this.tools.values()).map(e=>({type:"function",function:{name:e.name,description:e.description,parameters:e.parameters}}))}getToolNames(){return Array.from(this.tools.keys())}setTools(e){this.tools.clear(),this.addTools(e)}addTool(e){this.tools.set(e.name,e)}addTools(e){for(let r of e)this.addTool(r)}removeTool(e){return this.tools.delete(e)}},c=null,rt=Ze({input:process.stdin,crlfDelay:Number.POSITIVE_INFINITY});rt.on("line",t=>{ot(t)});async function ot(t){let e=t.trim();if(!e)return;let r;try{r=JSON.parse(e)}catch{return}if(r.id===void 0||r.id===null)return r.method==="notifications/initialized",void 0;try{let o=await nt(r);Z({jsonrpc:"2.0",id:r.id,result:o})}catch(o){Z({jsonrpc:"2.0",id:r.id,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}}async function nt(t){switch(t.method){case"initialize":return{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:L,version:"1.0.0"}};case"tools/list":return{tools:tt};case"tools/call":{let e=Y(t.params),r=typeof e.name=="string"?e.name:"",o=Y(e.arguments),n=st(),i=r==="mcp_connectors_list"||r==="mcp_tool_call"?await it(n):void 0,s=await O({tool:r,args:o,projectRoot:n,toolCatalog:i,mcpServerFailures:i?c?.manager?.getFailedServers():void 0});if(!s.handled)throw new Error(`Unknown tool: ${r}`);return{content:[{type:"text",text:JSON.stringify(s.result)}]}}default:throw new Error(`Unknown method: ${t.method}`)}}function st(){return process.env.ASTRACLAW_PROJECT_ROOT||process.env.QLOGICAGENT_PROJECT_ROOT||process.cwd()}async function it(t){if(c?.projectRoot===t)return await c.ready,c.catalog;c?.manager?.disconnectAll().catch(()=>{});let e=new P,r=new h({servers:at(),workspaceRoot:t,toolCatalog:e,log:{info:n=>process.stderr.write(`${n}
|
|
9
9
|
`),warn:n=>process.stderr.write(`${n}
|
|
10
|
-
`)}}),o=r.connectAll().then(()=>r.injectTools());return c={projectRoot:t,catalog:e,manager:r,ready:o},await o,e}function at(){let t=process.env.ASTRACLAW_MCP_CONFIG_PATH||
|
|
10
|
+
`)}}),o=r.connectAll().then(()=>r.injectTools());return c={projectRoot:t,catalog:e,manager:r,ready:o},await o,e}function at(){let t=process.env.ASTRACLAW_MCP_CONFIG_PATH||C();if(!Xe(t))return[];try{let e=et(t,"utf8").replace(/^\uFEFF/,"");return Q(JSON.parse(e))}catch(e){return process.stderr.write(`[astraclaw-native-mcp] failed to read MCP config: ${e instanceof Error?e.message:String(e)}
|
|
11
11
|
`),[]}}function Y(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function Z(t){process.stdout.write(`${JSON.stringify(t)}
|
|
12
12
|
`)}
|
|
@@ -117,5 +117,7 @@ export interface ToolDefinition {
|
|
|
117
117
|
isEgress?: boolean;
|
|
118
118
|
/** Egress carries user data (true) vs. read-only external query (false). */
|
|
119
119
|
egressCarriesData?: boolean;
|
|
120
|
+
/** Tool-specific foreground execution deadline used by the agent scheduler. */
|
|
121
|
+
executionTimeoutMs?: number;
|
|
120
122
|
};
|
|
121
123
|
}
|
|
@@ -39,6 +39,8 @@ export interface StreamingToolExecutorConfig {
|
|
|
39
39
|
maxConcurrentTools?: number;
|
|
40
40
|
/** Per foreground tool deadline. 0 or undefined means no executor deadline. */
|
|
41
41
|
maxExecutionMs?: number;
|
|
42
|
+
/** Optional per-tool deadlines; entries override maxExecutionMs for that tool only. */
|
|
43
|
+
executionTimeoutMsByTool?: ReadonlyMap<string, number>;
|
|
42
44
|
}
|
|
43
45
|
/**
|
|
44
46
|
* Executes tools with concurrency control and streaming progress.
|
|
@@ -39,6 +39,7 @@ export interface StreamingToolExecutorPortConfig {
|
|
|
39
39
|
signal?: AbortSignal;
|
|
40
40
|
maxConcurrentTools?: number;
|
|
41
41
|
maxExecutionMs?: number;
|
|
42
|
+
executionTimeoutMsByTool?: ReadonlyMap<string, number>;
|
|
42
43
|
/** Turn workspace/cwd — routes oversized tool-result spillover to the session's reachable data dir. */
|
|
43
44
|
projectRoot?: string;
|
|
44
45
|
}
|
|
@@ -135,6 +135,11 @@ export interface PortableTool<TParams = Record<string, unknown>> {
|
|
|
135
135
|
* The runtime should truncate/reject results exceeding this.
|
|
136
136
|
*/
|
|
137
137
|
maxResultSizeChars?: number;
|
|
138
|
+
/**
|
|
139
|
+
* Foreground execution deadline for this tool. It must cover any inner
|
|
140
|
+
* transport deadline so the agent does not abandon a still-running tool.
|
|
141
|
+
*/
|
|
142
|
+
executionTimeoutMs?: number;
|
|
138
143
|
/**
|
|
139
144
|
* Execute the tool.
|
|
140
145
|
*
|