grix-connector 3.2.9 → 3.3.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.
@@ -1 +1 @@
1
- import{readJSONFile as c,writeJSONFileAtomic as g}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class p{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,d){this.updateBinding(e,{agyConversationId:d})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{l.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await g(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(d,"agyConversationId")?d.agyConversationId:t?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{p as SessionBindingStore};
1
+ import{readJSONFile as c,writeJSONFileAtomic as g}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as s}from"../../adapter/claude/protocol-contract.js";const a=s.fullAuto;function n(r){const e=String(r??"").trim().toLowerCase();return e===s.approval||e===s.fullAuto?e:a}class x{bindings=new Map;filePath;writePromise=Promise.resolve();constructor(e){this.filePath=e??null}load(){if(!this.filePath)return;const e=c(this.filePath);if(Array.isArray(e)){this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:n(d.modeId)})}}set(e,d,t){const i=this.bindings.get(e),o=n(t?.modeId??i?.modeId);this.bindings.set(e,{aibotSessionId:e,cwd:d,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexServiceTier:i?.codexServiceTier,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:o,modelId:i?.modelId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,d){this.updateBinding(e,{acpSessionId:d})}setClaudeSessionId(e,d){this.updateBinding(e,{claudeSessionId:d})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,d){this.updateBinding(e,{codexThreadId:d})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,d){this.updateBinding(e,{agyConversationId:d})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,d){this.updateBinding(e,{codexModelId:d.modelId,codexModeId:d.modeId,codexReasoningEffort:d.reasoningEffort,codexServiceTier:d.serviceTier,codexSandboxMode:d.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexServiceTier(e){return this.bindings.get(e)?.codexServiceTier}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,d){this.updateBinding(e,{piSessionPath:d})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,d){this.updateBinding(e,{codewhaleThreadId:d})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=n(d);t.modeId!==i&&(this.bindings.set(e,{...t,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,d=a){const t=this.bindings.get(e);t&&(t.modeId||(this.bindings.set(e,{...t,modeId:n(d),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const d=this.bindings.get(e)?.modeId;return d?n(d):void 0}setModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=d.trim();t.modelId!==i&&(this.bindings.set(e,{...t,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setAcpModelId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModelId!==o&&(this.bindings.set(e,{...t,acpModelId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,d){const t=this.bindings.get(e);if(!t)return;const i=String(d??"").trim(),o=i===""?void 0:i;t.acpModeId!==o&&(this.bindings.set(e,{...t,acpModeId:o,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const d=e?.requireCwd!==!1;let t,i=-1;for(const o of this.bindings.values())d&&!String(o.cwd??"").trim()||o.updatedAt>i&&(i=o.updatedAt,t=o.aibotSessionId);return t}delete(e){this.bindings.delete(e),this.scheduleWrite()}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{l.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await g(this.filePath,e)}updateBinding(e,d){const t=this.bindings.get(e),i={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(d,"acpSessionId")?d.acpSessionId:t?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(d,"claudeSessionId")?d.claudeSessionId:t?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(d,"codexThreadId")?d.codexThreadId:t?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(d,"codexModelId")?d.codexModelId:t?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(d,"codexModeId")?d.codexModeId:t?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(d,"codewhaleThreadId")?d.codewhaleThreadId:t?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(d,"agyConversationId")?d.agyConversationId:t?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(d,"codexReasoningEffort")?d.codexReasoningEffort:t?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(d,"codexServiceTier")?d.codexServiceTier:t?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(d,"codexSandboxMode")?d.codexSandboxMode:t?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(d,"piSessionPath")?d.piSessionPath:t?.piSessionPath,cwd:t?.cwd,modeId:n(t?.modeId),modelId:t?.modelId,acpModelId:t?.acpModelId,acpModeId:t?.acpModeId,updatedAt:Date.now()};this.bindings.set(e,i),this.scheduleWrite()}}export{x as SessionBindingStore};
@@ -1 +1 @@
1
- import{existsSync as f,mkdirSync as M,readFileSync as v,renameSync as U,writeFileSync as $}from"node:fs";import{join as g,dirname as R}from"node:path";import{spawn as L}from"node:child_process";import{log as o}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as _}from"../util/client-version.js";import{UpgradeError as O,collectEnvInfo as D,getUpgradeLogTail as N,isVersionAvailable as F,npmInstall as H,pendingExists as T,preflightCheck as x,readPending as B,removePending as d,upgradeLog as s,verifyInstalledVersion as z,writePending as G}from"./npm-upgrader.js";import{isRetriableRegistryError as V}from"../installer/npm-registry.js";const j=360*60*1e3,K=300*1e3,S=1800*1e3,X=2,q=3,k="grix-connector",A=1e4,b=15,J=2e3,Y=8e3,W=1e3;function P(c){return new URL(c.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function E(){return g(m.data,"upgrade-state.json")}function C(){const c=E();if(!f(c))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(v(c,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Z(c){const t=E();M(R(t),{recursive:!0});const e=t+".tmp";$(e,JSON.stringify(c),"utf-8"),U(e,t)}function I(){return new Date().toISOString().slice(0,10)}class ot{agentConfigs;isBusy;timer=null;initialTimer=null;running=!1;stopped=!1;constructor(t,e){this.agentConfigs=t,this.isBusy=e}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),j)))},K)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!T())return;const t=B();if(!t){d();return}const e=_(),i=e===t.target_version,r=i?{from_version:t.from_version,to_version:t.target_version,status:"success"}:{from_version:t.from_version,to_version:t.target_version,status:"rolled_back",error_code:"STARTUP_CRASH",crash_count:t.crash_count};if(s(i?`daemon startup: version matches target ${t.target_version}, upgrade succeeded`:`daemon startup: version ${e} != target ${t.target_version}, rolled back`),i){const a=await this.confirmHealthz(Y);s(a?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(await this.reportUpgrade(r)){d();return}this.retryStartupReport(r)}async retryStartupReport(t){for(let e=2;e<=b;e++){if(this.stopped)return;const i=J*(e-1)+Math.floor(Math.random()*1e3);if(await new Promise(r=>setTimeout(r,i)),this.stopped)return;if(await this.reportUpgrade(t)){d();return}}s(`startup report (${t.status}) not delivered after ${b} attempts; clearing pending to unblock future checks`),d()}async confirmHealthz(t){let e="19579";try{const a=g(m.data,"health-port");f(a)&&(e=v(a,"utf-8").trim()||e)}catch{}const i=`http://127.0.0.1:${e}/healthz`,r=Date.now()+t;for(;Date.now()<r&&!this.stopped;){try{if((await fetch(i,{signal:AbortSignal.timeout(2e3)})).ok)return!0}catch{}await new Promise(a=>setTimeout(a,W))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(t){o.error("upgrade",`Check failed: ${t instanceof Error?t.message:t}`)}finally{this.running=!1}}}async check(){if(T()||this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(n=>this.queryUpgrade(n)))).find(n=>n.available&&n.release)?.release;if(!i)return;const r=i.version,a=_();if(!i.force&&!this.checkRateLimit(r))return;const h=x();if(!h.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:h.errorCode,error_msg:h.errorMsg});return}if(!await F(k,r)){s(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}s(`upgrade start: ${a} -> ${r}`);const y=Date.now();try{if(G(a,r),await H(k,r),z(r),this.startGuardian(),s("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),s("shutting down for restart"),this.isBusy?.()){s("active tasks detected, waiting for completion before restart");const n=3600*1e3,l=5e3,p=Date.now()+n;for(;this.isBusy()&&Date.now()<p&&!this.stopped;)await new Promise(w=>setTimeout(w,l));if(this.stopped){s("upgrade aborted: checker stopped during wait");return}this.isBusy()?s("active tasks still running after 1h max wait, forcing restart"):s("all tasks completed, proceeding with restart")}process.kill(process.pid,"SIGTERM")}catch(n){const l=n instanceof O?n.code:"NPM_INSTALL_FAILED",p=n instanceof Error?n.message:String(n);s(`upgrade failed: ${l} ${p}`),d(),V(p)||l==="NPM_TIMEOUT"?s(`failure is retriable (${l}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:l,error_msg:p,duration_ms:Date.now()-y,upgrade_log:N()})}}checkRateLimit(t){const e=C();if(e.last_failure_at){const u=Date.now()-new Date(e.last_failure_at).getTime();if(u<S)return o.info("upgrade",`In cooldown, ${(S-u)/6e4}m remaining`),!1}const i=e.version_attempts[t]??0;if(i>=q)return o.info("upgrade",`Version ${t} already tried ${i} times, skipping`),!1;const r=I(),a=e.daily_attempts[r]??0;return a>=X?(o.info("upgrade",`Already ${a} attempts today, skipping`),!1):!0}recordFailure(t){const e=C(),i=I();e.last_failure_at=new Date().toISOString(),e.last_failure_version=t,e.daily_attempts[i]=(e.daily_attempts[i]??0)+1,e.version_attempts[t]=(e.version_attempts[t]??0)+1,Z(e)}async queryUpgrade(t){const i=`${P(t.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:_(),channel:t.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const r=await fetch(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(A)});if(!r.ok)return o.warn("upgrade",`Check API returned ${r.status}`),{available:!1};const a=await r.json();return a.code!==0?{available:!1}:a.data}catch(r){return o.warn("upgrade",`Check API error: ${r instanceof Error?r.message:r}`),{available:!1}}}async reportUpgrade(t){if(this.agentConfigs.length===0)return!1;const e={client_type:"grix-connector",...t.npm_version?t:{...t,...D()}};return(await Promise.all(this.agentConfigs.map(async r=>{const u=`${P(r.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(u,{method:"POST",headers:{Authorization:`Bearer ${r.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(A)})).ok}catch{return!1}}))).some(r=>r)}startGuardian(){const t=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!f(t)){o.info("upgrade","Guardian not available on this platform, skipping");return}try{const e=L(t,[],{detached:!0,stdio:"ignore"});e.unref(),s(`guardian started (pid ${e.pid})`)}catch(e){o.warn("upgrade",`Failed to start guardian: ${e instanceof Error?e.message:e}`)}}}export{ot as UpgradeChecker};
1
+ import{existsSync as h,mkdirSync as U,readFileSync as w,renameSync as C,writeFileSync as M}from"node:fs";import{join as g,dirname as $}from"node:path";import{spawn as R}from"node:child_process";import{log as o}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as _}from"../util/client-version.js";import{UpgradeError as L,collectEnvInfo as O,getUpgradeLogTail as D,isVersionAvailable as N,npmInstall as F,pendingExists as v,preflightCheck as H,readPending as x,removePending as u,upgradeLog as s,verifyInstalledVersion as z,writePending as B}from"./npm-upgrader.js";import{isRetriableRegistryError as G}from"../installer/npm-registry.js";const V=360*60*1e3,j=300*1e3,T=1800*1e3,K=2,X=3,S="grix-connector",k=1e4,A=15,q=2e3,J=8e3,Y=1e3;function b(l){return new URL(l.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function P(){return g(m.data,"upgrade-state.json")}function E(){const l=P();if(!h(l))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(w(l,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Z(l){const t=P();U($(t),{recursive:!0});const e=t+".tmp";M(e,JSON.stringify(l),"utf-8"),C(e,t)}function I(){return new Date().toISOString().slice(0,10)}class ot{agentConfigs;isBusy;timer=null;initialTimer=null;running=!1;stopped=!1;constructor(t,e){this.agentConfigs=t,this.isBusy=e}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),V)))},j)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!v())return;const t=x();if(!t){u();return}const e=_(),i=e===t.target_version,r=i?{from_version:t.from_version,to_version:t.target_version,status:"success"}:{from_version:t.from_version,to_version:t.target_version,status:"rolled_back",error_code:"STARTUP_CRASH",crash_count:t.crash_count};if(s(i?`daemon startup: version matches target ${t.target_version}, upgrade succeeded`:`daemon startup: version ${e} != target ${t.target_version}, rolled back`),i){const a=await this.confirmHealthz(J);s(a?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(await this.reportUpgrade(r)){u();return}this.retryStartupReport(r)}async retryStartupReport(t){for(let e=2;e<=A;e++){if(this.stopped)return;const i=q*(e-1)+Math.floor(Math.random()*1e3);if(await new Promise(r=>setTimeout(r,i)),this.stopped)return;if(await this.reportUpgrade(t)){u();return}}s(`startup report (${t.status}) not delivered after ${A} attempts; clearing pending to unblock future checks`),u()}async confirmHealthz(t){let e="19579";try{const a=g(m.data,"health-port");h(a)&&(e=w(a,"utf-8").trim()||e)}catch{}const i=`http://127.0.0.1:${e}/healthz`,r=Date.now()+t;for(;Date.now()<r&&!this.stopped;){try{if((await fetch(i,{signal:AbortSignal.timeout(2e3)})).ok)return!0}catch{}await new Promise(a=>setTimeout(a,Y))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(t){o.error("upgrade",`Check failed: ${t instanceof Error?t.message:t}`)}finally{this.running=!1}}}async check(){if(v()||this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(n=>this.queryUpgrade(n)))).find(n=>n.available&&n.release)?.release;if(!i)return;const r=i.version,a=_();if(!i.force&&!this.checkRateLimit(r))return;const p=H();if(!p.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p.errorCode,error_msg:p.errorMsg});return}if(!await N(S,r)){s(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}s(`upgrade start: ${a} -> ${r}`);const y=Date.now();try{if(B(a,r),await F(S,r),z(r),this.startGuardian(),s("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),s("shutting down for restart"),!await this.waitUntilIdle()){s("upgrade aborted: checker stopped during wait");return}process.kill(process.pid,"SIGTERM")}catch(n){const d=n instanceof L?n.code:"NPM_INSTALL_FAILED",f=n instanceof Error?n.message:String(n);s(`upgrade failed: ${d} ${f}`),u(),G(f)||d==="NPM_TIMEOUT"?s(`failure is retriable (${d}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:d,error_msg:f,duration_ms:Date.now()-y,upgrade_log:D()})}}async waitUntilIdle(){if(!this.isBusy?.())return!this.stopped;s("active tasks detected, waiting until idle before restart (no max wait)");const t=5e3,e=600*1e3;let i=Date.now();for(;this.isBusy()&&!this.stopped;)await new Promise(r=>setTimeout(r,t)),Date.now()-i>=e&&(i=Date.now(),s("still waiting for active tasks to finish before restart"));return this.stopped?!1:(s("all tasks completed, proceeding with restart"),!0)}checkRateLimit(t){const e=E();if(e.last_failure_at){const c=Date.now()-new Date(e.last_failure_at).getTime();if(c<T)return o.info("upgrade",`In cooldown, ${(T-c)/6e4}m remaining`),!1}const i=e.version_attempts[t]??0;if(i>=X)return o.info("upgrade",`Version ${t} already tried ${i} times, skipping`),!1;const r=I(),a=e.daily_attempts[r]??0;return a>=K?(o.info("upgrade",`Already ${a} attempts today, skipping`),!1):!0}recordFailure(t){const e=E(),i=I();e.last_failure_at=new Date().toISOString(),e.last_failure_version=t,e.daily_attempts[i]=(e.daily_attempts[i]??0)+1,e.version_attempts[t]=(e.version_attempts[t]??0)+1,Z(e)}async queryUpgrade(t){const i=`${b(t.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:_(),channel:t.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const r=await fetch(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(k)});if(!r.ok)return o.warn("upgrade",`Check API returned ${r.status}`),{available:!1};const a=await r.json();return a.code!==0?{available:!1}:a.data}catch(r){return o.warn("upgrade",`Check API error: ${r instanceof Error?r.message:r}`),{available:!1}}}async reportUpgrade(t){if(this.agentConfigs.length===0)return!1;const e={client_type:"grix-connector",...t.npm_version?t:{...t,...O()}};return(await Promise.all(this.agentConfigs.map(async r=>{const c=`${b(r.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(k)})).ok}catch{return!1}}))).some(r=>r)}startGuardian(){const t=g(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!h(t)){o.info("upgrade","Guardian not available on this platform, skipping");return}try{const e=R(t,[],{detached:!0,stdio:"ignore"});e.unref(),s(`guardian started (pid ${e.pid})`)}catch(e){o.warn("upgrade",`Failed to start guardian: ${e instanceof Error?e.message:e}`)}}}export{ot as UpgradeChecker};
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as L,readdirSync as Q,writeFileSync as K}from"node:fs";import{join as b}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{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as d}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{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ne,resolveCliPath as ae}from"./core/util/cli-probe.js";import{AgentInstaller as re}from"./core/installer/installer.js";import{reportInstallFailure as se}from"./core/observability/sentry.js";const oe=8e3;function ie(){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 ce(){return Promise.all(ie().map(async n=>{const e=await ae(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ne(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function le(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 de(n,e){return b(A.data,`session-bindings-${O(n,e)}.json`)}function pe(n,e){return b(A.data,`active-events-${O(n,e)}.json`)}function ue(...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 me(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 T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=le(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,p=c==="acp",g=t==="qwen",f={...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,l=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,h=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,q=p&&!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,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const U=`${t}/base`,I=Z(c,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&d.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??h?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??U,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??l?.localActions??h?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??H?.localActions??q?.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:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:de(n.name,n.agent_id),activeEventStorePath:pe(n.name,n.agent_id),...s.enableSessionBinding||p?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:me(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ge(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):oe}function P(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(b(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 S(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(S).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${S(e[s])}`).join(",")}}`}function he(n,e){return S(n)===S(e)}class Ee{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new re;async start(e){const t=e??A.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),te(),D(),this.globalConfigStore=new Y(b(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=P(t);for(const c of a)d.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:p}of s)try{const g=T(c);r.push({config:g,entry:c,file:p}),d.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";d.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(r.length>0){const c=ge();d.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=l=>{this.instances=this.instances.filter(m=>m!==l)},f=r.map(({config:l,entry:m,file:h})=>{const u=new $(l,this.globalConfigStore);return u.setUpgradeTrigger(p),u.setShareSetHandler(_=>this.onShareSet(l,_)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(l.name)),this.instances.push(u),this.configMap.set(l.name,l),this.rawConfigMap.set(l.name,{entry:m,file:h}),{config:l,instance:u,startPromise:u.start()}}),w=await Promise.all(f.map(async l=>{const m=await new Promise(h=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},c);l.startPromise.then(()=>{u||(u=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:l,outcome:m}}));for(const{task:l,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(l.config);continue}if(m.kind==="failed"){if(j(m.error)){this.onAgentDeletedByPlatform(l.config.name);continue}g(l.instance),d.error("manager",`Failed to start ${l.config.name}: ${m.error}`);continue}i++,d.warn("manager",`Startup pending for ${l.config.name}, continue retrying in background`),l.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${l.config.name}`),this.restoreCachedSharedInstances(l.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(l.config.name);return}g(l.instance),d.error("manager",`Delayed start failed: ${l.config.name}: ${h}`)})}if(this.instances.length>0){const l=Math.max(0,this.instances.length-i);d.info("manager",`${l}/${r.length} agent(s) running now`)}i>0&&d.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 X(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(){d.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(),d.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 i=z(e,r),c=new $(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),d.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),d.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):d.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=>d.error("manager",`stop shared instance failed ${r}: ${i}`)),d.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(d.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(),d.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=T(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.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),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(),d.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(p=>d.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(s=>s.name===e)))return;d.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){d.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=b(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)+`
1
+ import{readFileSync as L,readdirSync as Q,writeFileSync as K}from"node:fs";import{join as b}from"node:path";import{AgentInstance as $}from"./bridge/bridge.js";import{buildSharedInstanceConfig as W,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as d}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{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as E}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ne,resolveCliPath as ae}from"./core/util/cli-probe.js";import{AgentInstaller as re}from"./core/installer/installer.js";import{reportInstallFailure as se}from"./core/observability/sentry.js";const oe=8e3;function ie(){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 ce(){return Promise.all(ie().map(async n=>{const e=await ae(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ne(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function le(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 de(n,e){return b(A.data,`session-bindings-${O(n,e)}.json`)}function pe(n,e){return b(A.data,`active-events-${O(n,e)}.json`)}function ue(...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 me(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 T(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),s=le(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,p=c==="acp",g=t==="qwen",f={...s.options??{}},w=c==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,o]}:null,l=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,h=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,q=p&&!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,N=c==="codex"||c==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[r,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const U=`${t}/base`,I=Z(c,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),M=k.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&d.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??h?.adapterHint??u?.adapterHint??_?.adapterHint??v?.adapterHint??C?.adapterHint??U,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??u?.capabilities??_?.capabilities??v?.capabilities??C?.capabilities??m?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??l?.localActions??h?.localActions??u?.localActions??_?.localActions??v?.localActions??C?.localActions??m?.localActions??H?.localActions??q?.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:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:de(n.name,n.agent_id),activeEventStorePath:pe(n.name,n.agent_id),...s.enableSessionBinding||p?{enableSessionBinding:!0}:{},...s.autoInjectArgs?{autoInjectArgs:s.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:me(c,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ge(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):oe}function P(n){const e=[],t=[],s=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of s)try{const r=JSON.parse(L(b(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 S(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(S).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${S(e[s])}`).join(",")}}`}function he(n,e){return S(n)===S(e)}class Ee{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new re;async start(e){const t=e??A.config;this.configDir=t,d.info("manager",`Loading configs from ${t}`),te(),D(),this.globalConfigStore=new Y(b(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:s,fileErrors:a}=P(t);for(const c of a)d.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:p}of s)try{const g=T(c);r.push({config:g,entry:c,file:p}),d.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof c?.name=="string"?c.name:"<unknown>";d.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let i=0;if(r.length>0){const c=ge();d.info("manager",`Starting ${r.length} agent(s), startup wait=${c}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=l=>{this.instances=this.instances.filter(m=>m!==l)},f=r.map(({config:l,entry:m,file:h})=>{const u=new $(l,this.globalConfigStore);return u.setUpgradeTrigger(p),u.setShareSetHandler(_=>this.onShareSet(l,_)),u.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(l.name)),this.instances.push(u),this.configMap.set(l.name,l),this.rawConfigMap.set(l.name,{entry:m,file:h}),{config:l,instance:u,startPromise:u.start()}}),w=await Promise.all(f.map(async l=>{const m=await new Promise(h=>{let u=!1;const _=setTimeout(()=>{u||(u=!0,h({kind:"timeout"}))},c);l.startPromise.then(()=>{u||(u=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{u||(u=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:l,outcome:m}}));for(const{task:l,outcome:m}of w){if(m.kind==="started"){this.restoreCachedSharedInstances(l.config);continue}if(m.kind==="failed"){if(j(m.error)){this.onAgentDeletedByPlatform(l.config.name);continue}g(l.instance),d.error("manager",`Failed to start ${l.config.name}: ${m.error}`);continue}i++,d.warn("manager",`Startup pending for ${l.config.name}, continue retrying in background`),l.startPromise.then(()=>{d.info("manager",`Delayed start succeeded: ${l.config.name}`),this.restoreCachedSharedInstances(l.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(l.config.name);return}g(l.instance),d.error("manager",`Delayed start failed: ${l.config.name}: ${h}`)})}if(this.instances.length>0){const l=Math.max(0,this.instances.length-i);d.info("manager",`${l}/${r.length} agent(s) running now`)}i>0&&d.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 X(r.map(({config:c})=>({apiKey:c.aibot.apiKey,wsUrl:c.aibot.url})),()=>this.instances.some(c=>c.hasPendingWork())),await this.upgradeChecker.start())}async stop(){d.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(),d.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=W(e,r),c=new $(i,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),d.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),d.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):d.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=>d.error("manager",`stop shared instance failed ${r}: ${i}`)),d.info("manager",`shared instance stopped: ${r}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(d.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(),d.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const s=T(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.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),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(),d.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(p=>d.error("manager",`stop shared instance failed ${i}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(s=>s.name===e)))return;d.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),d.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){d.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=b(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
2
  `,"utf-8")}catch(t){d.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),d.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(),d.info("manager",`Updated provider config for agent: ${e}`)}async replaceInstance(e,t,s){const a=T(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}=P(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)&&d.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(he(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(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(i.entry,i.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return d.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 fe(this.instances,e)}async probeOne(e,t={}){return _e(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return se(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function fe(n,e){const t=e.concurrency??4,s=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function l(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(){f++,g<w?l(g++):f===w&&p()}const h=Math.min(t,w);for(let u=0;u<h;u++)l(g++)});const r=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,i=a.filter(p=>p.status==="unavailable").length,c=await ce();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 _e(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{Ee as Manager,ce as probeInstalledClientCommands,fe as probeInstances,_e 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.9",
3
+ "version": "3.3.1",
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",