grix-connector 3.2.3 → 3.2.5

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.
@@ -1 +1 @@
1
- import{readFileSync as m,readdirSync as c,existsSync as u,mkdirSync as d,cpSync as p,writeFileSync as k,rmSync as y}from"node:fs";import{join as t,dirname as h}from"node:path";import{homedir as g}from"node:os";import{fileURLToPath as S}from"node:url";import{parseSkillFrontmatter as D}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as x}from"../core/config/index.js";import{log as a}from"../core/log/index.js";const l=h(S(import.meta.url)),f=".grix-managed";function v(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=m(n,"utf-8"),o=D(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function K(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(u(t(s,"SKILL.md")))try{d(e,{recursive:!0});const o=t(e,n.name);p(s,o,{recursive:!0}),k(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function F(){const e=g(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(x().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills")]}function M(){const e=[];for(const i of F()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(u(t(s,f)))try{y(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&a.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function R(){const e=v();if(e.length===0){a.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}a.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{M as cleanupProjectedSkills,R as logDefaultSkillsCheck,v as scanDefaultSkills,K as syncDefaultSkillsToDir};
1
+ import{readFileSync as d,readdirSync as c,existsSync as a,mkdirSync as p,cpSync as k,writeFileSync as y,rmSync as h}from"node:fs";import{join as t,dirname as g}from"node:path";import{homedir as S}from"node:os";import{fileURLToPath as D}from"node:url";import{parseSkillFrontmatter as x,scanSkills as v,dedupeSkills as F}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as w}from"../core/config/index.js";import{log as u}from"../core/log/index.js";const l=g(D(import.meta.url)),f=".grix-managed";function m(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=d(n,"utf-8"),o=x(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function M(e){let i=[];try{i=v(e)}catch{i=[]}return F([...i,...m()])}function b(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(a(t(s,"SKILL.md")))try{p(e,{recursive:!0});const o=t(e,n.name);k(s,o,{recursive:!0}),y(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function L(){const e=S(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(w().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills")]}function A(){const e=[];for(const i of L()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(a(t(s,f)))try{h(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&u.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function E(){const e=m();if(e.length===0){u.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}u.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{M as buildReportedSkills,A as cleanupProjectedSkills,E as logDefaultSkillsCheck,m as scanDefaultSkills,b as syncDefaultSkillsToDir};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as Q,readdirSync as B,writeFileSync as K}from"node:fs";import{join as A}from"node:path";import{AgentInstance as $}from"./bridge/bridge.js";import{buildSharedInstanceConfig as z,diffSharedOwners as G,sharedInstanceKey as W,SharedOwnersCache as J}from"./manager-share-config.js";import{GRIX_PATHS as b,log as p}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{scanSkills as Z,dedupeSkills as ee}from"./adapter/claude/skill-scanner.js";import{scanDefaultSkills as te,logDefaultSkillsCheck as ne,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as q}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ae,resolveCliPath as re}from"./core/util/cli-probe.js";import{AgentInstaller as se}from"./core/installer/installer.js";import{reportInstallFailure as oe}from"./core/observability/sentry.js";const ie=8e3;function ce(){const n=q();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function le(){return Promise.all(ce().map(async n=>{const e=await re(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function de(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=q();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function P(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function pe(n,e){return A(b.data,`session-bindings-${P(n,e)}.json`)}function ue(n,e){return A(b.data,`active-events-${P(n,e)}.json`)}function me(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function ge(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function I(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=de(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const i=s.adapterType,l=i==="acp",g=t==="qwen",h={...s.options??{}},w=i==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,d=i==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,f=i==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=i==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=i==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=i==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,C=i==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,k=i==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,F=l&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,H=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,U=i==="codex"||i==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,R=[r,o];l&&h.raw_transport===void 0&&(h.raw_transport=t==="gemini");const L=`${t}/base`,M=i==="claude"?"claude":i==="codex"?"codex":i==="pi"?"pi":t==="kiro"?"kiro":"gemini";let S;try{const E=M==="kiro"?void 0:process.cwd();S=Z({mode:M,projectDir:E})??void 0,S&&S.length===0&&(S=void 0)}catch{}const j=te();if(j.length>0){const x=ee([...S??[],...j]),O=x.filter(T=>T.source==="connector").map(T=>T.name);O.length>0&&p.info("manager",`[${n.name}] injecting connector skills: [${O.join(", ")}]`),S=x.length>0?x:void 0}return{name:n.name,adapterType:i,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??f?.adapterHint??u?.adapterHint??_?.adapterHint??C?.adapterHint??k?.adapterHint??L,capabilities:w?.capabilities??y?.capabilities??f?.capabilities??u?.capabilities??_?.capabilities??C?.capabilities??k?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:me(w?.localActions??y?.localActions??d?.localActions??f?.localActions??u?.localActions??_?.localActions??C?.localActions??k?.localActions??m?.localActions??H?.localActions??F?.localActions??["exec_approve","exec_reject"],U,R,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:S},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:h,acpAuthMethod:h.auth_method,acpInitialMode:h.initial_mode,acpMcpTools:h.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:pe(n.name,n.agent_id),activeEventStorePath:ue(n.name,n.agent_id),...s.enableSessionBinding||l?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ge(i,n.event_queue),logDir:b.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function he(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ie}function N(n){const e=[],t=[],s=B(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(Q(A(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function v(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(v).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${v(e[s])}`).join(",")}}`}function fe(n,e){return v(n)===v(e)}class Oe{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;upgradeChecker=null;globalConfigStore;configDir=b.config;sharedOwnersCache=new J(b.data);installer=new se;async start(e){const t=e??b.config;this.configDir=t,p.info("manager",`Loading configs from ${t}`),ne(),D(),this.globalConfigStore=new Y(A(b.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=N(t);for(const i of a)p.error("manager",`Failed to load ${i.file}: ${i.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:i,file:l}of s)try{const g=I(i);r.push({config:g,entry:i,file:l}),p.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${l}`)}catch(g){const h=typeof i?.name=="string"?i.name:"<unknown>";p.error("manager",`Invalid agent config in ${l} (name=${h}): ${g}`),o++}let c=0;if(r.length>0){const i=he();p.info("manager",`Starting ${r.length} agent(s), startup wait=${i}ms`);const l=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(m=>m!==d)},h=r.map(({config:d,entry:m,file:f})=>{const u=new $(d,this.globalConfigStore);return u.setUpgradeTrigger(l),u.setShareSetHandler(_=>this.onShareSet(d,_)),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:m,file:f}),{config:d,instance:u,startPromise:u.start()}}),w=await Promise.all(h.map(async d=>{const m=await new Promise(f=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,f({kind:"timeout"}))},i);d.startPromise.then(()=>{u||(u=!0,clearTimeout(_),f({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),f({kind:"failed",error:y}))})});return{task:d,outcome:m}}));for(const{task:d,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(m.kind==="failed"){g(d.instance),p.error("manager",`Failed to start ${d.config.name}: ${m.error}`);continue}c++,p.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{p.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(f=>{g(d.instance),p.error("manager",`Delayed start failed: ${d.config.name}: ${f}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);p.info("manager",`${d}/${r.length} agent(s) running now`)}c>0&&p.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new X(r.map(({config:i})=>({apiKey:i.aibot.apiKey,wsUrl:i.aibot.url})),()=>this.instances.some(i=>i.getStatus().busy)),await this.upgradeChecker.start())}async stop(){p.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],D(),p.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:s,toRemove:a}=G(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=W(e.name,r);try{const c=z(e,r),i=new $(c,this.globalConfigStore);this.sharedInstances.set(o,i),await i.start(),p.info("manager",`shared instance started: ${o}`)}catch(c){this.sharedInstances.delete(o);const i=c instanceof Error?c.message:String(c);/Auth failed/i.test(i)?(this.sharedOwnersCache.remove(e.name,r),p.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):p.error("manager",`start shared instance failed ${o}: ${c}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(c=>p.error("manager",`stop shared instance failed ${r}: ${c}`)),p.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(p.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),p.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=I(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new $(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),p.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(c=>c.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[c,i]of[...this.sharedInstances.entries()])c.startsWith(o)&&(this.sharedInstances.delete(c),i.stop().catch(l=>p.error("manager",`stop shared instance failed ${c}: ${l}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}persistAgentsConfig(){const e=A(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}K(e,JSON.stringify({agents:t},null,4)+`
2
- `,"utf-8")}catch(t){p.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),p.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const s=this.rawConfigMap.get(e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...s.entry,provider:t};await this.replaceInstance(e,a,s.file),this.persistAgentsConfig(),p.info("manager",`Updated provider config for agent: ${e}`)}async replaceInstance(e,t,s){const a=I(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,s),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=N(this.configDir);if(t.length>0){const o=t.map(c=>`${c.file}: ${c.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=new Map;for(const o of e){const c=String(o.entry?.name??"").trim();c&&(s.has(c)&&p.warn("manager",`reload: duplicate agent name "${c}", last one wins`),s.set(c,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},r=new Set(this.rawConfigMap.keys());for(const o of r)if(!s.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(c){a.failed.push({name:o,error:c instanceof Error?c.message:String(c)})}for(const[o,c]of s){const i=this.rawConfigMap.get(o);if(i)if(fe(i.entry,c.entry))this.rawConfigMap.set(o,c),a.unchanged.push(o);else try{await this.replaceInstance(o,c.entry,c.file),a.restarted.push(o)}catch(l){a.failed.push({name:o,error:l instanceof Error?l.message:String(l)})}else try{await this.addAgentInternal(c.entry,c.file),a.added.push(o)}catch(l){a.failed.push({name:o,error:l instanceof Error?l.message:String(l)})}}return p.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return _e(this.instances,e)}async probeOne(e,t={}){return we(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return oe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function _e(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(l=>{let g=0,h=0;const w=n.length;if(w===0){l();return}function d(u){const _=n[u];_.probe(e).then(y=>{a[u]=y,m()},y=>{a[u]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},m()})}function m(){h++,g<w?d(g++):h===w&&l()}const f=Math.min(t,w);for(let u=0;u<f;u++)d(g++)});const r=a.filter(l=>l.status==="healthy").length,o=a.filter(l=>l.status==="degraded").length,c=a.filter(l=>l.status==="unavailable").length,i=await le();return{ok:r===a.length&&a.length>0,total:a.length,healthy:r,degraded:o,unavailable:c,installed_clients:i,agents:a,probed_at:s,duration_ms:Date.now()-s}}async function we(n,e,t){const s=n.find(a=>a.name===e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return s.probe(t)}export{Oe as Manager,le as probeInstalledClientCommands,_e as probeInstances,we as probeOneInstance};
1
+ import{readFileSync as L,readdirSync as Q,writeFileSync as B}from"node:fs";import{join as S}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as W}from"./manager-share-config.js";import{GRIX_PATHS as b,log as p}from"./core/log/index.js";import{resolveClientVersion as J}from"./core/util/client-version.js";import{UpgradeChecker as V}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as X}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Y}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as Z,logDefaultSkillsCheck as ee,cleanupProjectedSkills as j}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as te,resolveCliPath as ne}from"./core/util/cli-probe.js";import{AgentInstaller as ae}from"./core/installer/installer.js";import{reportInstallFailure as re}from"./core/observability/sentry.js";const se=8e3;function oe(){const n=E();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ie(){return Promise.all(oe().map(async n=>{const e=await ne(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await te(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function ce(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=E();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":throw new Error('client_type "hermes" is not handled by grix-connector. Hermes runs as a separate project \u2014 see https://github.com/askie/grix-hermes-python');default:throw new Error(`Unsupported client_type: ${n}`)}}function O(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function le(n,e){return S(b.data,`session-bindings-${O(n,e)}.json`)}function de(n,e){return S(b.data,`active-events-${O(n,e)}.json`)}function pe(...n){const e=[],t=new Set;for(const s of n)for(const a of s??[]){const r=String(a??"").trim(),o=r.toLowerCase();!r||t.has(o)||(t.add(o),e.push(r))}return e.length>0?e:void 0}function ue(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},s=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??s.maxConcurrent??1,maxQueued:e?.max_queued??s.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??s.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function $(n){const e=J(),t=String(n.client_type??"").trim().toLowerCase(),s=ce(t),a=String(n.ws_url??"").trim(),r="get_session_usage",o="get_rate_limits",i="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const c=s.adapterType,l=c==="acp",g=t==="qwen",h={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,d=c==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,o]}:null,m=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,f=c==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,u=c==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,_=c==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,o]}:null,y=c==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,v=c==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,C=c==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,P=l&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,o]}:null,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];l&&h.raw_transport===void 0&&(h.raw_transport=t==="gemini");const H=`${t}/base`,I=Y(c,t),U=I==="kiro"?void 0:process.cwd(),k=Z({mode:I,projectDir:U}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&p.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let R=k.length>0?k:void 0;return{name:n.name,adapterType:c,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:s.adapterHint??m?.adapterHint??f?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??H,capabilities:w?.capabilities??y?.capabilities??f?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:pe(w?.localActions??y?.localActions??d?.localActions??f?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??q?.localActions??P?.localActions??["exec_approve","exec_reject"],N,F,["connector_rollback","connector_upgrade_push",i,"configure_gateway_provider"]),skills:R},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:h,acpAuthMethod:h.auth_method,acpInitialMode:h.initial_mode,acpMcpTools:h.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:le(n.name,n.agent_id),activeEventStorePath:de(n.name,n.agent_id),...s.enableSessionBinding||l?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ue(c,n.event_queue),logDir:b.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function me(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):se}function D(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(r.agents)){if(r.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of r.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(r){t.push({file:a,error:r instanceof Error?r.message:String(r)})}return{entries:e,fileErrors:t}}function A(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(A).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${A(e[s])}`).join(",")}}`}function ge(n,e){return A(n)===A(e)}class je{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;upgradeChecker=null;globalConfigStore;configDir=b.config;sharedOwnersCache=new W(b.data);installer=new ae;async start(e){const t=e??b.config;this.configDir=t,p.info("manager",`Loading configs from ${t}`),ee(),j(),this.globalConfigStore=new X(S(b.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=D(t);for(const c of a)p.error("manager",`Failed to load ${c.file}: ${c.error}`);if(s.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const r=[];let o=a.length;for(const{entry:c,file:l}of s)try{const g=$(c);r.push({config:g,entry:c,file:l}),p.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${l}`)}catch(g){const h=typeof c?.name=="string"?c.name:"<unknown>";p.error("manager",`Invalid agent config in ${l} (name=${h}): ${g}`),o++}let i=0;if(r.length>0){const c=me();p.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const l=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(m=>m!==d)},h=r.map(({config:d,entry:m,file:f})=>{const u=new T(d,this.globalConfigStore);return u.setUpgradeTrigger(l),u.setShareSetHandler(_=>this.onShareSet(d,_)),this.instances.push(u),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:m,file:f}),{config:d,instance:u,startPromise:u.start()}}),w=await Promise.all(h.map(async d=>{const m=await new Promise(f=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,f({kind:"timeout"}))},c);d.startPromise.then(()=>{u||(u=!0,clearTimeout(_),f({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),f({kind:"failed",error:y}))})});return{task:d,outcome:m}}));for(const{task:d,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(m.kind==="failed"){g(d.instance),p.error("manager",`Failed to start ${d.config.name}: ${m.error}`);continue}i++,p.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{p.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(f=>{g(d.instance),p.error("manager",`Delayed start failed: ${d.config.name}: ${f}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-i);p.info("manager",`${d}/${r.length} agent(s) running now`)}i>0&&p.warn("manager",`${i} agent(s) still connecting in background`)}if(this.instances.length===0&&r.length>0)throw new Error("All agent configurations failed to start");r.length>0&&(this.upgradeChecker=new V(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.getStatus().busy)),await this.upgradeChecker.start())}async stop(){p.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),p.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:s,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const r of s){if(this.stopping)break;const o=G(e.name,r);try{const i=K(e,r),c=new T(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),p.info("manager",`shared instance started: ${o}`)}catch(i){this.sharedInstances.delete(o);const c=i instanceof Error?i.message:String(i);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,r),p.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):p.error("manager",`start shared instance failed ${o}: ${i}`)}}for(const{key:r}of a){const o=this.sharedInstances.get(r);o&&(this.sharedInstances.delete(r),o.stop().catch(i=>p.error("manager",`stop shared instance failed ${r}: ${i}`)),p.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(p.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),p.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=$(e);if(this.instances.some(a=>a.name===s.name))throw new Error(`Agent "${s.name}" already exists`);await this.startInstanceFromConfig(s,e,t)}async startInstanceFromConfig(e,t,s){const a=new T(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(r=>this.onShareSet(e,r)),a.setProviderConfigHandler(r=>this.setAgentProvider(e.name,r)),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:s})}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),p.info("manager",`Removed agent: ${e}`)}async removeAgentInternal(e,t){const s=this.instances.findIndex(i=>i.name===e),a=s===-1?void 0:this.instances[s];s!==-1&&this.instances.splice(s,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const r=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),r&&await r.catch(()=>{});const o=`${e}#shared:`;for(const[i,c]of[...this.sharedInstances.entries()])i.startsWith(o)&&(this.sharedInstances.delete(i),c.stop().catch(l=>p.error("manager",`stop shared instance failed ${i}: ${l}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const r=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...r?{provider:r}:{}})}B(e,JSON.stringify({agents:t},null,4)+`
2
+ `,"utf-8")}catch(t){p.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),p.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const s=this.rawConfigMap.get(e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...s.entry,provider:t};await this.replaceInstance(e,a,s.file),this.persistAgentsConfig(),p.info("manager",`Updated provider config for agent: ${e}`)}async replaceInstance(e,t,s){const a=$(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,s),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=D(this.configDir);if(t.length>0){const o=t.map(i=>`${i.file}: ${i.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=new Map;for(const o of e){const i=String(o.entry?.name??"").trim();i&&(s.has(i)&&p.warn("manager",`reload: duplicate agent name "${i}", last one wins`),s.set(i,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},r=new Set(this.rawConfigMap.keys());for(const o of r)if(!s.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(i){a.failed.push({name:o,error:i instanceof Error?i.message:String(i)})}for(const[o,i]of s){const c=this.rawConfigMap.get(o);if(c)if(ge(c.entry,i.entry))this.rawConfigMap.set(o,i),a.unchanged.push(o);else try{await this.replaceInstance(o,i.entry,i.file),a.restarted.push(o)}catch(l){a.failed.push({name:o,error:l instanceof Error?l.message:String(l)})}else try{await this.addAgentInternal(i.entry,i.file),a.added.push(o)}catch(l){a.failed.push({name:o,error:l instanceof Error?l.message:String(l)})}}return p.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return he(this.instances,e)}async probeOne(e,t={}){return fe(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return re(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function he(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(l=>{let g=0,h=0;const w=n.length;if(w===0){l();return}function d(u){const _=n[u];_.probe(e).then(y=>{a[u]=y,m()},y=>{a[u]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},m()})}function m(){h++,g<w?d(g++):h===w&&l()}const f=Math.min(t,w);for(let u=0;u<f;u++)d(g++)});const r=a.filter(l=>l.status==="healthy").length,o=a.filter(l=>l.status==="degraded").length,i=a.filter(l=>l.status==="unavailable").length,c=await ie();return{ok:r===a.length&&a.length>0,total:a.length,healthy:r,degraded:o,unavailable:i,installed_clients:c,agents:a,probed_at:s,duration_ms:Date.now()-s}}async function fe(n,e,t){const s=n.find(a=>a.name===e);if(!s)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return s.probe(t)}export{je as Manager,ie as probeInstalledClientCommands,he as probeInstances,fe as probeOneInstance};
@@ -1 +1 @@
1
- function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.2.3",
3
+ "version": "3.2.5",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",