qlogicagent 2.17.10 → 2.18.1
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 +16 -16
- package/dist/cli.js +1 -1
- package/dist/index.js +431 -423
- package/dist/skills/mcp/astraclaw-native-mcp-server.js +6 -5
- package/dist/types/agent/tool-loop.d.ts +6 -0
- package/dist/types/agent/types.d.ts +5 -0
- package/dist/types/cli/handlers/agents-handler.d.ts +9 -0
- package/dist/types/cli/handlers/config-handler.d.ts +5 -0
- package/dist/types/cli/handlers/files-handler.d.ts +7 -0
- package/dist/types/cli/handlers/session-handler.d.ts +43 -0
- package/dist/types/cli/handlers/turn-handler.d.ts +12 -0
- package/dist/types/cli/permission-rule-persistence.d.ts +16 -0
- package/dist/types/cli/stdio-rpc-handler-hosts.d.ts +4 -0
- package/dist/types/cli/stdio-server.d.ts +16 -1
- package/dist/types/cli/turn-core.d.ts +2 -0
- package/dist/types/orchestration/agent-instance.d.ts +4 -0
- package/dist/types/orchestration/delegation-coordinator.d.ts +10 -0
- package/dist/types/orchestration/product-persistence.d.ts +4 -0
- package/dist/types/protocol/wire/acp-agent-management.d.ts +16 -0
- package/dist/types/protocol/wire/agent-methods.d.ts +3 -0
- package/dist/types/runtime/infra/acp-detector.d.ts +35 -5
- package/dist/types/runtime/infra/acp-protocol-adapter.d.ts +14 -0
- package/dist/types/runtime/infra/agent-process.d.ts +15 -0
- package/dist/types/runtime/infra/llmrouter-catalog.d.ts +26 -6
- package/dist/types/runtime/infra/model-registry.d.ts +43 -2
- package/dist/types/runtime/infra/user-dirs.d.ts +21 -0
- package/dist/types/runtime/permission-model.d.ts +1 -1
- package/dist/types/runtime/ports/permission-contracts.d.ts +5 -2
- package/dist/types/runtime/prompt/fresh-workspace-evidence.d.ts +1 -2
- package/dist/types/runtime/prompt/identity-section.d.ts +1 -1
- package/dist/types/runtime/session/native/claude-session-source.d.ts +23 -0
- package/dist/types/runtime/session/native/codex-index.d.ts +21 -0
- package/dist/types/runtime/session/native/codex-session-source.d.ts +31 -0
- package/dist/types/runtime/session/native/cwd-normalize.d.ts +53 -0
- package/dist/types/runtime/session/native/ephemeral-rollout-sweep.d.ts +13 -0
- package/dist/types/runtime/session/native/native-cwd-agents.d.ts +17 -0
- package/dist/types/runtime/session/native/native-session-source.d.ts +89 -0
- package/dist/types/runtime/session/native/native-title.d.ts +1 -0
- package/dist/types/runtime/session/native/register-builtin-sources.d.ts +1 -0
- package/dist/types/runtime/session/session-types.d.ts +8 -0
- package/dist/types/skills/permissions/hook-runner.d.ts +9 -0
- package/dist/types/skills/permissions/settings-watcher.d.ts +7 -0
- package/dist/types/transport/acp-server.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createInterface as
|
|
3
|
-
`),o=e.match(/^---\n[\s\S]*?^version:\s*(\S+)/m),r=e.match(/^---\n[\s\S]*?^description:\s*(.+)/m),n=e.match(/^---\n[\s\S]*?^category:\s*(.+)/m),i=e.match(/^---\n[\s\S]*?^author:\s*(.+)/m),s=e.match(/^---\n[\s\S]*?^requires:\s*(.+)/m),a=e.match(/^---\n[\s\S]*?^environments:\s*(.+)/m);return{version:o?.[1],description:r?.[1]?.trim(),category:n?.[1]?.trim()||void 0,author:i?.[1]?.trim()||void 0,requiredTools:E(s?.[1]),environments:E(a?.[1])}}var M=new Map,y=new Map;function pe(t,e,o,r){let n=e.has(t.name),i=o.has(t.name),s=!he(t.meta,r);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 fe(t){let e;try{e=await se(t,{withFileTypes:!0})}catch{e=[]}let o=[];for(let s of e){if(!s.isDirectory()||s.name.startsWith("."))continue;let a=u(t,s.name),l=u(a,"SKILL.md");try{let g=await ae(l);if(!g.isFile())continue;o.push({name:s.name,filePath:l,baseDir:a,mtimeMs:g.mtimeMs,size:g.size})}catch{continue}}o.sort((s,a)=>s.name.localeCompare(a.name));let r=o.map(s=>`${s.name}:${s.mtimeMs}:${s.size}`).join("|"),n=M.get(t);if(n&&n.fingerprint===r)return n.raw;let i=[];for(let s of o){let a;try{a=await ie(s.filePath,"utf8")}catch{continue}i.push({name:s.name,filePath:s.filePath,baseDir:s.baseDir,systemGenerated:de(s.name),mtimeMs:s.mtimeMs,size:s.size,meta:me(a)})}return M.set(t,{fingerprint:r,raw:i}),ye(t,i),i}async function ge(t,e){let o=h(),r=A(),n=t?R(t):new Set,i=y.get(o);return i||(i=fe(o).finally(()=>y.delete(o)),y.set(o,i)),(await i).map(a=>pe(a,r,n,e))}async function D(t,e){return(await ge(t,e)).filter(o=>o.active)}function I(t){let e=h(),o=u(e,t),r=u(o,"SKILL.md");try{if(ne(r).isFile())return{baseDir:o,filePath:r}}catch{}return null}function E(t){if(!t)return;let e=t.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(o=>o.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function he(t,e){if(t.environments&&e?.currentEnvironment&&!t.environments.includes(e.currentEnvironment))return!1;if(t.requiredTools){if(!e?.availableToolNames)return!1;let o=new Set(e.availableToolNames);return t.requiredTools.every(r=>o.has(r))}return!0}async function ye(t,e){try{let r={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 le(u(t,ue),JSON.stringify(r,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 o=I(e);if(!o)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:be(o.filePath,"utf8")}}}if(t.tool==="mcp_connectors_list"){if(!t.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance."}};let e=t.toolCatalog.getToolManifest().filter(o=>o.function.name.startsWith("mcp__")).map(o=>({toolName:o.function.name,server:Se(o.function.name),name:ke(o.function.name),description:o.function.description,inputSchema:o.function.parameters}));return{handled:!0,result:{tools:e,count:e.length}}}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 o=t.toolCatalog.findTool(e);if(!o?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let r=Te(t.args.args),n=`astraclaw_mcp_${ve().slice(0,8)}`,i=await o.execute(n,r);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 Te(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function Se(t){let e=t.split("__");return e.length>=3?e[1]??"":""}function ke(t){let e=t.split("__");return e.length>=3?e.slice(2).join("__"):t}import d from"node:path";import{fileURLToPath as Ae}from"node:url";import{createRuntime as Re}from"mcporter";var O=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),Pe=new Set([...O,"agent"]);function N(t){return t?.riskLevel?t.riskLevel:t?.isDangerous?"system":t?.isReadOnly?"read":t?.isEgress?"external_egress":"write"}var b=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let o of e)this.toolPool.set(o.name,o)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let o of e)this.addTool(o)}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,o,r,n){let i=this.toolPool.get(e);if(!i)throw new Error(`Tool not found: ${e}`);return i.execute(o,r,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let o of this.toolPool.values()){if(o.isEnabled?.()===!1)continue;let r=N(o);r==="write"&&(o.category==="media"||o.category==="web"||o.category==="mcp"?r="external_egress":(o.category==="system"||o.name==="exec")&&(r="system")),e.push({type:"function",function:{name:o.name,description:o.description,parameters:o.parameters},meta:{category:o.category??"other",displayName:o.displayName??{key:`capability.tool.${o.name}.name`,fallback:o.label},displayDescription:o.displayDescription??{key:`capability.tool.${o.name}.description`,fallback:""},parallelSafe:o.isConcurrencySafe??!1,riskLevel:r,isReadOnly:r==="read",isDangerous:r==="system",isDelete:o.isDelete??!1,isEgress:o.isEgress??r==="external_egress",egressCarriesData:o.egressCarriesData??!1}})}return e}},$=new b;function q(t){$.addTools(t)}function U(t){return $.removeTool(t)}var _e=new Set(["describe","fetch","find","get","inspect","list","query","read","search","stat"]),Ce=new Set(["append","copy","create","delete","edit","move","patch","remove","rename","set","update","upload","write"]);function F(t){let e=t.annotations,o=xe(t.name),r=o.some(l=>Ce.has(l)),n=o.some(l=>_e.has(l)),i=e?.destructiveHint===!0||r,s=e?.readOnlyHint===!0||!i&&n,a=s||e?.idempotentHint===!0&&!i;return{isReadOnly:s,isConcurrencySafe:a,isDestructive:i}}function xe(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var Me=12e4,v=3,Ee=1e3;async function De(t,e){let o=e.sleep??(i=>new Promise(s=>setTimeout(s,i))),r=Math.max(1,e.attempts),n;for(let i=1;i<=r;i++)try{return await t()}catch(s){n=s,i<r&&(e.onRetry?.(i,s),await o(e.baseDelayMs*i))}throw n}var f=class{runtime=null;definitions=[];toolsByServer=new Map;injectedNames=new Set;log;workspaceRoot;toolCatalog;serverToolFilters=new Map;serverOAuth=new Map;constructor(e){this.log=e.log??{info:()=>{},warn:()=>{}},this.workspaceRoot=e.workspaceRoot?d.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(o=>!o.disabled).filter(o=>{let r=He(o);for(let n of r)this.log.warn(`[mcp] rejected server "${o.name}": ${n.message}`);return o.tools&&this.serverToolFilters.set(o.name,o.tools),o.oauth===!0&&this.serverOAuth.set(o.name,!0),r.length===0}).map(o=>this.toServerDefinition(o)).filter(o=>o!==null)}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`),null;let r;try{r=new URL(e.url)}catch{return this.log.warn(`[mcp] server "${e.name}" has an invalid url "${e.url}", skipping`),null}return{name:e.name,command:{kind:"http",url:r,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`),null)}async connectAll(){this.definitions.length!==0&&(this.runtime=await Re({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,o)=>this.log.warn(`[mcp] ${e}${o?` (${o instanceof Error?o.message:String(o)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let o=await De(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:v,baseDelayMs:Ee,onRetry:(n,i)=>this.log.warn(`[mcp] connect attempt ${n}/${v} for ${e.name} failed, retrying: ${i instanceof Error?i.message:i}`)}),r=z(o,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,r),this.log.info(`[mcp] connected to ${e.name} (${r.length}/${o.length} tools)`)}catch(o){this.log.warn(`[mcp] failed to connect to ${e.name} after ${v} attempts: ${o instanceof Error?o.message:o}`)}})))}injectTools(){for(let[e,o]of this.toolsByServer){let r=this.injectPortableTools(e,o);this.log.info(`[mcp] injected ${r.length} tools from ${e}`)}}async refreshServerTools(e){if(!this.runtime)throw new Error("MCP runtime is not connected");let o=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),r=z(o,this.serverToolFilters.get(e));return this.toolsByServer.set(e,r),this.retractServerTools(e),this.injectPortableTools(e,r),r.length}getConnectedServers(){return Array.from(this.toolsByServer.keys())}getToolCount(){let e=0;for(let o of this.toolsByServer.values())e+=o.length;return e}async disconnectAll(){for(let o of this.injectedNames)this.removeRegisteredTool(o);this.injectedNames.clear(),this.toolsByServer.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,o){let r=F({name:o.name,description:o.description});return{name:`mcp__${G(e)}__${o.name}`,label:`[${e}] ${o.name}`,category:"mcp",description:o.description??`MCP tool from ${e}`,parameters:o.inputSchema??{type:"object",properties:{}},isConcurrencySafe:r.isConcurrencySafe,isReadOnly:r.isReadOnly,isDestructive:r.isDestructive,searchHint:`mcp ${e} ${o.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,o.name,{args:i,timeoutMs:Me});return Ie(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(o=>{if(!o.execute)return o;let r=o.execute;return{...o,execute:async(n,i,s)=>{let a=Le(o.name,i,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:r(n,i,s)}}}):e}injectPortableTools(e,o){let r=this.wrapToolsForWorkspaceBoundary(o.map(n=>this.toPortableTool(e,n)));this.registerTools(r);for(let n of r)this.injectedNames.add(n.name);return r}retractServerTools(e){let o=`mcp__${G(e)}__`;for(let r of[...this.injectedNames])r.startsWith(o)&&(this.removeRegisteredTool(r),this.injectedNames.delete(r))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):q(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??U(e)}};function Ie(t){let e=t&&typeof t=="object"?t:{};return{content:[{type:"text",text:(Array.isArray(e.content)?e.content:[]).filter(i=>!!i&&typeof i=="object").filter(i=>i.type==="text"&&typeof i.text=="string").map(i=>i.text).join(`
|
|
4
|
-
`)||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function G(t){return t.replace(/[^a-zA-Z0-9_]/g,"_").toLowerCase()}var je=/(?:^|_)(path|paths|file|files|filename|filenames|dir|dirs|directory|directories|folder|folders|cwd|root|uri)(?:$|_)/i;function Le(t,e,o){if(!o)return null;for(let r of T(e)){let n=$e(r.value,o);if(n&&!qe(n.candidate,n.root,n.pathApi))return`Blocked: MCP tool "${t}" path "${r.value}" is outside the workspace boundary "${n.root}"`}return null}function T(t,e="",o=0){if(o>8)return[];if(typeof t=="string")return Oe(e,t)?[{key:e,value:t}]:[];if(Array.isArray(t))return t.flatMap(n=>T(n,e,o+1));if(!t||typeof t!="object")return[];let r=[];for(let[n,i]of Object.entries(t))r.push(...T(i,n,o+1));return r}function Oe(t,e){if(!je.test(t))return!1;let o=e.trim();return!o||/^https?:\/\//i.test(o)?!1:Ne(o)}function Ne(t){return d.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 $e(t,e){let o=t.trim();if(/^file:\/\//i.test(o))try{o=Ae(o)}catch{return null}let r=W(o)||W(e)?d.win32:d,n=r.resolve(e);return{candidate:r.isAbsolute(o)?r.resolve(o):r.resolve(n,o),root:n,pathApi:r}}function qe(t,e,o){let r=o.relative(e,t);return r===""||!r.startsWith("..")&&!o.isAbsolute(r)}function W(t){return/^[A-Za-z]:[\\/]/.test(t)||/^\\\\/.test(t)}function J(t){if(!t||typeof t!="object")return[];let e=t,o=[],r=e.mcpServers??e.servers??e;for(let[n,i]of Object.entries(r)){if(!i||typeof i!="object")continue;let s=i;if(typeof s.url=="string"){o.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:B(s.tools)});continue}typeof s.command=="string"&&o.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:B(s.tools)})}return o}function z(t,e){let o=new Set(e?.include??[]),r=new Set(e?.exclude??[]);return t.filter(n=>o.size>0&&!o.has(n.name)?!1:!r.has(n.name))}function B(t){if(!t||typeof t!="object")return;let e=t,o=H(e.include),r=H(e.exclude);if(!(!o&&!r))return{...o?{include:o}:{},...r?{exclude:r}:{}}}function H(t){if(!Array.isArray(t))return;let e=t.filter(o=>typeof o=="string"&&o.trim().length>0);return e.length>0?e:void 0}var Ue=new Set(["bash","sh","zsh","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]),Fe=new Set(["-c","/c","-command","-encodedcommand","-e"]),Ge=["curl","wget","invokewebrequest","invoke-restmethod","iwr","irm","nc","netcat"],We=["env","processenv","apikey","api_key","token","secret","credential","credentials","awsaccesskey","awssecret"],ze=["authorizedkeys","sshknownhosts","crontab","launchctl","schtasks","startup","runkey","runonce","bashrc","profile"],Be=/\bssh-(?:rsa|ed25519|ecdsa-[^\s]+)\s+[A-Za-z0-9+/=]{40,}/i;function He(t){let e=[],o=[t.command??"",...t.args??[],...Object.keys(t.env??{}),...Object.values(t.env??{})].join(`
|
|
5
|
-
`)
|
|
2
|
+
import{createInterface as Ye}from"node:readline";import{existsSync as Ze,readFileSync as Xe}from"node:fs";import{readFileSync as ve}from"node:fs";import{randomUUID as we}from"node:crypto";import{readdirSync as Pt,readFileSync as _t,statSync as ne,writeFileSync as Ct}from"node:fs";import{readdir as se,readFile as ie,stat as ae,writeFile as le}from"node:fs/promises";import{join as u}from"node:path";import{homedir as Q}from"node:os";import{join as c,resolve as ct}from"node:path";import{existsSync as dt}from"node:fs";import{createHash as pt}from"node:crypto";var k=".qlogicagent";function Y(){return process.env.QLOGICAGENT_HOME||c(Q(),k)}function Z(){let t=process.env.QLOGIC_LLMROUTER_USER_ID?.trim();if(t)return t;let e=process.env.QLOGIC_IMPLICIT_OWNER_ID?.trim();if(e)return e;let o=process.env.QLOGIC_DEVICE_ID?.trim();return o?`oc_${o}`:"oc_local"}function X(t){let e=t.trim();if(!e)throw new Error("ownerUserId is required for profile-scoped storage");return encodeURIComponent(e).replace(/\./g,"%2E")}function p(t=Z()){return c(Y(),"profiles",X(t))}function h(){return c(p(),"skills")}function P(){return c(p(),"mcp.json")}function ee(t){if(!t)throw new Error("getProjectAgentDir: cwd is required (must not fall back to process.cwd())");return c(t,k)}function _(t){return c(ee(t),"skills-disabled.json")}import{readFileSync as te,writeFileSync as ht,mkdirSync as yt,renameSync as bt}from"node:fs";import{dirname as wt,join as oe}from"node:path";function C(){return{version:1,disabled:[]}}function x(t){try{let e=JSON.parse(te(t,"utf8"));return!e||typeof e!="object"||!Array.isArray(e.disabled)?C():{version:1,disabled:[...new Set(e.disabled.filter(r=>typeof r=="string"&&r.trim().length>0))]}}catch{return C()}}function re(){return oe(p(),"skills-disabled.json")}function A(){return new Set(x(re()).disabled)}function R(t){return new Set(x(_(t)).disabled)}var ce=["auto-skill-","test-skill-"],ue=".skills_prompt_snapshot.json";function de(t){return ce.some(e=>t.startsWith(e))}function me(t){let e=t.replace(/\r\n/g,`
|
|
3
|
+
`),o=e.match(/^---\n[\s\S]*?^version:\s*(\S+)/m),r=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:o?.[1],description:pe(a),category:r?.[1]?.trim()||void 0,author:n?.[1]?.trim()||void 0,requiredTools:D(i?.[1]),environments:D(s?.[1])}}function pe(t){let e=t.split(`
|
|
4
|
+
`),o=e.findIndex(s=>/^description:/.test(s));if(o===-1)return;let r=e[o].replace(/^description:[ \t]*/,"").trim(),n=/^[>|][+-]?$/.test(r),i=[];!n&&r&&i.push(r);for(let s=o+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,y=new Map;function fe(t,e,o,r){let n=e.has(t.name),i=o.has(t.name),s=!ye(t.meta,r);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 ge(t){let e;try{e=await se(t,{withFileTypes:!0})}catch{e=[]}let o=[];for(let s of e){if(!s.isDirectory()||s.name.startsWith("."))continue;let a=u(t,s.name),l=u(a,"SKILL.md");try{let g=await ae(l);if(!g.isFile())continue;o.push({name:s.name,filePath:l,baseDir:a,mtimeMs:g.mtimeMs,size:g.size})}catch{continue}}o.sort((s,a)=>s.name.localeCompare(a.name));let r=o.map(s=>`${s.name}:${s.mtimeMs}:${s.size}`).join("|"),n=M.get(t);if(n&&n.fingerprint===r)return n.raw;let i=[];for(let s of o){let a;try{a=await ie(s.filePath,"utf8")}catch{continue}i.push({name:s.name,filePath:s.filePath,baseDir:s.baseDir,systemGenerated:de(s.name),mtimeMs:s.mtimeMs,size:s.size,meta:me(a)})}return M.set(t,{fingerprint:r,raw:i}),be(t,i),i}async function he(t,e){let o=h(),r=A(),n=t?R(t):new Set,i=y.get(o);return i||(i=ge(o).finally(()=>y.delete(o)),y.set(o,i)),(await i).map(a=>fe(a,r,n,e))}async function E(t,e){return(await he(t,e)).filter(o=>o.active)}function I(t){let e=h(),o=u(e,t),r=u(o,"SKILL.md");try{if(ne(r).isFile())return{baseDir:o,filePath:r}}catch{}return null}function D(t){if(!t)return;let e=t.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(o=>o.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function ye(t,e){if(t.environments&&e?.currentEnvironment&&!t.environments.includes(e.currentEnvironment))return!1;if(t.requiredTools){if(!e?.availableToolNames)return!1;let o=new Set(e.availableToolNames);return t.requiredTools.every(r=>o.has(r))}return!0}async function be(t,e){try{let r={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 le(u(t,ue),JSON.stringify(r,null,2),"utf8")}catch{}}var j="astraclaw_capabilities";async function L(t){if(t.tool==="skills_list")return{handled:!0,result:{skills:(await E(t.projectRoot,{availableToolNames:t.availableToolNames,currentEnvironment:t.currentEnvironment??process.platform})).map(Te)}};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 o=I(e);if(!o)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:ve(o.filePath,"utf8")}}}if(t.tool==="mcp_connectors_list"){if(!t.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance."}};let e=t.toolCatalog.getToolManifest().filter(o=>o.function.name.startsWith("mcp__")).map(o=>({toolName:o.function.name,server:ke(o.function.name),name:Pe(o.function.name),description:o.function.description,inputSchema:o.function.parameters}));return{handled:!0,result:{tools:e,count:e.length}}}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 o=t.toolCatalog.findTool(e);if(!o?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let r=Se(t.args.args),n=`astraclaw_mcp_${we().slice(0,8)}`,i=await o.execute(n,r);return{handled:!0,result:{toolName:e,content:i.content,details:i.details,imageUrls:i.imageUrls}}}return{handled:!1}}function Te(t){return{name:t.name,description:t.description,version:t.version,category:t.category,requiredTools:t.requiredTools,environments:t.environments}}function Se(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function ke(t){let e=t.split("__");return e.length>=3?e[1]??"":""}function Pe(t){let e=t.split("__");return e.length>=3?e.slice(2).join("__"):t}import d from"node:path";import{fileURLToPath as Re}from"node:url";import{createRuntime as Me}from"mcporter";var O=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),_e=new Set([...O,"agent"]);function N(t){return t?.riskLevel?t.riskLevel:t?.isDangerous?"system":t?.isReadOnly?"read":t?.isEgress?"external_egress":"write"}var b=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let o of e)this.toolPool.set(o.name,o)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let o of e)this.addTool(o)}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,o,r,n){let i=this.toolPool.get(e);if(!i)throw new Error(`Tool not found: ${e}`);return i.execute(o,r,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let o of this.toolPool.values()){if(o.isEnabled?.()===!1)continue;let r=N(o);r==="write"&&(o.category==="media"||o.category==="web"||o.category==="mcp"?r="external_egress":(o.category==="system"||o.name==="exec")&&(r="system")),e.push({type:"function",function:{name:o.name,description:o.description,parameters:o.parameters},meta:{category:o.category??"other",displayName:o.displayName??{key:`capability.tool.${o.name}.name`,fallback:o.label},displayDescription:o.displayDescription??{key:`capability.tool.${o.name}.description`,fallback:""},parallelSafe:o.isConcurrencySafe??!1,riskLevel:r,isReadOnly:r==="read",isDangerous:r==="system",isDelete:o.isDelete??!1,isEgress:o.isEgress??r==="external_egress",egressCarriesData:o.egressCarriesData??!1}})}return e}},$=new b;function q(t){$.addTools(t)}function U(t){return $.removeTool(t)}var Ce=new Set(["describe","fetch","find","get","inspect","list","query","read","search","stat"]),xe=new Set(["append","copy","create","delete","edit","move","patch","remove","rename","set","update","upload","write"]);function F(t){let e=t.annotations,o=Ae(t.name),r=o.some(l=>xe.has(l)),n=o.some(l=>Ce.has(l)),i=e?.destructiveHint===!0||r,s=e?.readOnlyHint===!0||!i&&n,a=s||e?.idempotentHint===!0&&!i;return{isReadOnly:s,isConcurrencySafe:a,isDestructive:i}}function Ae(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var De=12e4,v=3,Ee=1e3;async function Ie(t,e){let o=e.sleep??(i=>new Promise(s=>setTimeout(s,i))),r=Math.max(1,e.attempts),n;for(let i=1;i<=r;i++)try{return await t()}catch(s){n=s,i<r&&(e.onRetry?.(i,s),await o(e.baseDelayMs*i))}throw n}var f=class{runtime=null;definitions=[];toolsByServer=new Map;injectedNames=new Set;log;workspaceRoot;toolCatalog;serverToolFilters=new Map;serverOAuth=new Map;constructor(e){this.log=e.log??{info:()=>{},warn:()=>{}},this.workspaceRoot=e.workspaceRoot?d.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(o=>!o.disabled).filter(o=>{let r=Je(o);for(let n of r)this.log.warn(`[mcp] rejected server "${o.name}": ${n.message}`);return o.tools&&this.serverToolFilters.set(o.name,o.tools),o.oauth===!0&&this.serverOAuth.set(o.name,!0),r.length===0}).map(o=>this.toServerDefinition(o)).filter(o=>o!==null)}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`),null;let r;try{r=new URL(e.url)}catch{return this.log.warn(`[mcp] server "${e.name}" has an invalid url "${e.url}", skipping`),null}return{name:e.name,command:{kind:"http",url:r,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`),null)}async connectAll(){this.definitions.length!==0&&(this.runtime=await Me({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,o)=>this.log.warn(`[mcp] ${e}${o?` (${o instanceof Error?o.message:String(o)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let o=await Ie(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:v,baseDelayMs:Ee,onRetry:(n,i)=>this.log.warn(`[mcp] connect attempt ${n}/${v} for ${e.name} failed, retrying: ${i instanceof Error?i.message:i}`)}),r=z(o,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,r),this.log.info(`[mcp] connected to ${e.name} (${r.length}/${o.length} tools)`)}catch(o){this.log.warn(`[mcp] failed to connect to ${e.name} after ${v} attempts: ${o instanceof Error?o.message:o}`)}})))}injectTools(){for(let[e,o]of this.toolsByServer){let r=this.injectPortableTools(e,o);this.log.info(`[mcp] injected ${r.length} tools from ${e}`)}}async refreshServerTools(e){if(!this.runtime)throw new Error("MCP runtime is not connected");let o=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),r=z(o,this.serverToolFilters.get(e));return this.toolsByServer.set(e,r),this.retractServerTools(e),this.injectPortableTools(e,r),r.length}getConnectedServers(){return Array.from(this.toolsByServer.keys())}getToolCount(){let e=0;for(let o of this.toolsByServer.values())e+=o.length;return e}async disconnectAll(){for(let o of this.injectedNames)this.removeRegisteredTool(o);this.injectedNames.clear(),this.toolsByServer.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,o){let r=F({name:o.name,description:o.description});return{name:`mcp__${G(e)}__${o.name}`,label:`[${e}] ${o.name}`,category:"mcp",description:o.description??`MCP tool from ${e}`,parameters:o.inputSchema??{type:"object",properties:{}},isConcurrencySafe:r.isConcurrencySafe,isReadOnly:r.isReadOnly,isDestructive:r.isDestructive,searchHint:`mcp ${e} ${o.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,o.name,{args:i,timeoutMs:De});return je(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(o=>{if(!o.execute)return o;let r=o.execute;return{...o,execute:async(n,i,s)=>{let a=Oe(o.name,i,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:r(n,i,s)}}}):e}injectPortableTools(e,o){let r=this.wrapToolsForWorkspaceBoundary(o.map(n=>this.toPortableTool(e,n)));this.registerTools(r);for(let n of r)this.injectedNames.add(n.name);return r}retractServerTools(e){let o=`mcp__${G(e)}__`;for(let r of[...this.injectedNames])r.startsWith(o)&&(this.removeRegisteredTool(r),this.injectedNames.delete(r))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):q(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??U(e)}};function je(t){let e=t&&typeof t=="object"?t:{};return{content:[{type:"text",text:(Array.isArray(e.content)?e.content:[]).filter(i=>!!i&&typeof i=="object").filter(i=>i.type==="text"&&typeof i.text=="string").map(i=>i.text).join(`
|
|
5
|
+
`)||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function G(t){return t.replace(/[^a-zA-Z0-9_]/g,"_").toLowerCase()}var Le=/(?:^|_)(path|paths|file|files|filename|filenames|dir|dirs|directory|directories|folder|folders|cwd|root|uri)(?:$|_)/i;function Oe(t,e,o){if(!o)return null;for(let r of T(e)){let n=qe(r.value,o);if(n&&!Ue(n.candidate,n.root,n.pathApi))return`Blocked: MCP tool "${t}" path "${r.value}" is outside the workspace boundary "${n.root}"`}return null}function T(t,e="",o=0){if(o>8)return[];if(typeof t=="string")return Ne(e,t)?[{key:e,value:t}]:[];if(Array.isArray(t))return t.flatMap(n=>T(n,e,o+1));if(!t||typeof t!="object")return[];let r=[];for(let[n,i]of Object.entries(t))r.push(...T(i,n,o+1));return r}function Ne(t,e){if(!Le.test(t))return!1;let o=e.trim();return!o||/^https?:\/\//i.test(o)?!1:$e(o)}function $e(t){return d.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 qe(t,e){let o=t.trim();if(/^file:\/\//i.test(o))try{o=Re(o)}catch{return null}let r=W(o)||W(e)?d.win32:d,n=r.resolve(e);return{candidate:r.isAbsolute(o)?r.resolve(o):r.resolve(n,o),root:n,pathApi:r}}function Ue(t,e,o){let r=o.relative(e,t);return r===""||!r.startsWith("..")&&!o.isAbsolute(r)}function W(t){return/^[A-Za-z]:[\\/]/.test(t)||/^\\\\/.test(t)}function J(t){if(!t||typeof t!="object")return[];let e=t,o=[],r=e.mcpServers??e.servers??e;for(let[n,i]of Object.entries(r)){if(!i||typeof i!="object")continue;let s=i;if(typeof s.url=="string"){o.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:B(s.tools)});continue}typeof s.command=="string"&&o.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:B(s.tools)})}return o}function z(t,e){let o=new Set(e?.include??[]),r=new Set(e?.exclude??[]);return t.filter(n=>o.size>0&&!o.has(n.name)?!1:!r.has(n.name))}function B(t){if(!t||typeof t!="object")return;let e=t,o=H(e.include),r=H(e.exclude);if(!(!o&&!r))return{...o?{include:o}:{},...r?{exclude:r}:{}}}function H(t){if(!Array.isArray(t))return;let e=t.filter(o=>typeof o=="string"&&o.trim().length>0);return e.length>0?e:void 0}var Fe=new Set(["bash","sh","zsh","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]),Ge=new Set(["-c","/c","-command","-encodedcommand","-e"]),We=["curl","wget","invokewebrequest","invoke-restmethod","iwr","irm","nc","netcat"],ze=["env","processenv","apikey","api_key","token","secret","credential","credentials","awsaccesskey","awssecret"],Be=["authorizedkeys","sshknownhosts","crontab","launchctl","schtasks","startup","runkey","runonce","bashrc","profile"],He=/\bssh-(?:rsa|ed25519|ecdsa-[^\s]+)\s+[A-Za-z0-9+/=]{40,}/i;function Je(t){let e=[],o=[t.command??"",...t.args??[],...Object.keys(t.env??{}),...Object.values(t.env??{})].join(`
|
|
6
|
+
`);if(He.test(o)&&e.push({code:"ioc_public_key",message:"configuration contains an inline SSH public key IOC"}),(t.type??(t.url?"http":"stdio"))!=="stdio"||!Ve(t.command))return e;let r=Ke(t.args??[]);if(!r)return e;let n=Qe(r);return w(n,Be)&&e.push({code:"persistence",message:"stdio shell command attempts a persistence write"}),w(n,We)&&w(n,ze)&&e.push({code:"dangerous_egress",message:"stdio shell command combines network egress with likely secret access"}),e}function Ve(t){if(!t)return!1;let e=d.basename(t).toLowerCase();return Fe.has(e)}function Ke(t){for(let e=0;e<t.length;e++){let o=t[e]?.toLowerCase();if(o&&Ge.has(o))return t.slice(e+1).join(" ")}return""}function Qe(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"")}function w(t,e){return e.some(o=>t.includes(o))}var et=[{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}}],S=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 o of e)this.addTool(o)}removeTool(e){return this.tools.delete(e)}},m=null,tt=Ye({input:process.stdin,crlfDelay:Number.POSITIVE_INFINITY});tt.on("line",t=>{ot(t)});async function ot(t){let e=t.trim();if(!e)return;let o;try{o=JSON.parse(e)}catch{return}if(o.id===void 0||o.id===null)return o.method==="notifications/initialized",void 0;try{let r=await rt(o);K({jsonrpc:"2.0",id:o.id,result:r})}catch(r){K({jsonrpc:"2.0",id:o.id,error:{code:-32603,message:r instanceof Error?r.message:String(r)}})}}async function rt(t){switch(t.method){case"initialize":return{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:j,version:"1.0.0"}};case"tools/list":return{tools:et};case"tools/call":{let e=V(t.params),o=typeof e.name=="string"?e.name:"",r=V(e.arguments),n=nt(),i=o==="mcp_connectors_list"||o==="mcp_tool_call"?await st(n):void 0,s=await L({tool:o,args:r,projectRoot:n,toolCatalog:i});if(!s.handled)throw new Error(`Unknown tool: ${o}`);return{content:[{type:"text",text:JSON.stringify(s.result)}]}}default:throw new Error(`Unknown method: ${t.method}`)}}function nt(){return process.env.ASTRACLAW_PROJECT_ROOT||process.env.QLOGICAGENT_PROJECT_ROOT||process.cwd()}async function st(t){if(m?.projectRoot===t)return await m.ready,m.catalog;m?.manager?.disconnectAll().catch(()=>{});let e=new S,o=new f({servers:it(),workspaceRoot:t,toolCatalog:e,log:{info:n=>process.stderr.write(`${n}
|
|
6
7
|
`),warn:n=>process.stderr.write(`${n}
|
|
7
|
-
`)}}),r=o.connectAll().then(()=>o.injectTools());return m={projectRoot:t,catalog:e,manager:o,ready:r},await r,e}function
|
|
8
|
+
`)}}),r=o.connectAll().then(()=>o.injectTools());return m={projectRoot:t,catalog:e,manager:o,ready:r},await r,e}function it(){let t=process.env.ASTRACLAW_MCP_CONFIG_PATH||P();if(!Ze(t))return[];try{let e=Xe(t,"utf8").replace(/^\uFEFF/,"");return J(JSON.parse(e))}catch(e){return process.stderr.write(`[astraclaw-native-mcp] failed to read MCP config: ${e instanceof Error?e.message:String(e)}
|
|
8
9
|
`),[]}}function V(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:{}}function K(t){process.stdout.write(`${JSON.stringify(t)}
|
|
9
10
|
`)}
|
|
@@ -64,6 +64,12 @@ export interface ToolLoopParams {
|
|
|
64
64
|
* or the same array reference if unchanged.
|
|
65
65
|
*/
|
|
66
66
|
refreshTools?: () => ToolDefinition[];
|
|
67
|
+
/**
|
|
68
|
+
* Drain user guidance queued MID-TURN (队列"运行中引导"). Called at the top of
|
|
69
|
+
* every loop round; each returned string is appended as a user message before
|
|
70
|
+
* the next LLM call, steering the run without waiting for the turn to finish.
|
|
71
|
+
*/
|
|
72
|
+
drainPendingGuidance?: () => string[];
|
|
67
73
|
/**
|
|
68
74
|
* Post-sampling hooks -fire-and-forget after LLM response completes (CC executePostSamplingHooks parity).
|
|
69
75
|
* Cannot modify messages. Used for background tasks (memory extraction, skill learning, etc.).
|
|
@@ -88,6 +88,11 @@ export interface TurnRequest {
|
|
|
88
88
|
* become callable in the SAME turn instead of only on the next user message.
|
|
89
89
|
*/
|
|
90
90
|
refreshTools?: () => ToolDefinition[];
|
|
91
|
+
/**
|
|
92
|
+
* Drain user guidance queued mid-turn (运行中引导). Consulted at the top of every
|
|
93
|
+
* tool-loop round; returned strings become user messages before the next LLM call.
|
|
94
|
+
*/
|
|
95
|
+
drainPendingGuidance?: () => string[];
|
|
91
96
|
systemPrompt?: string;
|
|
92
97
|
config?: TurnConfig;
|
|
93
98
|
}
|
|
@@ -69,6 +69,15 @@ export declare function handleAgentsProcesses(this: AgentsHandlerHost, msg: Agen
|
|
|
69
69
|
export declare function handleAgentsKill(this: AgentsHandlerHost, msg: AgentRpcRequest): void;
|
|
70
70
|
export declare function handleAgentsGetLog(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
71
71
|
export declare function handleAgentsTestConnection(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
72
|
+
/** Formats a session persisted transcript into a compact prior-conversation block for an external
|
|
73
|
+
* ACP agent that was cold-spawned (fresh process, empty session). Mirrors the built-in agent
|
|
74
|
+
* withResumedHistory: the engine on-disk history is authoritative, so we re-supply it and the agent
|
|
75
|
+
* regains full context after a restart/eviction. Only user/assistant text turns are included;
|
|
76
|
+
* returns "" when there is no prior history (e.g. the first turn of a new session). */
|
|
77
|
+
export declare function buildExternalHistoryPreamble(messages: Array<{
|
|
78
|
+
role: string;
|
|
79
|
+
content: unknown;
|
|
80
|
+
}>): string;
|
|
72
81
|
/** Pre-warm an external agent's process (on agent-select) so the first message skips the cold start. */
|
|
73
82
|
export declare function handleAgentsWarm(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
74
83
|
export declare function handleAgentsPrompt(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* config.get/config.update only operate on tunables and non-model settings.
|
|
8
8
|
*/
|
|
9
9
|
import { type AgentRpcError, type AgentRpcRequest } from "../../protocol/wire/index.js";
|
|
10
|
+
import type { PermissionMode } from "../../runtime/ports/index.js";
|
|
10
11
|
import type { BackgroundTaskManager } from "../../runtime/infra/background-tasks.js";
|
|
11
12
|
import type { TaskStore } from "../../runtime/infra/task-runtime.js";
|
|
12
13
|
import type { ToolCatalog } from "../../runtime/ports/index.js";
|
|
@@ -15,6 +16,10 @@ export interface ConfigHandlerHost {
|
|
|
15
16
|
backgroundTasks: BackgroundTaskManager;
|
|
16
17
|
toolCatalog?: ToolCatalog;
|
|
17
18
|
syncToolListMediaConfig?(): void;
|
|
19
|
+
/** 权限热应用:config.update 写盘后立即同步活的 rule engine(否则要等下一轮 _meta/重启)。 */
|
|
20
|
+
applyPermissionConfig?(perm: {
|
|
21
|
+
mode: PermissionMode;
|
|
22
|
+
}): void;
|
|
18
23
|
sendResponse(id: string | number, result?: unknown, error?: AgentRpcError): void;
|
|
19
24
|
}
|
|
20
25
|
export declare function handleToolsList(this: ConfigHandlerHost, msg: AgentRpcRequest): void;
|
|
@@ -13,6 +13,13 @@ export interface FilesHandlerHost {
|
|
|
13
13
|
export declare function resolveProjectDir(projectId: string): string | null;
|
|
14
14
|
export declare function handleFilesList(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
15
15
|
export declare function handleFilesCreate(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* files.readLocal — read an ABSOLUTE local path for chat file-reference preview.
|
|
18
|
+
* Workspace-relative reads keep going through files.read; this endpoint serves the
|
|
19
|
+
* "user/agent mentioned a file outside every project workspace" preview gap (the
|
|
20
|
+
* media proxy only serves media extensions, so .txt/.log etc. had no channel).
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleFilesReadLocal(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
16
23
|
export declare function handleFilesRead(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
17
24
|
export declare function handleFilesRename(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
18
25
|
export declare function handleFilesDelete(this: FilesHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { SessionState } from "../../runtime/session/session-state.js";
|
|
14
14
|
import { createMemoryPrefetchState } from "../../runtime/hooks/memory-hooks.js";
|
|
15
|
+
import { type NativeSessionEntry } from "../../runtime/session/native/native-session-source.js";
|
|
15
16
|
import { type AgentRpcError, type AgentRpcErrorCode, type AgentRpcRequest } from "../../protocol/wire/index.js";
|
|
16
17
|
import type { SessionHistoryCoordinator } from "../session-history-coordinator.js";
|
|
17
18
|
export interface SessionHandlerHost {
|
|
@@ -32,7 +33,49 @@ export declare function handleThreadCreate(this: SessionHandlerHost, msg: AgentR
|
|
|
32
33
|
export declare function handleThreadList(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
33
34
|
export declare function handleSessionCreate(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
34
35
|
export declare function handleSessionResolve(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve an agentId's sessionHistory capability ("native-cwd" vs "own") for session.list routing.
|
|
38
|
+
*
|
|
39
|
+
* Mirrors the hard invariant already encoded in {@link assembleBackendDescriptor} (acp-detector.ts):
|
|
40
|
+
* only codex/claude are adapted for native CWD-scoped session discovery — every other agentId,
|
|
41
|
+
* including undefined/missing (the default for all existing callers), custom agents, and unknown
|
|
42
|
+
* ids, resolves to "own". This is intentionally NOT a live/async descriptor lookup (that would
|
|
43
|
+
* require an AcpDetector scan — CLI probing — on the session.list hot path); it keys off the static
|
|
44
|
+
* ACP_BACKENDS registry so the two places can never drift on WHICH ids are native-adapted, without
|
|
45
|
+
* paying scan cost. Cut 1 does not distinguish installed vs not-installed codex/claude here (unlike
|
|
46
|
+
* assembleBackendDescriptor's not_installed branch) because resolveSessionSource's "no registered
|
|
47
|
+
* adapter" fallback already yields [] for native-cwd either way — the distinction has no observable
|
|
48
|
+
* effect until Cut 2 lands a real adapter.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveAgentSessionHistory(agentId?: string): "native-cwd" | "own";
|
|
51
|
+
/**
|
|
52
|
+
* Select the session source for session.list: "own" (default, includes missing/unknown agentId)
|
|
53
|
+
* returns the caller's already-assembled own entries UNCHANGED (same reference — zero-regression
|
|
54
|
+
* contract for every existing caller, none of which pass agentId today); "native-cwd" routes to a
|
|
55
|
+
* registered {@link NativeSessionSource} for that agentId, or an empty list when none is registered
|
|
56
|
+
* yet (Cut 1: always, until Cut 2 lands codex/claude adapters) or cwd is unavailable.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveSessionSource<T>(args: {
|
|
59
|
+
agentId?: string;
|
|
60
|
+
capability?: "native-cwd" | "own";
|
|
61
|
+
cwd?: string;
|
|
62
|
+
limit?: number;
|
|
63
|
+
ownEntries: T[];
|
|
64
|
+
}): Promise<T[] | NativeSessionEntry[]>;
|
|
35
65
|
export declare function handleSessionList(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Loads the full transcript of a single native (third-party-agent-owned) session, identified by
|
|
68
|
+
* the opaque `nativeRef` a prior session.list call returned in a `source: "native"` entry. Read-only
|
|
69
|
+
* and idempotent — the frontend calls this when a user opens a native session in the UI.
|
|
70
|
+
*
|
|
71
|
+
* Unlike handleSessionList (which falls back to [] on ANY unresolvable state — missing capability,
|
|
72
|
+
* missing cwd, no registered adapter), missing/unregistered here has exactly one meaning (no adapter
|
|
73
|
+
* registered for that agentId, or no nativeRef was sent at all) and resolves the same way: an empty
|
|
74
|
+
* transcript, NOT an error. Only an actual adapter failure (loadTranscript rejecting) is an error —
|
|
75
|
+
* that distinction matters because "no adapter yet" is an expected state during rollout (e.g. Cut 2
|
|
76
|
+
* ships claude only; codex nativeRefs would still 404-equivalent to [] until Cut 3), not a bug.
|
|
77
|
+
*/
|
|
78
|
+
export declare function handleSessionLoadNativeTranscript(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
36
79
|
export declare function handleSessionGet(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
37
80
|
export declare function handleSessionGetMessages(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
38
81
|
export declare function handleSessionUpdate(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
@@ -18,7 +18,19 @@ export interface TurnControlHandlerHost {
|
|
|
18
18
|
configureTurnMedia?(config: Record<string, unknown> | undefined, turnId: string): void;
|
|
19
19
|
log(message: string): void;
|
|
20
20
|
sendResponse(id: string | number, result?: unknown, error?: unknown): void;
|
|
21
|
+
/** Mid-turn guidance queue (运行中引导) — drained by the tool loop each round. */
|
|
22
|
+
pendingTurnGuidance?: string[];
|
|
23
|
+
currentSessionId?: string;
|
|
24
|
+
currentTurnId?: string;
|
|
25
|
+
getActiveProjectRoot?(): string;
|
|
21
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* turn.guide — queue user guidance INTO the running turn (队列"运行中引导").
|
|
29
|
+
* The text lands in the tool loop's next round as a user message, and is
|
|
30
|
+
* persisted to the session transcript so later turns keep it in history.
|
|
31
|
+
* Fails when no turn is active (caller falls back to the plain queue).
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleTurnGuide(this: TurnControlHandlerHost, msg: AgentRpcRequest): void;
|
|
22
34
|
/**
|
|
23
35
|
* First-turn session-title generation, extracted so the caller can run it
|
|
24
36
|
* fire-and-forget (see run()). Behavior is identical to the previous inline
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist "始终允许此文件夹" approvals into the USER-level settings.json
|
|
3
|
+
* (`~/.qlogicagent/profiles/<owner>/settings.json` → permissions.rules).
|
|
4
|
+
*
|
|
5
|
+
* Same file config.update writes and the settings-watcher's user layer reads,
|
|
6
|
+
* so a persisted rule survives restarts via loadInitialSettings/watchSettings.
|
|
7
|
+
* turn-permission-sync leaves rules alone when a frame carries only `{ mode }`,
|
|
8
|
+
* so desktop turns cannot wipe these entries.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Append an allow rule for `pathPrefix` to the user settings, preserving the rest
|
|
12
|
+
* of the file. Idempotent: an existing allow rule with the same prefix is kept as-is.
|
|
13
|
+
*/
|
|
14
|
+
export declare function persistUserDirectoryAllowRule(rule: {
|
|
15
|
+
pathPrefix: string;
|
|
16
|
+
}): Promise<void>;
|
|
@@ -3,6 +3,9 @@ import type { ModelPurpose } from "../runtime/infra/model-registry.js";
|
|
|
3
3
|
import type { RpcHandlerHosts } from "./rpc-registry.js";
|
|
4
4
|
type DirectStdioRpcHost = RpcHandlerHosts["config"] & RpcHandlerHosts["control"] & RpcHandlerHosts["files"] & RpcHandlerHosts["skills"] & RpcHandlerHosts["project"] & RpcHandlerHosts["community"] & RpcHandlerHosts["settings"] & RpcHandlerHosts["petConfirm"] & {
|
|
5
5
|
currentTurnId?: string;
|
|
6
|
+
/** Mid-turn guidance queue (运行中引导) — shared between handleTurnGuide and the tool loop's drain. */
|
|
7
|
+
pendingTurnGuidance?: string[];
|
|
8
|
+
getActiveProjectRoot(): string;
|
|
6
9
|
readonly multiAgentHandlerHost: RpcHandlerHosts["multiAgent"];
|
|
7
10
|
readonly workflowHandlerHost: RpcHandlerHosts["workflow"];
|
|
8
11
|
};
|
|
@@ -39,6 +42,7 @@ type StdioRpcAdapterSource = DirectStdioRpcHost & {
|
|
|
39
42
|
sendError: NonNullable<RpcHandlerHosts["session"]["sendError"]>;
|
|
40
43
|
createDreamHandlerHost(): RpcHandlerHosts["dream"];
|
|
41
44
|
};
|
|
45
|
+
export declare function createTurnHost(host: StdioRpcAdapterSource): RpcHandlerHosts["turn"];
|
|
42
46
|
/**
|
|
43
47
|
* Build the per-domain handler-host bundle the RPC registry binds to.
|
|
44
48
|
*
|
|
@@ -25,7 +25,7 @@ import { type MultiAgentHandlerHost } from "./multi-agent-handler-host.js";
|
|
|
25
25
|
import type { WorkflowHandlerHost } from "./handlers/workflow-handler.js";
|
|
26
26
|
import { SessionHistoryCoordinator } from "./session-history-coordinator.js";
|
|
27
27
|
import { PetConfirmCoordinator } from "./pet-confirm-coordinator.js";
|
|
28
|
-
import type { MemoryDreamRuntimeProvider, MemoryHandlerProvider, ProjectMemoryStore } from "../runtime/ports/index.js";
|
|
28
|
+
import type { MemoryDreamRuntimeProvider, MemoryHandlerProvider, PermissionMode, ProjectMemoryStore } from "../runtime/ports/index.js";
|
|
29
29
|
export interface StdioServerConfig {
|
|
30
30
|
verbose: boolean;
|
|
31
31
|
transport?: Transport;
|
|
@@ -106,6 +106,13 @@ export declare class StdioServer {
|
|
|
106
106
|
set currentSessionId(v: string);
|
|
107
107
|
get currentTurnId(): string;
|
|
108
108
|
set currentTurnId(v: string);
|
|
109
|
+
/**
|
|
110
|
+
* Mid-turn guidance queue (运行中引导). Reset per turn by runTurnPipeline, pushed by
|
|
111
|
+
* handleTurnGuide (via the turn host proxy), drained each round by the tool loop.
|
|
112
|
+
* Declared as a real field so the proxy in createTurnHost has a shared backing store
|
|
113
|
+
* (turn pipeline and handleTurnGuide must see the SAME array).
|
|
114
|
+
*/
|
|
115
|
+
pendingTurnGuidance: string[];
|
|
109
116
|
/** LH-10: an explicit cancel of a session stops the background tasks that
|
|
110
117
|
* session launched — the forked agent + the exec processes it spawned —
|
|
111
118
|
* instead of leaving them running as zombies. Keyed by session id, which is
|
|
@@ -121,6 +128,14 @@ export declare class StdioServer {
|
|
|
121
128
|
get currentHooks(): import("../contracts/hooks.js").HookRegistry | null;
|
|
122
129
|
get permissionChecker(): AgentRuntimeSessionState["permissionChecker"];
|
|
123
130
|
private set permissionChecker(value);
|
|
131
|
+
/**
|
|
132
|
+
* config.update 权限热应用:写盘的同时立即更新活的 rule engine,
|
|
133
|
+
* 当前会话(乃至运行中 turn)的下一次工具调用就按新模式判定——
|
|
134
|
+
* 不再等下一轮 _meta 或重启。settings-watcher 仍监听文件作为外部改动兜底。
|
|
135
|
+
*/
|
|
136
|
+
applyPermissionConfig(perm: {
|
|
137
|
+
mode: PermissionMode;
|
|
138
|
+
}): void;
|
|
124
139
|
private set mcpManager(value);
|
|
125
140
|
get memoryProvider(): MemoryHandlerProvider | null;
|
|
126
141
|
set memoryProvider(value: MemoryHandlerProvider | null);
|
|
@@ -16,6 +16,8 @@ export interface TurnCoreRequest {
|
|
|
16
16
|
tools: ToolDefinition[];
|
|
17
17
|
/** Live tool-manifest rebuild for same-turn activation (forwarded to the loop). */
|
|
18
18
|
refreshTools?: () => ToolDefinition[];
|
|
19
|
+
/** Mid-turn guidance drain (运行中引导) — forwarded to the tool loop. */
|
|
20
|
+
drainPendingGuidance?: () => string[];
|
|
19
21
|
systemPrompt: string;
|
|
20
22
|
config: TurnConfig;
|
|
21
23
|
}
|
|
@@ -121,6 +121,10 @@ export declare class ProductOrchestrator {
|
|
|
121
121
|
*/
|
|
122
122
|
replayTask(productId: string, taskId: string): Promise<void>;
|
|
123
123
|
/** Get product status. */
|
|
124
|
+
/** P2-1: the native (codex/claude) session id captured for a delegation's single worker, so the
|
|
125
|
+
* DelegationCoordinator can resume it on the next same-(chat×agent) delegation. undefined for a
|
|
126
|
+
* multi-instance product, an own agent, or before the worker's first task settles. */
|
|
127
|
+
getDelegationNativeSessionId(productId: string): string | undefined;
|
|
124
128
|
getStatus(productId: string): ProductStatus | null;
|
|
125
129
|
/** Live DAG progress snapshot for a product (for the planner's execution-phase leader). */
|
|
126
130
|
getProgressSnapshot(productId: string): {
|
|
@@ -15,6 +15,16 @@ type DelegationStatus = "running" | "completed" | "failed" | "cancelled";
|
|
|
15
15
|
export declare class DelegationCoordinator {
|
|
16
16
|
private readonly deps;
|
|
17
17
|
private readonly active;
|
|
18
|
+
/** Per-(chat×agent) tail promise — the next same-conversation delegation chains after it so they run
|
|
19
|
+
* strictly sequentially (single writer to the shared reused worktree + native session). One entry per
|
|
20
|
+
* active (chat×agent) pair (keyed, overwritten — not per-delegation), so it stays bounded. */
|
|
21
|
+
private readonly convChains;
|
|
22
|
+
/** Per-(chat×agent) native (codex/claude) session id — the background analogue of the foreground U5
|
|
23
|
+
* convergence. Seeded from the first delegation's captured session id; passed as resumeNativeSessionId on
|
|
24
|
+
* every subsequent same-conversation delegation so context accumulates (git AND non-git projects).
|
|
25
|
+
* In-memory (one entry per active pair); an engine restart resets it → the next delegation starts fresh,
|
|
26
|
+
* same as a cold foreground turn whose pool was evicted. */
|
|
27
|
+
private readonly nativeSessions;
|
|
18
28
|
constructor(deps: DelegationDeps);
|
|
19
29
|
/** Active + recent delegations for this project (x/mention.list). */
|
|
20
30
|
list(): {
|
|
@@ -30,6 +30,10 @@ export interface PersistedProductState {
|
|
|
30
30
|
* PlanningSession is strings / arrays / plain objects (no functions, dates, or class instances).
|
|
31
31
|
*/
|
|
32
32
|
leaderDialogue?: PlanningSession;
|
|
33
|
+
/** P2-1: run tasks in the project cwd instead of per-task worktrees (see
|
|
34
|
+
* ProductCreateParams.runInProjectCwd) — persisted so a delegation resumed after an engine restart
|
|
35
|
+
* keeps running in the project cwd, not a fresh isolated worktree. */
|
|
36
|
+
runInProjectCwd?: boolean;
|
|
33
37
|
}
|
|
34
38
|
export interface ProductPersistenceOptions {
|
|
35
39
|
pathService?: PathService;
|
|
@@ -337,6 +337,22 @@ export interface ProductCreateParams {
|
|
|
337
337
|
maxTotalTokens?: number;
|
|
338
338
|
maxDuration?: number;
|
|
339
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* P2-1: run ALL tasks directly in the project cwd (`cwd`) instead of isolated per-task git worktrees.
|
|
342
|
+
* Set by DelegationCoordinator so a background @mention shares the project workspace exactly like
|
|
343
|
+
* foreground @codex — its native session then lives under a STABLE cwd (git or not) and resumes across
|
|
344
|
+
* delegations. Omitted everywhere else (per-task worktree isolation stays the default for parallel
|
|
345
|
+
* product/goal/solo tasks).
|
|
346
|
+
*/
|
|
347
|
+
runInProjectCwd?: boolean;
|
|
348
|
+
/**
|
|
349
|
+
* P2-1: native (codex/claude) session id to RESUME for this delegation (ACP session/load), so repeated
|
|
350
|
+
* background @mention to the same (chat×agent) accumulates context — the background analogue of the
|
|
351
|
+
* foreground U4 resume. The DelegationCoordinator tracks it per (chat×agent): undefined on the first
|
|
352
|
+
* delegation (→ fresh session, whose id it then captures), the prior id thereafter. Validated by `has()`
|
|
353
|
+
* before use, so a stale id falls back to a fresh session. Git-independent (id, not cwd, drives reuse).
|
|
354
|
+
*/
|
|
355
|
+
resumeNativeSessionId?: string;
|
|
340
356
|
}
|
|
341
357
|
/**
|
|
342
358
|
* product.plan RPC params — starts an interactive planning session.
|
|
@@ -20,6 +20,8 @@ export interface AgentCapabilities {
|
|
|
20
20
|
supportsResume: boolean;
|
|
21
21
|
supportsUsageUpdate: boolean;
|
|
22
22
|
skillsDirs?: string[];
|
|
23
|
+
/** 会话历史来源:native-cwd=有本地按 cwd 可发现的原生会话;省略/own=只显示自有会话 */
|
|
24
|
+
sessionHistory?: "native-cwd" | "own";
|
|
23
25
|
}
|
|
24
26
|
/** Descriptor for a discovered or registered agent. */
|
|
25
27
|
export interface AgentDescriptor {
|
|
@@ -719,6 +721,7 @@ export interface AgentRpcMethodMap extends GatewayRpcMethodMap {
|
|
|
719
721
|
params: {
|
|
720
722
|
modelId: string;
|
|
721
723
|
enabled: boolean;
|
|
724
|
+
purpose?: string;
|
|
722
725
|
};
|
|
723
726
|
result: {
|
|
724
727
|
ok: true;
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Reference: AionUI AcpDetector + ACP_BACKENDS_ALL registry.
|
|
8
8
|
*/
|
|
9
|
-
import type { AcpBackendConfig, AgentDescriptor, AgentConfigStoreData, AuthPreference } from "../../protocol/wire/acp-agent-management.js";
|
|
9
|
+
import type { AcpBackendConfig, AgentDescriptor, CustomAgentDef, AgentConfigStoreData, AuthPreference } from "../../protocol/wire/acp-agent-management.js";
|
|
10
10
|
export declare const ACP_BACKENDS: Record<string, AcpBackendConfig>;
|
|
11
11
|
export declare const AGENT_CATALOG_IDS: readonly ["claude", "codex", "gemini", "copilot", "cursor", "qwen", "kimi", "glm", "opencode", "kiro", "qoder", "openclaw", "hermes"];
|
|
12
12
|
/** Merged catalog entry (base ACP config + grid/install metadata) for one id. */
|
|
13
13
|
export declare function getCatalogEntry(id: string): AcpBackendConfig | undefined;
|
|
14
14
|
/** Ordered curated catalog: base ACP config merged with grid/install metadata. */
|
|
15
15
|
export declare function getAgentCatalog(): AcpBackendConfig[];
|
|
16
|
+
export declare function buildInternalAgentDescriptors(): AgentDescriptor[];
|
|
16
17
|
export declare function resolveWindowsCopilotLoader(): string | null;
|
|
17
18
|
export declare function getNpmInstallPackageName(command: string | undefined): string | null;
|
|
18
19
|
export declare function compareSemverVersions(a: string, b: string): number;
|
|
@@ -134,6 +135,24 @@ export declare function resolveCatalogKeyEnv(entry: AcpBackendConfig | undefined
|
|
|
134
135
|
* Returns [] when not in account mode (or the agent has no account path — claude is coerced to auto).
|
|
135
136
|
*/
|
|
136
137
|
export declare function resolveSuppressEnvKeys(entry: AcpBackendConfig | undefined, mode: AuthPreference, sources?: CatalogKeySources): string[];
|
|
138
|
+
/**
|
|
139
|
+
* Build a backend's descriptor from already-resolved cliPath/version (+ the caller-resolved
|
|
140
|
+
* hasConfig flag). Shared by AcpDetector's sync and async detection paths so they can never drift
|
|
141
|
+
* (status rules, copilot's version-required gate, etc.) — extracted to a standalone, IO-free, pure
|
|
142
|
+
* function (not a class method) so it is directly unit-testable without spawning a real CLI.
|
|
143
|
+
*
|
|
144
|
+
* sessionHistory (Task 1.2, third-party native session discovery): "native-cwd" only for an
|
|
145
|
+
* INSTALLED (status "available") codex or claude backend — the two agents whose native CWD-scoped
|
|
146
|
+
* session stores we've actually adapted to. Every other case — not-installed codex/claude, any other
|
|
147
|
+
* backend regardless of install state — falls back to "own". Unadapted agents ALWAYS get "own"; this
|
|
148
|
+
* is a hard invariant, not a default that later widens implicitly.
|
|
149
|
+
*/
|
|
150
|
+
export declare function assembleBackendDescriptor(backend: AcpBackendConfig, cliPath: string | null, version: string | null, hasConfig: boolean): AgentDescriptor;
|
|
151
|
+
/** Build a user-registered custom agent's descriptor. Extracted to a standalone, IO-free, pure
|
|
152
|
+
* function for the same testability reason as {@link assembleBackendDescriptor}. Custom agents are
|
|
153
|
+
* never adapted for native session discovery — regardless of what id/name the user gives them —
|
|
154
|
+
* so sessionHistory is always "own" (the hard own-by-default invariant). */
|
|
155
|
+
export declare function assembleCustomAgentDescriptor(custom: CustomAgentDef, cliPath: string | null, hasConfig: boolean): AgentDescriptor;
|
|
137
156
|
export declare class AcpDetector {
|
|
138
157
|
private cache;
|
|
139
158
|
private configStore;
|
|
@@ -176,13 +195,9 @@ export declare class AcpDetector {
|
|
|
176
195
|
private detectBackend;
|
|
177
196
|
/** Async, NON-BLOCKING twin of {@link detectBackend} — identical detection, async probes. */
|
|
178
197
|
private detectBackendAsync;
|
|
179
|
-
/** Build a backend's descriptor from already-resolved cliPath/version. Shared by the sync and async
|
|
180
|
-
* detection paths so they can never drift (status rules, copilot's version-required gate, etc.). */
|
|
181
|
-
private assembleBackendDescriptor;
|
|
182
198
|
private detectCustomAgent;
|
|
183
199
|
/** Async, NON-BLOCKING twin of {@link detectCustomAgent}. */
|
|
184
200
|
private detectCustomAgentAsync;
|
|
185
|
-
private assembleCustomAgentDescriptor;
|
|
186
201
|
private hasAgentConfig;
|
|
187
202
|
/**
|
|
188
203
|
* Descriptor for qlogicagent participating in Solo/Product as a self-hosted ACP agent.
|
|
@@ -201,6 +216,21 @@ export declare class AcpDetector {
|
|
|
201
216
|
* Returns null if the agent is not available.
|
|
202
217
|
*/
|
|
203
218
|
buildExternalDescriptor(agentId: string): import("../../protocol/wire/acp-agent-management.js").ExternalAgentDescriptor | null;
|
|
219
|
+
/**
|
|
220
|
+
* Async, NON-BLOCKING twin of {@link buildExternalDescriptor} — identical output for the same
|
|
221
|
+
* detector state, but resolves the agent list via {@link scanAsync} (a real probe, awaited, when
|
|
222
|
+
* the cache is cold) instead of the sync {@link list}'s cache-or-empty shortcut. Load-bearing for
|
|
223
|
+
* `agents.warm` (agents-handler.ts): the sync path's detect calls are cache-fronted today, but
|
|
224
|
+
* that non-blocking guarantee is an implementation detail of `list()` — routing the RPC path
|
|
225
|
+
* through this method instead makes "never synchronously probe on this path" an explicit
|
|
226
|
+
* contract that can't silently regress if `list()`'s internals change later.
|
|
227
|
+
*/
|
|
228
|
+
buildExternalDescriptorAsync(agentId: string): Promise<import("../../protocol/wire/acp-agent-management.js").ExternalAgentDescriptor | null>;
|
|
229
|
+
/** Shared descriptor-assembly logic for {@link buildExternalDescriptor} and
|
|
230
|
+
* {@link buildExternalDescriptorAsync} — everything AFTER "locate this agentId's AgentDescriptor
|
|
231
|
+
* in an already-resolved list", so the sync/async twins can never drift on behavior, only on how
|
|
232
|
+
* they obtain `agents`. */
|
|
233
|
+
private assembleExternalDescriptorFromAgents;
|
|
204
234
|
/**
|
|
205
235
|
* Whether an agent is ready to compete in Solo/Product: installed/available AND configured.
|
|
206
236
|
* qlogicagent self-resolves from the owner profile (always ready). External agents need a usable
|
|
@@ -151,7 +151,20 @@ export declare class AcpProtocolAdapter {
|
|
|
151
151
|
mcpServers?: McpServerConfig[];
|
|
152
152
|
additionalDirectories?: string[];
|
|
153
153
|
systemPrompt?: string;
|
|
154
|
+
/** Platform/managed model to select over ACP after session/new (see selectSessionModel). */
|
|
155
|
+
model?: string;
|
|
154
156
|
}): Promise<AcpSessionResult>;
|
|
157
|
+
/**
|
|
158
|
+
* Select the platform/managed model over ACP after session/new. External CLIs (notably
|
|
159
|
+
* claude-code-acp) default to their OWN model (e.g. Sonnet 4.5) and merely EXPOSE the injected
|
|
160
|
+
* ANTHROPIC_MODEL/OPENAI_MODEL as an "available" option — they do NOT select it. Left on the default,
|
|
161
|
+
* the first prompt fails ("issue with the selected model …") because the platform shelf has no such
|
|
162
|
+
* model, and it surfaces as a cryptic "Agent process stdio closed". Best-effort: when the agent
|
|
163
|
+
* advertised its models, only switch if it offers `desired` and isn't already on it; otherwise attempt
|
|
164
|
+
* anyway and swallow rejection (the session still exists on its default model — the wrong-model prompt
|
|
165
|
+
* error is the backstop). Uses the generic sendRequest because the SDK has no typed set_model yet.
|
|
166
|
+
*/
|
|
167
|
+
private selectSessionModel;
|
|
155
168
|
/**
|
|
156
169
|
* Send a prompt to a running ACP session (session/prompt). Content streams back via session/update
|
|
157
170
|
* notifications during execution; the response carries only { stopReason, usage? }. On timeout the
|
|
@@ -165,6 +178,7 @@ export declare class AcpProtocolAdapter {
|
|
|
165
178
|
resumeSession(_child: ChildProcess, sessionId: string, options?: {
|
|
166
179
|
cwd?: string;
|
|
167
180
|
mcpServers?: McpServerConfig[];
|
|
181
|
+
model?: string;
|
|
168
182
|
}): Promise<AcpSessionResult>;
|
|
169
183
|
/**
|
|
170
184
|
* Translate an ACP notification into qlogicagent's internal format.
|
|
@@ -52,6 +52,14 @@ export interface AgentProcessConfig {
|
|
|
52
52
|
mcpServers?: McpServerConfig[];
|
|
53
53
|
/** System prompt to inject into ACP session. */
|
|
54
54
|
systemPrompt?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Cut 5 — RESUME a third-party native session (ACP session/load, replays history) instead of
|
|
57
|
+
* creating a fresh one. The id is the agent's OWN native session id (codex rollout id / claude
|
|
58
|
+
* UUID). Only honored when the agent advertised loadSession at initialize (handle.supportsResume);
|
|
59
|
+
* otherwise spawn throws — we never silently open a blank session ("fake continuation" is forbidden
|
|
60
|
+
* by the Phase 2 design).
|
|
61
|
+
*/
|
|
62
|
+
resumeSessionId?: string;
|
|
55
63
|
}
|
|
56
64
|
export declare function buildExternalAcpSpawnCommand(cliPath: string, acpArgs: string[]): {
|
|
57
65
|
command: string;
|
|
@@ -296,6 +304,13 @@ export declare class AgentProcessManager {
|
|
|
296
304
|
isAlive(memberId: string): boolean;
|
|
297
305
|
/** Get usage tracker for an ACP agent (returns null for internal agents). */
|
|
298
306
|
getUsageTracker(memberId: string): AcpUsageTracker | null;
|
|
307
|
+
/**
|
|
308
|
+
* The agent-assigned native session id (from ACP session/new or session/load) for a pooled member,
|
|
309
|
+
* once its session exists — undefined before creation. For native-cwd agents (codex/claude) this is
|
|
310
|
+
* the id under which the agent persists its OWN transcript (e.g. claude's ~/.claude uuid), distinct
|
|
311
|
+
* from the engine/stream id; used to converge on a single session identity (own/native unification).
|
|
312
|
+
*/
|
|
313
|
+
getNativeSessionId(memberId: string): string | undefined;
|
|
299
314
|
/**
|
|
300
315
|
* Feed token usage into the tracker from a child notification (Tier 1). Handles all three shapes a
|
|
301
316
|
* child may use so the tracker is actually populated:
|