qlogicagent 2.17.9 → 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 +21 -21
- package/dist/cli.js +1 -1566
- package/dist/index.js +445 -421
- package/dist/orchestration.js +7 -7
- package/dist/protocol.js +1 -1
- package/dist/skills/builtin/desktop-screenshot/SKILL.md +62 -0
- package/dist/skills/builtin/office-doc-reading/SKILL.md +19 -0
- package/dist/skills/mcp/astraclaw-native-mcp-server.js +7 -6
- package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +18 -0
- package/dist/types/agent/tool-loop/loop-helpers.d.ts +9 -0
- package/dist/types/agent/tool-loop.d.ts +14 -5
- package/dist/types/agent/types.d.ts +5 -0
- package/dist/types/cli/acp-session-handlers.d.ts +8 -1
- package/dist/types/cli/acp-session-host.d.ts +5 -1
- package/dist/types/cli/default-project-bootstrap.d.ts +33 -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 +49 -0
- package/dist/types/cli/handlers/turn-handler.d.ts +77 -0
- package/dist/types/cli/media-understanding.d.ts +45 -0
- package/dist/types/cli/permission-rule-persistence.d.ts +16 -0
- package/dist/types/cli/session-context.d.ts +5 -1
- package/dist/types/cli/stdio-acp-request-host.d.ts +1 -1
- package/dist/types/cli/stdio-rpc-handler-hosts.d.ts +4 -0
- package/dist/types/cli/stdio-server.d.ts +17 -1
- package/dist/types/cli/tool-bootstrap-core-registration.d.ts +2 -0
- 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/orchestration/workflow/capability-catalog.d.ts +1 -1
- package/dist/types/orchestration/workflow/workflow-controller.d.ts +1 -1
- package/dist/types/orchestration/workflow/workflow-runtime.d.ts +1 -1
- package/dist/types/protocol/notifications.d.ts +1 -1
- package/dist/types/protocol/wire/acp-agent-management.d.ts +16 -0
- package/dist/types/protocol/wire/agent-events.d.ts +2 -2
- package/dist/types/protocol/wire/agent-methods.d.ts +3 -0
- package/dist/types/protocol/wire/index.d.ts +1 -1
- package/dist/types/protocol/wire/notification-payloads.d.ts +26 -0
- package/dist/types/runtime/execution/turn-progress-notifier.d.ts +16 -0
- package/dist/types/runtime/infra/acp-detector.d.ts +40 -6
- package/dist/types/runtime/infra/acp-protocol-adapter.d.ts +14 -0
- package/dist/types/runtime/infra/agent-process.d.ts +16 -0
- package/dist/types/runtime/infra/astraclaw-capabilities.d.ts +2 -2
- package/dist/types/runtime/infra/builtin-skills-seed.d.ts +31 -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/project-data-gc.d.ts +7 -1
- package/dist/types/runtime/infra/skill-resolver.d.ts +15 -0
- 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/environment-context.d.ts +24 -2
- package/dist/types/runtime/prompt/fresh-workspace-evidence.d.ts +1 -2
- package/dist/types/runtime/prompt/identity-section.d.ts +17 -0
- package/dist/types/runtime/session/inbound-upload-persistence.d.ts +20 -0
- 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-recovery.d.ts +58 -0
- package/dist/types/runtime/session/session-types.d.ts +30 -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/skills/tools/exec-tool.d.ts +1 -1
- package/dist/types/skills/tools/shell/command-classification.d.ts +1 -0
- package/dist/types/skills/tools/shell/index.d.ts +3 -3
- package/dist/types/skills/tools/shell/powershell-provider.d.ts +8 -3
- package/dist/types/skills/tools/shell/shell-exec.d.ts +18 -0
- package/dist/types/skills/tools/shell/shell-provider.d.ts +67 -5
- package/dist/types/transport/acp-server.d.ts +6 -0
- package/package.json +4 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createInterface as
|
|
3
|
-
`),t=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),s=e.match(/^---\n[\s\S]*?^author:\s*(.+)/m),i=e.match(/^---\n[\s\S]*?^requires:\s*(.+)/m),a=e.match(/^---\n[\s\S]*?^environments:\s*(.+)/m);return{version:t?.[1],description:r?.[1]?.trim(),category:n?.[1]?.trim()||void 0,author:s?.[1]?.trim()||void 0,requiredTools:j(i?.[1]),environments:j(a?.[1])}}function ge(o,e){let t=y(),r=D(),n=o?I(o):new Set,s=[],i;try{i=le(t,{withFileTypes:!0})}catch{i=[]}for(let a of i){if(!a.isDirectory()||a.name.startsWith("."))continue;let l=d(t,a.name),h=d(l,"SKILL.md"),k;try{if(!b(h).isFile())continue;k=ce(h,"utf8")}catch{continue}let P=r.has(a.name),C=n.has(a.name),c=fe(k),X=!he(c,e);s.push({name:a.name,filePath:h,baseDir:l,globallyDisabled:P,projectDisabled:C,active:!P&&!C&&!X,systemGenerated:me(a.name),description:c.description,version:c.version,category:c.category,author:c.author,requiredTools:c.requiredTools,environments:c.environments})}return ye(t,s),s}function L(o,e){return ge(o,e).filter(t=>t.active)}function O(o){let e=y(),t=d(e,o),r=d(t,"SKILL.md");try{if(b(r).isFile())return{baseDir:t,filePath:r}}catch{}return null}function j(o){if(!o)return;let e=o.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(t=>t.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function he(o,e){if(o.environments&&e?.currentEnvironment&&!o.environments.includes(e.currentEnvironment))return!1;if(o.requiredTools){if(!e?.availableToolNames)return!1;let t=new Set(e.availableToolNames);return o.requiredTools.every(r=>t.has(r))}return!0}function ye(o,e){try{let r={version:1,entries:e.map(n=>{let s=b(n.filePath);return{name:n.name,size:s.size,mtimeMs:s.mtimeMs,description:n.description,version:n.version,requiredTools:n.requiredTools,environments:n.environments}})};ue(d(o,pe),JSON.stringify(r,null,2),"utf8")}catch{}}var N="astraclaw_capabilities";async function $(o){if(o.tool==="skills_list")return{handled:!0,result:{skills:L(o.projectRoot,{availableToolNames:o.availableToolNames,currentEnvironment:o.currentEnvironment??process.platform}).map(we)}};if(o.tool==="skill_view"){let e=typeof o.args.name=="string"?o.args.name.trim():"";if(!e)throw new Error("skill_view requires a skill name");let t=O(e);if(!t)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:be(t.filePath,"utf8")}}}if(o.tool==="mcp_connectors_list"){if(!o.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance."}};let e=o.toolCatalog.getToolManifest().filter(t=>t.function.name.startsWith("mcp__")).map(t=>({toolName:t.function.name,server:Se(t.function.name),name:_e(t.function.name),description:t.function.description,inputSchema:t.function.parameters}));return{handled:!0,result:{tools:e,count:e.length}}}if(o.tool==="mcp_tool_call"){if(!o.toolCatalog)throw new Error("AstraClaw MCP connector catalog is not attached");let e=typeof o.args.toolName=="string"?o.args.toolName.trim():"";if(!e.startsWith("mcp__"))throw new Error("mcp_tool_call requires a connector toolName returned by mcp_connectors_list");let t=o.toolCatalog.findTool(e);if(!t?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let r=ve(o.args.args),n=`astraclaw_mcp_${Te().slice(0,8)}`,s=await t.execute(n,r);return{handled:!0,result:{toolName:e,content:s.content,details:s.details,imageUrls:s.imageUrls}}}return{handled:!1}}function we(o){return{name:o.name,description:o.description,version:o.version,category:o.category,requiredTools:o.requiredTools,environments:o.environments}}function ve(o){return typeof o=="object"&&o!==null&&!Array.isArray(o)?o:{}}function Se(o){let e=o.split("__");return e.length>=3?e[1]??"":""}function _e(o){let e=o.split("__");return e.length>=3?e.slice(2).join("__"):o}import p from"node:path";import{fileURLToPath as Ae}from"node:url";import{createRuntime as Re}from"mcporter";var U=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),ke=new Set([...U,"agent"]);function q(o){return o?.riskLevel?o.riskLevel:o?.isDangerous?"system":o?.isReadOnly?"read":o?.isEgress?"external_egress":"write"}var T=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let t of e)this.toolPool.set(t.name,t)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let t of e)this.addTool(t)}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,t,r,n){let s=this.toolPool.get(e);if(!s)throw new Error(`Tool not found: ${e}`);return s.execute(t,r,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let t of this.toolPool.values()){if(t.isEnabled?.()===!1)continue;let r=q(t);r==="write"&&(t.category==="media"||t.category==="web"||t.category==="mcp"?r="external_egress":(t.category==="system"||t.name==="exec"||t.name==="computer")&&(r="system")),e.push({type:"function",function:{name:t.name,description:t.description,parameters:t.parameters},meta:{category:t.category??"other",displayName:t.displayName??{key:`capability.tool.${t.name}.name`,fallback:t.label},displayDescription:t.displayDescription??{key:`capability.tool.${t.name}.description`,fallback:""},parallelSafe:t.isConcurrencySafe??!1,riskLevel:r,isReadOnly:r==="read",isDangerous:r==="system",isDelete:t.isDelete??!1,isEgress:t.isEgress??r==="external_egress",egressCarriesData:t.egressCarriesData??!1}})}return e}},W=new T;function F(o){W.addTools(o)}function G(o){return W.removeTool(o)}var Pe=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 B(o){let e=o.annotations,t=xe(o.name),r=t.some(l=>Ce.has(l)),n=t.some(l=>Pe.has(l)),s=e?.destructiveHint===!0||r,i=e?.readOnlyHint===!0||!s&&n,a=i||e?.idempotentHint===!0&&!s;return{isReadOnly:i,isConcurrencySafe:a,isDestructive:s}}function xe(o){return o.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var Ee=12e4,w=3,Me=1e3;async function De(o,e){let t=e.sleep??(s=>new Promise(i=>setTimeout(i,s))),r=Math.max(1,e.attempts),n;for(let s=1;s<=r;s++)try{return await o()}catch(i){n=i,s<r&&(e.onRetry?.(s,i),await t(e.baseDelayMs*s))}throw n}var g=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?p.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(t=>!t.disabled).filter(t=>{let r=ze(t);for(let n of r)this.log.warn(`[mcp] rejected server "${t.name}": ${n.message}`);return t.tools&&this.serverToolFilters.set(t.name,t.tools),t.oauth===!0&&this.serverOAuth.set(t.name,!0),r.length===0}).map(t=>this.toServerDefinition(t)).filter(t=>t!==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,t)=>this.log.warn(`[mcp] ${e}${t?` (${t instanceof Error?t.message:String(t)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let t=await De(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:w,baseDelayMs:Me,onRetry:(n,s)=>this.log.warn(`[mcp] connect attempt ${n}/${w} for ${e.name} failed, retrying: ${s instanceof Error?s.message:s}`)}),r=J(t,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,r),this.log.info(`[mcp] connected to ${e.name} (${r.length}/${t.length} tools)`)}catch(t){this.log.warn(`[mcp] failed to connect to ${e.name} after ${w} attempts: ${t instanceof Error?t.message:t}`)}})))}injectTools(){for(let[e,t]of this.toolsByServer){let r=this.injectPortableTools(e,t);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 t=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),r=J(t,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 t of this.toolsByServer.values())e+=t.length;return e}async disconnectAll(){for(let t of this.injectedNames)this.removeRegisteredTool(t);this.injectedNames.clear(),this.toolsByServer.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,t){let r=B({name:t.name,description:t.description});return{name:`mcp__${H(e)}__${t.name}`,label:`[${e}] ${t.name}`,category:"mcp",description:t.description??`MCP tool from ${e}`,parameters:t.inputSchema??{type:"object",properties:{}},isConcurrencySafe:r.isConcurrencySafe,isReadOnly:r.isReadOnly,isDestructive:r.isDestructive,searchHint:`mcp ${e} ${t.name.replace(/[_-]+/g," ")}`,execute:async(n,s)=>{let i=this.runtime;if(!i)return{content:[{type:"text",text:`MCP server "${e}" is not connected.`}],details:{error:"mcp_not_connected"}};try{let a=await i.callTool(e,t.name,{args:s,timeoutMs:Ee});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(t=>{if(!t.execute)return t;let r=t.execute;return{...t,execute:async(n,s,i)=>{let a=Le(t.name,s,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:r(n,s,i)}}}):e}injectPortableTools(e,t){let r=this.wrapToolsForWorkspaceBoundary(t.map(n=>this.toPortableTool(e,n)));this.registerTools(r);for(let n of r)this.injectedNames.add(n.name);return r}retractServerTools(e){let t=`mcp__${H(e)}__`;for(let r of[...this.injectedNames])r.startsWith(t)&&(this.removeRegisteredTool(r),this.injectedNames.delete(r))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):F(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??G(e)}};function Ie(o){let e=o&&typeof o=="object"?o:{};return{content:[{type:"text",text:(Array.isArray(e.content)?e.content:[]).filter(s=>!!s&&typeof s=="object").filter(s=>s.type==="text"&&typeof s.text=="string").map(s=>s.text).join(`
|
|
4
|
-
`)||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function H(o){return o.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(o,e,t){if(!t)return null;for(let r of S(e)){let n=$e(r.value,t);if(n&&!Ue(n.candidate,n.root,n.pathApi))return`Blocked: MCP tool "${o}" path "${r.value}" is outside the workspace boundary "${n.root}"`}return null}function S(o,e="",t=0){if(t>8)return[];if(typeof o=="string")return Oe(e,o)?[{key:e,value:o}]:[];if(Array.isArray(o))return o.flatMap(n=>S(n,e,t+1));if(!o||typeof o!="object")return[];let r=[];for(let[n,s]of Object.entries(o))r.push(...S(s,n,t+1));return r}function Oe(o,e){if(!je.test(o))return!1;let t=e.trim();return!t||/^https?:\/\//i.test(t)?!1:Ne(t)}function Ne(o){return p.isAbsolute(o)||/^[A-Za-z]:[\\/]/.test(o)||/^\\\\/.test(o)||/^file:\/\//i.test(o)||o.includes("../")||o.includes("..\\")||o.startsWith(".\\")||o.startsWith("./")||o.includes("/")||o.includes("\\")}function $e(o,e){let t=o.trim();if(/^file:\/\//i.test(t))try{t=Ae(t)}catch{return null}let r=z(t)||z(e)?p.win32:p,n=r.resolve(e);return{candidate:r.isAbsolute(t)?r.resolve(t):r.resolve(n,t),root:n,pathApi:r}}function Ue(o,e,t){let r=t.relative(e,o);return r===""||!r.startsWith("..")&&!t.isAbsolute(r)}function z(o){return/^[A-Za-z]:[\\/]/.test(o)||/^\\\\/.test(o)}function Q(o){if(!o||typeof o!="object")return[];let e=o,t=[],r=e.mcpServers??e.servers??e;for(let[n,s]of Object.entries(r)){if(!s||typeof s!="object")continue;let i=s;if(typeof i.url=="string"){t.push({name:n,type:"http",url:i.url,headers:i.headers&&typeof i.headers=="object"?i.headers:void 0,disabled:i.disabled===!0,oauth:i.oauth===!0,tools:V(i.tools)});continue}typeof i.command=="string"&&t.push({name:n,type:"stdio",command:i.command,args:Array.isArray(i.args)?i.args:void 0,env:i.env&&typeof i.env=="object"?i.env:void 0,cwd:typeof i.cwd=="string"?i.cwd:void 0,disabled:i.disabled===!0,oauth:i.oauth===!0,tools:V(i.tools)})}return t}function J(o,e){let t=new Set(e?.include??[]),r=new Set(e?.exclude??[]);return o.filter(n=>t.size>0&&!t.has(n.name)?!1:!r.has(n.name))}function V(o){if(!o||typeof o!="object")return;let e=o,t=K(e.include),r=K(e.exclude);if(!(!t&&!r))return{...t?{include:t}:{},...r?{exclude:r}:{}}}function K(o){if(!Array.isArray(o))return;let e=o.filter(t=>typeof t=="string"&&t.trim().length>0);return e.length>0?e:void 0}var qe=new Set(["bash","sh","zsh","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]),We=new Set(["-c","/c","-command","-encodedcommand","-e"]),Fe=["curl","wget","invokewebrequest","invoke-restmethod","iwr","irm","nc","netcat"],Ge=["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 ze(o){let e=[],t=[o.command??"",...o.args??[],...Object.keys(o.env??{}),...Object.values(o.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=
|
|
8
|
-
`),[]}}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)}
|
|
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
|
`)}
|
|
@@ -122,6 +122,18 @@ export interface FinalStatusReconciliation {
|
|
|
122
122
|
/** A ```final-status block was present but not valid schema-1 JSON (treated as absent). */
|
|
123
123
|
malformedBlock: boolean;
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Locale for engine-authored final-status content. Only the fixed wording skeleton is localized;
|
|
127
|
+
* dynamic data (paths, tool names, counts, blocker reasons, evidence) is always verbatim.
|
|
128
|
+
* Model-authored claims are untouched — they already follow the conversation language.
|
|
129
|
+
*/
|
|
130
|
+
export type EngineStatusLocale = "zh" | "en";
|
|
131
|
+
/**
|
|
132
|
+
* Derive the conversation locale from the LAST user message's text. Han characters → "zh",
|
|
133
|
+
* otherwise "en" (including when no string-content user message exists). Only the newest user
|
|
134
|
+
* turn matters, so engine status follows the language the user is currently writing in.
|
|
135
|
+
*/
|
|
136
|
+
export declare function deriveConversationLocale(messages: readonly ChatMessage[]): EngineStatusLocale;
|
|
125
137
|
export declare function reconcileFinalStatus(params: {
|
|
126
138
|
content: string;
|
|
127
139
|
/** From finalStatusContractApplies() over the turn's tool surface; inactive turns pass through untouched. */
|
|
@@ -138,4 +150,10 @@ export declare function reconcileFinalStatus(params: {
|
|
|
138
150
|
activeTasks?: readonly TaskContractItem[];
|
|
139
151
|
/** The turn ended on a forced/failure terminal rather than a clean model stop. */
|
|
140
152
|
forcedStop?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Conversation language for engine-authored content. Defaults to "en" so existing tests and
|
|
155
|
+
* non-Chinese sessions are unaffected. Model-authored claims ignore this — they already follow
|
|
156
|
+
* the conversation language.
|
|
157
|
+
*/
|
|
158
|
+
locale?: EngineStatusLocale;
|
|
141
159
|
}): FinalStatusReconciliation;
|
|
@@ -3,6 +3,7 @@ export { isEmptyToolResult } from "./tool-result-classification.js";
|
|
|
3
3
|
export declare function findLastToolError(messages: unknown[], _toolName: string): string | undefined;
|
|
4
4
|
export declare function looksLikeBuildRequest(messages?: readonly ChatMessage[]): boolean;
|
|
5
5
|
export declare function isBareIntentPreamble(text: string): boolean;
|
|
6
|
+
export declare function isCapabilityDenial(text: string): boolean;
|
|
6
7
|
export declare function resolveToolLoopBudget(requested?: number, messages?: readonly ChatMessage[]): number;
|
|
7
8
|
export declare function countExplicitOutputArtifacts(messages?: readonly ChatMessage[]): number;
|
|
8
9
|
export declare function resolveTotalToolCallBudget(requested?: number, messages?: readonly ChatMessage[]): number;
|
|
@@ -29,4 +30,12 @@ export declare function buildRepeatedToolFailureStopContent(messages: ChatMessag
|
|
|
29
30
|
* the error put in front of it once. Returns null when there is no tool error to correct from.
|
|
30
31
|
*/
|
|
31
32
|
export declare function buildToolFailureCorrectionNudge(messages: ChatMessage[]): ChatMessage | null;
|
|
33
|
+
/**
|
|
34
|
+
* exec 命令的"族"标识:程序名 + 子命令/脚本名(最多两段),用于 churn/变体失败
|
|
35
|
+
* 计数。审计 CASE2:按精确命令串 keying 时,每轮换一个转换/安装命令即可永远绕过
|
|
36
|
+
* 阈值 —— 按族计数后 "pip install A" / "pip install B" 归并为同一目标。
|
|
37
|
+
*/
|
|
38
|
+
export declare function execCommandFamily(command: string): string;
|
|
39
|
+
export declare function isIterativeRunnerFamily(family: string): boolean;
|
|
40
|
+
export declare function isSetupOnlyExecCommand(command: string | null): boolean;
|
|
32
41
|
export declare function summarizeToolInput(rawArgs: string): string | undefined;
|
|
@@ -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.).
|
|
@@ -109,19 +115,22 @@ export interface ToolLoopParams {
|
|
|
109
115
|
/** Crossing this many actions on the SAME target within a turn flags a no-progress candidate. */
|
|
110
116
|
export declare const NO_PROGRESS_TARGET_THRESHOLD = 4;
|
|
111
117
|
/**
|
|
112
|
-
* [lever 3
|
|
113
|
-
*
|
|
118
|
+
* [lever 3] Identify the "target" a tool call acts on, for churn counting: the file path
|
|
119
|
+
* for write/edit/patch, the command FAMILY (program + subcommand/script) for exec — with
|
|
120
|
+
* iterate-by-design runner families (test/build/lint/git) exempted, since repeatedly
|
|
121
|
+
* re-running those is a normal fix-verify cycle, not churn. Crossing the threshold now
|
|
122
|
+
* feeds an acting once-per-family nudge (plus recoveryTrace telemetry). Returns null for
|
|
114
123
|
* tools whose repetition isn't a stuck-signal (read, search, etc.). Exported for testing.
|
|
115
124
|
*/
|
|
116
125
|
export declare function noProgressTargetKey(toolName: string, rawArguments: string): string | null;
|
|
117
|
-
export declare function
|
|
118
|
-
export declare function
|
|
126
|
+
export declare function buildLongHorizonStateMaintenanceInstruction(documents: ReadonlyMap<string, MaintainedDocumentIndex>, turn: number): string | null;
|
|
127
|
+
export declare function longHorizonStateMaintenanceWriteValidation(maintainedDocuments: ReadonlyMap<string, MaintainedDocumentIndex>, filePath: string, rawArguments: string): {
|
|
119
128
|
ok: true;
|
|
120
129
|
} | {
|
|
121
130
|
ok: false;
|
|
122
131
|
reason: string;
|
|
123
132
|
};
|
|
124
|
-
export declare function
|
|
133
|
+
export declare function longHorizonStateHashIntegrityViolations(maintainedDocuments: ReadonlyMap<string, MaintainedDocumentIndex>): {
|
|
125
134
|
path: string;
|
|
126
135
|
reason: string;
|
|
127
136
|
}[];
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { AcpSessionHost } from "./acp-session-host.js";
|
|
2
|
-
import { type AcpInitializeParams, type AcpInitializeResult, type AcpSessionCloseParams, type AcpSessionLoadParams, type AcpSessionNewParams, type AcpSessionNewResult, type AcpSessionPromptParams, type AcpSessionPromptResult, type AcpSessionSetConfigParams } from "../protocol/wire/index.js";
|
|
2
|
+
import { type AcpInitializeParams, type AcpInitializeResult, type AcpSessionCloseParams, type AcpSessionLoadParams, type AcpSessionMeta, type AcpSessionNewParams, type AcpSessionNewResult, type AcpSessionPromptParams, type AcpSessionPromptResult, type AcpSessionSetConfigParams } from "../protocol/wire/index.js";
|
|
3
3
|
export type { AcpSessionHost };
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the _meta a prompt must inherit: prefer the meta registered for THIS session (session/new)
|
|
6
|
+
* over the per-connection singleton, so multiplexed sessions (desktop × WeChat on one connection)
|
|
7
|
+
* never swap each other's config. Falls back to the legacy singleton for unregistered sessions
|
|
8
|
+
* (old-gateway compat), then to an empty bag.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveSessionMetaForPrompt(host: AcpSessionHost, sessionId: string): AcpSessionMeta;
|
|
4
11
|
export declare function handleAcpInitialize(host: AcpSessionHost, params: AcpInitializeParams, packageVersion: string): Promise<AcpInitializeResult>;
|
|
5
12
|
export declare function handleAcpSessionNew(host: AcpSessionHost, params: AcpSessionNewParams): Promise<AcpSessionNewResult>;
|
|
6
13
|
/**
|
|
@@ -19,8 +19,12 @@ export interface SessionLlmConfig {
|
|
|
19
19
|
baseUrl?: string;
|
|
20
20
|
}
|
|
21
21
|
export interface AcpSessionHost {
|
|
22
|
-
/** Gateway-supplied per-session context (_meta from session/new)
|
|
22
|
+
/** Gateway-supplied per-session context (_meta from session/new).
|
|
23
|
+
* LEGACY fallback: a per-CONNECTION singleton overwritten by the last session/new — kept only
|
|
24
|
+
* for old-gateway compat. Multiplexed connections must read via acpSessionMetaBySession instead. */
|
|
23
25
|
acpSessionMeta: AcpSessionMeta | null;
|
|
26
|
+
/** Per-session gateway _meta(session/new 注册;prompt 按 sessionId 取,消除跨会话串扰)。 */
|
|
27
|
+
acpSessionMetaBySession: Map<string, AcpSessionMeta>;
|
|
24
28
|
/** Host-pinned per-session LLM config; overlaid into every prompt's turn config. */
|
|
25
29
|
sessionLlmConfig: SessionLlmConfig;
|
|
26
30
|
currentHooks: HookRegistry | null;
|
|
@@ -5,4 +5,37 @@ export interface DefaultProjectBootstrapDeps {
|
|
|
5
5
|
projectMemoryStoreFactory: ProjectMemoryStoreFactory;
|
|
6
6
|
sendNotification<M extends keyof NotificationMethodMap>(method: M, params: NotificationMethodMap[M]): void;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Slow, non-essential startup chores split OUT of the synchronous initialize path. Deferring these
|
|
10
|
+
* off the sync response of `initialize` is what keeps a concurrent `session.list` from being starved
|
|
11
|
+
* (T17-class bug: sync fs on Node's single thread blocks RPC dispatch until it finishes — here
|
|
12
|
+
* `gcOrphanProjectData`'s work is unbounded in the count of project dirs ever created on the box).
|
|
13
|
+
* Mirrors the AcpDetector.scanAsync precedent: move the blocking work off the hot path (here via one
|
|
14
|
+
* deferral to a later event-loop turn — not per-iteration setImmediate slicing). None of this is
|
|
15
|
+
* needed before session.list/session.prompt can be served —
|
|
16
|
+
* the default project already exists and is registered/active (that stays synchronous below), the
|
|
17
|
+
* seeded builtin skills only enrich the lazy progressive-disclosure catalog, and GC only reclaims
|
|
18
|
+
* orphaned companion dirs (never live data).
|
|
19
|
+
*/
|
|
20
|
+
interface DeferredHeavyWork {
|
|
21
|
+
/** A default project was freshly created this run → its starter templates still need seeding. */
|
|
22
|
+
freshDefaultDir?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Resolves once the most recently scheduled deferred heavy work has settled (never rejects). */
|
|
25
|
+
export declare function getDefaultProjectHeavyWorkPromise(): Promise<void>;
|
|
8
26
|
export declare function ensureDefaultProjectBootstrap(deps: DefaultProjectBootstrapDeps): ProjectMemoryStore;
|
|
27
|
+
/**
|
|
28
|
+
* Schedule (fire-and-forget) the deferred heavy work so it runs off the synchronous initialize path.
|
|
29
|
+
* Exposed for tests; production calls it from ensureDefaultProjectBootstrap. Records a gate promise on
|
|
30
|
+
* `heavyWorkPromise` so a caller that must wait can, without any synchronous blocking of startup.
|
|
31
|
+
*
|
|
32
|
+
* Uses a single `setImmediate` — NOT setImmediate *slicing*: the entire block is deferred once to a
|
|
33
|
+
* later event-loop turn (the check phase after the next poll phase), so a `session.list` that was
|
|
34
|
+
* already queued on stdin is read and dispatched BEFORE this synchronous-fs block runs. A microtask
|
|
35
|
+
* would drain before returning to the poll phase and so would not let session.list through first.
|
|
36
|
+
* The block still starts with bounded synchronous fs (builtin-skill seed + template copy), so the
|
|
37
|
+
* macrotask boundary is what lets the queued session.list run before it; the unbounded orphan GC
|
|
38
|
+
* inside is itself async and yields between directories.
|
|
39
|
+
*/
|
|
40
|
+
export declare function scheduleDefaultProjectHeavyWork(heavy: DeferredHeavyWork): void;
|
|
41
|
+
export {};
|
|
@@ -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,13 +33,61 @@ 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>;
|
|
39
82
|
export declare function handleSessionDelete(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
40
83
|
export declare function handleSessionDeleteAll(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
41
84
|
export declare function handleSessionArchive(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* L2 优雅关闭清标记:清所有 session 残留的 in-flight 标记(== hermes .clean_shutdown 语义)。
|
|
87
|
+
* gateway 优雅退出时在 adapter.dispose() 前调用,据此区分"优雅关闭"与"崩溃"——
|
|
88
|
+
* 启动扫描(L3)看到的残留标记因此一定是崩溃残留,不误报。无参、幂等。
|
|
89
|
+
*/
|
|
90
|
+
export declare function handleSessionMarkCleanShutdown(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
42
91
|
export declare function handleSessionResume(this: SessionHandlerHost, msg: AgentRpcRequest): Promise<void>;
|
|
43
92
|
export declare function handleSessionGetInfo(this: SessionHandlerHost, msg: AgentRpcRequest): void;
|
|
44
93
|
export declare function handleSessionSwitchProject(this: SessionHandlerHost, msg: AgentRpcRequest): void;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Turn execution handlers — extracted from StdioServer.
|
|
3
3
|
* Handles: the shared turn pipeline (runTurnPipeline) + thread.user_response (ask-user answers).
|
|
4
4
|
*/
|
|
5
|
+
import { type ModelPurpose } from "../../runtime/infra/model-registry.js";
|
|
6
|
+
import { type UnderstandMediaClient, type UnderstandMediaKind } from "../media-understanding.js";
|
|
5
7
|
import type { PermissionMetadataResolver, ToolCatalog } from "../../runtime/ports/index.js";
|
|
6
8
|
import type { ChatMessage, TokenUsage, ToolDefinition, TurnConfig } from "../../agent/types.js";
|
|
7
9
|
import type { AgentRpcRequest } from "../../protocol/wire/agent-rpc.js";
|
|
@@ -16,7 +18,47 @@ export interface TurnControlHandlerHost {
|
|
|
16
18
|
configureTurnMedia?(config: Record<string, unknown> | undefined, turnId: string): void;
|
|
17
19
|
log(message: string): void;
|
|
18
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;
|
|
19
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;
|
|
34
|
+
/**
|
|
35
|
+
* First-turn session-title generation, extracted so the caller can run it
|
|
36
|
+
* fire-and-forget (see run()). Behavior is identical to the previous inline
|
|
37
|
+
* block; the only change is that the LLM round-trip inside `generateTitle`
|
|
38
|
+
* no longer sits on the critical path before executeTurnCore.
|
|
39
|
+
*
|
|
40
|
+
* Dependencies are injected so this is a pure, testable unit:
|
|
41
|
+
* - `generateTitle` wraps generateSessionTitle (which keeps the deterministic
|
|
42
|
+
* fast-path internally — it is NOT bypassed here).
|
|
43
|
+
* - `fallbackTitle` wraps titleFromPromptFallback (applied when generation
|
|
44
|
+
* yields nothing).
|
|
45
|
+
* - `persistTitle` wraps updateSessionMetadata (swallowing its own errors).
|
|
46
|
+
*
|
|
47
|
+
* Returns a promise for the async work; callers that want fire-and-forget
|
|
48
|
+
* should `void` it (with a `.catch(() => {})` guard).
|
|
49
|
+
*/
|
|
50
|
+
export interface FirstTurnTitleGenerationDeps {
|
|
51
|
+
existingTitle: string | undefined;
|
|
52
|
+
firstUserText: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
projectId: string | undefined;
|
|
55
|
+
generateTitle(firstUserText: string): Promise<string | null>;
|
|
56
|
+
fallbackTitle(firstUserText: string): string;
|
|
57
|
+
persistTitle(title: string): Promise<void>;
|
|
58
|
+
sendNotification(method: string, params: Record<string, unknown>): void;
|
|
59
|
+
log(message: string): void;
|
|
60
|
+
}
|
|
61
|
+
export declare function runFirstTurnTitleGeneration(deps: FirstTurnTitleGenerationDeps): Promise<void>;
|
|
20
62
|
export declare function hydrateTurnConfigFromModelMetadata(config: TurnConfig): TurnConfig;
|
|
21
63
|
export declare function hydrateAttachmentReferenceUrlsForTurn(messages: ChatMessage[]): ChatMessage[];
|
|
22
64
|
export declare function getFocusedAttachmentToolNamesForTurn(messages: ChatMessage[]): Set<string> | null;
|
|
@@ -47,6 +89,41 @@ export declare function getFocusedAttachmentModelPurposeForTurn(messages: ChatMe
|
|
|
47
89
|
export declare function requiresPreToolPreambleForTurn(messages: readonly ChatMessage[]): boolean;
|
|
48
90
|
export declare function refreshPermissionCheckerToolMeta(permissionChecker: PermissionMetadataResolver | null | undefined, tools: ToolDefinition[]): void;
|
|
49
91
|
export declare function handleUserResponse(this: TurnControlHandlerHost, msg: AgentRpcRequest): void;
|
|
92
|
+
/**
|
|
93
|
+
* Pure: assemble the text appended to the user message after modality routing.
|
|
94
|
+
* `descriptorsByKind` are the successfully understood texts (in order per modality);
|
|
95
|
+
* `fallbackStripped` are the "X 张图片"-style labels for media that was dropped without
|
|
96
|
+
* understanding. Returns "" when there is nothing to inject.
|
|
97
|
+
*
|
|
98
|
+
* The understood blocks lead with a note stating they are model transcriptions; the
|
|
99
|
+
* "media omitted" note is appended ONLY for the fallback set, so a successful transcription
|
|
100
|
+
* is never contradicted by a blanket "media omitted" line.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildUnderstoodInjection(descriptorsByKind: Record<UnderstandMediaKind, string[]>, fallbackStripped: string[]): string;
|
|
103
|
+
interface ModalityRegistryView {
|
|
104
|
+
acceptsInputModality(modality: string, purpose: ModelPurpose): boolean;
|
|
105
|
+
isAvailable(purpose: ModelPurpose): boolean;
|
|
106
|
+
}
|
|
107
|
+
interface RouteUnsupportedModalitiesDeps {
|
|
108
|
+
lastUserMsg: ChatMessage;
|
|
109
|
+
purpose: ModelPurpose;
|
|
110
|
+
registry: ModalityRegistryView;
|
|
111
|
+
resolveClient(purpose: ModelPurpose): UnderstandMediaClient | null;
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
log(message: string): void;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* For each modality the resolved main model can't consume, route the media to its
|
|
117
|
+
* understanding model (image→imageUnderstanding, audio→stt, video→videoUnderstanding)
|
|
118
|
+
* and fold the returned text into the user message; media that can't be understood
|
|
119
|
+
* (model unavailable / call failed / empty / over the cap) degrades to the previous
|
|
120
|
+
* behavior (field dropped + omission note). The media field is ALWAYS deleted afterward
|
|
121
|
+
* — the main model still can't consume it, so leaving it would 400 the turn.
|
|
122
|
+
*
|
|
123
|
+
* Mutates `lastUserMsg` in place. Injection only touches string content (array content is
|
|
124
|
+
* left as-is, matching the prior behavior). Best-effort: never throws for a per-item failure.
|
|
125
|
+
*/
|
|
126
|
+
export declare function routeUnsupportedModalitiesToUnderstanding(deps: RouteUnsupportedModalitiesDeps): Promise<void>;
|
|
50
127
|
export interface TurnPipelineInput {
|
|
51
128
|
turnId: string;
|
|
52
129
|
sessionId: string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared media-understanding — single source of truth for turning a non-text medium
|
|
3
|
+
* (image / audio / video) into plain text via the matching understanding model.
|
|
4
|
+
*
|
|
5
|
+
* Used by BOTH pipelines:
|
|
6
|
+
* - memory candidates (memory-candidate-service.ts): passes a base64 `data:` URL built
|
|
7
|
+
* from persisted attachment bytes.
|
|
8
|
+
* - the turn gate (handlers/turn-handler.ts): passes the local media-proxy URL already
|
|
9
|
+
* stamped onto the user message, so a medium the main model can't consume is routed
|
|
10
|
+
* to the understanding model and its description injected instead of dropped.
|
|
11
|
+
*
|
|
12
|
+
* The function is URL-agnostic on purpose: it just places whatever URL string it is given
|
|
13
|
+
* onto the modality-correct message field and streams the reply. It never reads attachment
|
|
14
|
+
* bytes and never persists anything — callers own storage/injection.
|
|
15
|
+
*/
|
|
16
|
+
export type UnderstandMediaKind = "image" | "audio" | "video";
|
|
17
|
+
/** Maps each medium to the ModelPurpose that understands it. */
|
|
18
|
+
export declare const PURPOSE_BY_KIND: Record<UnderstandMediaKind, "imageUnderstanding" | "stt" | "videoUnderstanding">;
|
|
19
|
+
/** Maps each medium to the ChatMessage field the transport reads media URLs from. */
|
|
20
|
+
export declare const URL_FIELD_BY_KIND: Record<UnderstandMediaKind, "imageUrls" | "audioUrls" | "videoUrls">;
|
|
21
|
+
/** Per-medium instruction. Every kind is normalized to plain text so callers only swap a label. */
|
|
22
|
+
export declare const PROMPT_BY_KIND: Record<UnderstandMediaKind, string>;
|
|
23
|
+
/**
|
|
24
|
+
* A resolved understanding client. Structurally compatible with both the real LLMTransport
|
|
25
|
+
* (whose `stream` takes an optional third `signal` arg) and the memory client. Deliberately
|
|
26
|
+
* loose on the request/chunk shapes so it composes with either caller without import cycles.
|
|
27
|
+
*/
|
|
28
|
+
export interface UnderstandMediaClient {
|
|
29
|
+
model: string;
|
|
30
|
+
apiKey: string;
|
|
31
|
+
transport: {
|
|
32
|
+
stream(request: Record<string, unknown>, apiKey: string, signal?: AbortSignal): AsyncIterable<{
|
|
33
|
+
type: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Understand a single medium at `url` and return the model's trimmed text (may be empty
|
|
40
|
+
* if the model produced nothing). Never throws for an empty reply — an empty string is a
|
|
41
|
+
* valid "no text" result the caller degrades on.
|
|
42
|
+
*/
|
|
43
|
+
export declare function understandMediaUrl(client: UnderstandMediaClient, kind: UnderstandMediaKind, url: string, opts?: {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}): Promise<string>;
|
|
@@ -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>;
|
|
@@ -24,6 +24,10 @@ export declare class SessionContext {
|
|
|
24
24
|
/** Host-pinned per-session LLM config (session/set_config_option · set_model · /model).
|
|
25
25
|
* NEVER wiped by ModelRegistry churn — distinct from the DERIVED ResolvedAgentCache. */
|
|
26
26
|
llmConfig: SessionLlmConfig;
|
|
27
|
-
/** ACP session-level context supplement (_meta from session/new), inherited by every prompt.
|
|
27
|
+
/** ACP session-level context supplement (_meta from session/new), inherited by every prompt.
|
|
28
|
+
* LEGACY per-connection singleton — kept for old-gateway fallback; multiplexed prompts read
|
|
29
|
+
* acpMetaBySession by sessionId instead. */
|
|
28
30
|
acpMeta: Record<string, unknown> | null;
|
|
31
|
+
/** Per-session gateway _meta(按 sessionId 存,消除跨会话串扰)。 */
|
|
32
|
+
acpMetaBySession: Map<string, Record<string, unknown>>;
|
|
29
33
|
}
|