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.
Files changed (78) hide show
  1. package/dist/agent.js +21 -21
  2. package/dist/cli.js +1 -1566
  3. package/dist/index.js +445 -421
  4. package/dist/orchestration.js +7 -7
  5. package/dist/protocol.js +1 -1
  6. package/dist/skills/builtin/desktop-screenshot/SKILL.md +62 -0
  7. package/dist/skills/builtin/office-doc-reading/SKILL.md +19 -0
  8. package/dist/skills/mcp/astraclaw-native-mcp-server.js +7 -6
  9. package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +18 -0
  10. package/dist/types/agent/tool-loop/loop-helpers.d.ts +9 -0
  11. package/dist/types/agent/tool-loop.d.ts +14 -5
  12. package/dist/types/agent/types.d.ts +5 -0
  13. package/dist/types/cli/acp-session-handlers.d.ts +8 -1
  14. package/dist/types/cli/acp-session-host.d.ts +5 -1
  15. package/dist/types/cli/default-project-bootstrap.d.ts +33 -0
  16. package/dist/types/cli/handlers/agents-handler.d.ts +9 -0
  17. package/dist/types/cli/handlers/config-handler.d.ts +5 -0
  18. package/dist/types/cli/handlers/files-handler.d.ts +7 -0
  19. package/dist/types/cli/handlers/session-handler.d.ts +49 -0
  20. package/dist/types/cli/handlers/turn-handler.d.ts +77 -0
  21. package/dist/types/cli/media-understanding.d.ts +45 -0
  22. package/dist/types/cli/permission-rule-persistence.d.ts +16 -0
  23. package/dist/types/cli/session-context.d.ts +5 -1
  24. package/dist/types/cli/stdio-acp-request-host.d.ts +1 -1
  25. package/dist/types/cli/stdio-rpc-handler-hosts.d.ts +4 -0
  26. package/dist/types/cli/stdio-server.d.ts +17 -1
  27. package/dist/types/cli/tool-bootstrap-core-registration.d.ts +2 -0
  28. package/dist/types/cli/turn-core.d.ts +2 -0
  29. package/dist/types/orchestration/agent-instance.d.ts +4 -0
  30. package/dist/types/orchestration/delegation-coordinator.d.ts +10 -0
  31. package/dist/types/orchestration/product-persistence.d.ts +4 -0
  32. package/dist/types/orchestration/workflow/capability-catalog.d.ts +1 -1
  33. package/dist/types/orchestration/workflow/workflow-controller.d.ts +1 -1
  34. package/dist/types/orchestration/workflow/workflow-runtime.d.ts +1 -1
  35. package/dist/types/protocol/notifications.d.ts +1 -1
  36. package/dist/types/protocol/wire/acp-agent-management.d.ts +16 -0
  37. package/dist/types/protocol/wire/agent-events.d.ts +2 -2
  38. package/dist/types/protocol/wire/agent-methods.d.ts +3 -0
  39. package/dist/types/protocol/wire/index.d.ts +1 -1
  40. package/dist/types/protocol/wire/notification-payloads.d.ts +26 -0
  41. package/dist/types/runtime/execution/turn-progress-notifier.d.ts +16 -0
  42. package/dist/types/runtime/infra/acp-detector.d.ts +40 -6
  43. package/dist/types/runtime/infra/acp-protocol-adapter.d.ts +14 -0
  44. package/dist/types/runtime/infra/agent-process.d.ts +16 -0
  45. package/dist/types/runtime/infra/astraclaw-capabilities.d.ts +2 -2
  46. package/dist/types/runtime/infra/builtin-skills-seed.d.ts +31 -0
  47. package/dist/types/runtime/infra/llmrouter-catalog.d.ts +26 -6
  48. package/dist/types/runtime/infra/model-registry.d.ts +43 -2
  49. package/dist/types/runtime/infra/project-data-gc.d.ts +7 -1
  50. package/dist/types/runtime/infra/skill-resolver.d.ts +15 -0
  51. package/dist/types/runtime/infra/user-dirs.d.ts +21 -0
  52. package/dist/types/runtime/permission-model.d.ts +1 -1
  53. package/dist/types/runtime/ports/permission-contracts.d.ts +5 -2
  54. package/dist/types/runtime/prompt/environment-context.d.ts +24 -2
  55. package/dist/types/runtime/prompt/fresh-workspace-evidence.d.ts +1 -2
  56. package/dist/types/runtime/prompt/identity-section.d.ts +17 -0
  57. package/dist/types/runtime/session/inbound-upload-persistence.d.ts +20 -0
  58. package/dist/types/runtime/session/native/claude-session-source.d.ts +23 -0
  59. package/dist/types/runtime/session/native/codex-index.d.ts +21 -0
  60. package/dist/types/runtime/session/native/codex-session-source.d.ts +31 -0
  61. package/dist/types/runtime/session/native/cwd-normalize.d.ts +53 -0
  62. package/dist/types/runtime/session/native/ephemeral-rollout-sweep.d.ts +13 -0
  63. package/dist/types/runtime/session/native/native-cwd-agents.d.ts +17 -0
  64. package/dist/types/runtime/session/native/native-session-source.d.ts +89 -0
  65. package/dist/types/runtime/session/native/native-title.d.ts +1 -0
  66. package/dist/types/runtime/session/native/register-builtin-sources.d.ts +1 -0
  67. package/dist/types/runtime/session/session-recovery.d.ts +58 -0
  68. package/dist/types/runtime/session/session-types.d.ts +30 -0
  69. package/dist/types/skills/permissions/hook-runner.d.ts +9 -0
  70. package/dist/types/skills/permissions/settings-watcher.d.ts +7 -0
  71. package/dist/types/skills/tools/exec-tool.d.ts +1 -1
  72. package/dist/types/skills/tools/shell/command-classification.d.ts +1 -0
  73. package/dist/types/skills/tools/shell/index.d.ts +3 -3
  74. package/dist/types/skills/tools/shell/powershell-provider.d.ts +8 -3
  75. package/dist/types/skills/tools/shell/shell-exec.d.ts +18 -0
  76. package/dist/types/skills/tools/shell/shell-provider.d.ts +67 -5
  77. package/dist/types/transport/acp-server.d.ts +6 -0
  78. package/package.json +4 -1
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{createInterface as Qe}from"node:readline";import{existsSync as Ye,readFileSync as Ze}from"node:fs";import{readFileSync as be}from"node:fs";import{randomUUID as Te}from"node:crypto";import{readdirSync as le,readFileSync as ce,statSync as b,writeFileSync as ue}from"node:fs";import{join as d}from"node:path";import{homedir as ee}from"node:os";import{join as u,resolve as lt}from"node:path";import{existsSync as ut}from"node:fs";import{createHash as pt}from"node:crypto";var x=".qlogicagent";function te(){return process.env.QLOGICAGENT_HOME||u(ee(),x)}function oe(){let o=process.env.QLOGIC_LLMROUTER_USER_ID?.trim();if(o)return o;let e=process.env.QLOGIC_IMPLICIT_OWNER_ID?.trim();if(e)return e;let t=process.env.QLOGIC_DEVICE_ID?.trim();return t?`oc_${t}`:"oc_local"}function re(o){let e=o.trim();if(!e)throw new Error("ownerUserId is required for profile-scoped storage");return encodeURIComponent(e).replace(/\./g,"%2E")}function f(o=oe()){return u(te(),"profiles",re(o))}function y(){return u(f(),"skills")}function A(){return u(f(),"mcp.json")}function ne(o){if(!o)throw new Error("getProjectAgentDir: cwd is required (must not fall back to process.cwd())");return u(o,x)}function R(o){return u(ne(o),"skills-disabled.json")}import{readFileSync as se,writeFileSync as gt,mkdirSync as ht,renameSync as yt}from"node:fs";import{dirname as Tt,join as ie}from"node:path";function E(){return{version:1,disabled:[]}}function M(o){try{let e=JSON.parse(se(o,"utf8"));return!e||typeof e!="object"||!Array.isArray(e.disabled)?E():{version:1,disabled:[...new Set(e.disabled.filter(r=>typeof r=="string"&&r.trim().length>0))]}}catch{return E()}}function ae(){return ie(f(),"skills-disabled.json")}function D(){return new Set(M(ae()).disabled)}function I(o){return new Set(M(R(o)).disabled)}var de=["auto-skill-","test-skill-"],pe=".skills_prompt_snapshot.json";function me(o){return de.some(e=>o.startsWith(e))}function fe(o){let e=o.replace(/\r\n/g,`
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
- `);if(He.test(t)&&e.push({code:"ioc_public_key",message:"configuration contains an inline SSH public key IOC"}),(o.type??(o.url?"http":"stdio"))!=="stdio"||!Je(o.command))return e;let r=Ve(o.args??[]);if(!r)return e;let n=Ke(r);return v(n,Be)&&e.push({code:"persistence",message:"stdio shell command attempts a persistence write"}),v(n,Fe)&&v(n,Ge)&&e.push({code:"dangerous_egress",message:"stdio shell command combines network egress with likely secret access"}),e}function Je(o){if(!o)return!1;let e=p.basename(o).toLowerCase();return qe.has(e)}function Ve(o){for(let e=0;e<o.length;e++){let t=o[e]?.toLowerCase();if(t&&We.has(t))return o.slice(e+1).join(" ")}return""}function Ke(o){return o.toLowerCase().replace(/[^a-z0-9]+/g,"")}function v(o,e){return e.some(t=>o.includes(t))}var Xe=[{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}}],_=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 t of e)this.addTool(t)}removeTool(e){return this.tools.delete(e)}},m=null,et=Qe({input:process.stdin,crlfDelay:Number.POSITIVE_INFINITY});et.on("line",o=>{tt(o)});async function tt(o){let e=o.trim();if(!e)return;let t;try{t=JSON.parse(e)}catch{return}if(t.id===void 0||t.id===null)return t.method==="notifications/initialized",void 0;try{let r=await ot(t);Z({jsonrpc:"2.0",id:t.id,result:r})}catch(r){Z({jsonrpc:"2.0",id:t.id,error:{code:-32603,message:r instanceof Error?r.message:String(r)}})}}async function ot(o){switch(o.method){case"initialize":return{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:N,version:"1.0.0"}};case"tools/list":return{tools:Xe};case"tools/call":{let e=Y(o.params),t=typeof e.name=="string"?e.name:"",r=Y(e.arguments),n=rt(),s=t==="mcp_connectors_list"||t==="mcp_tool_call"?await nt(n):void 0,i=await $({tool:t,args:r,projectRoot:n,toolCatalog:s});if(!i.handled)throw new Error(`Unknown tool: ${t}`);return{content:[{type:"text",text:JSON.stringify(i.result)}]}}default:throw new Error(`Unknown method: ${o.method}`)}}function rt(){return process.env.ASTRACLAW_PROJECT_ROOT||process.env.QLOGICAGENT_PROJECT_ROOT||process.cwd()}async function nt(o){if(m?.projectRoot===o)return await m.ready,m.catalog;m?.manager?.disconnectAll().catch(()=>{});let e=new _,t=new g({servers:st(),workspaceRoot:o,toolCatalog:e,log:{info:n=>process.stderr.write(`${n}
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=t.connectAll().then(()=>t.injectTools());return m={projectRoot:o,catalog:e,manager:t,ready:r},await r,e}function st(){let o=process.env.ASTRACLAW_MCP_CONFIG_PATH||A();if(!Ye(o))return[];try{let e=Ze(o,"utf8").replace(/^\uFEFF/,"");return Q(JSON.parse(e))}catch(e){return process.stderr.write(`[astraclaw-native-mcp] failed to read MCP config: ${e instanceof Error?e.message:String(e)}
8
- `),[]}}function Y(o){return typeof o=="object"&&o!==null&&!Array.isArray(o)?o:{}}function Z(o){process.stdout.write(`${JSON.stringify(o)}
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 — observe-only] Identify the "target" a tool call acts on, for churn counting:
113
- * the file path for write/edit/patch, the normalized command for exec. Returns null for
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 buildBenchmarkStateMaintenanceInstruction(documents: ReadonlyMap<string, MaintainedDocumentIndex>, turn: number): string | null;
118
- export declare function benchmarkStateMaintenanceWriteValidation(maintainedDocuments: ReadonlyMap<string, MaintainedDocumentIndex>, filePath: string, rawArguments: string): {
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 benchmarkStateHashIntegrityViolations(maintainedDocuments: ReadonlyMap<string, MaintainedDocumentIndex>): {
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); inherited by every prompt. */
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
  }