grix-connector 4.2.8 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/deepseek-harness/bridge-installer.js +4 -4
- package/dist/adapter/deepseek-harness/dsh-provider-config.js +7 -7
- package/dist/adapter/deepseek-harness/dsh-version.js +1 -0
- package/dist/adapter/deepseek-harness/jsonrpc-adapter.js +2 -2
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.3.0.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +5 -5
- package/dist/bridge/bridge.js +5 -5
- package/dist/core/upgrade/upgrade-checker.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/package.json +1 -1
- package/scripts/upgrade-guardian.mjs +43 -2
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.2.8.tgz +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as v,mkdirSync as X,readFileSync as S,renameSync as J,writeFileSync as Y}from"node:fs";import{join as g,dirname as Z}from"node:path";import{spawn as U}from"node:child_process";import{log as f}from"../log/index.js";import{GRIX_PATHS as _}from"../log/index.js";import{resolveClientVersion as R}from"../util/client-version.js";import{UpgradeError as T,armWindowsUpgradeHandoff as N,armWindowsUpgradeRollback as Q,cleanupPendingBackup as O,collectEnvInfo as ee,discardPendingStaging as k,finalizePendingTransaction as P,getUpgradeLogTail as te,isVersionAvailable as ae,npmInstall as re,normalizeUpgradeVersion as ie,pendingExists as G,preflightCheck as ne,readPending as E,readPendingPackageVersion as se,removeWindowsHandoffFiles as M,rollbackPendingInstall as oe,updatePending as I,upgradeLog as n,verifyInstalledVersion as de,writePending as ce}from"./npm-upgrader.js";import{isRetriableRegistryError as le,isTransientInstallError as ue}from"../installer/npm-registry.js";const pe=360*60*1e3,he=300*1e3,W=1800*1e3,fe=2,ge=3,me=10,L="grix-connector",C=1e4,x=15,_e=2e3,we=8e3,ve=1e3,ye=2e3,be=120*1e3,q=1e4,Ae=6e4,Se=3,Te=600*1e3,Pe=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),V=600*1e3,D=1800*1e3;function $(s,e=V){const t=Date.parse(s.activation_started_at??s.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function H(s){const e=Date.parse(s.verification_started_at??s.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>be}function Re(s){if((s.guardian_handback_count??0)>=Se)return!1;const e=Date.parse(s.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=Te}function ke(s){if(!Number.isInteger(s)||(s??0)<=0)return!1;try{return process.kill(s,0),!0}catch{return!1}}function A(){const s=g(_.base,"bin"),e=g(s,"upgrade-guardian.mjs");if(v(e))return e;const t=g(s,"upgrade-guardian.fallback.mjs");return v(t)?t:null}function B(s){if(!ke(s.guardian_pid))return!1;try{const t=JSON.parse(S(g(_.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===s.transaction_id&&t.pid===s.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=q)return!0}catch{}const e=Date.parse(s.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=q}function F(s){return new URL(s.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function j(){return g(_.data,"upgrade-state.json")}function z(){const s=j();if(!v(s))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(S(s,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Ee(s){const e=j();X(Z(e),{recursive:!0});const t=e+".tmp";Y(t,JSON.stringify(s),"utf-8"),J(t,e)}function K(){return new Date().toISOString().slice(0,10)}class Ce{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,i,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=i,this.options=a}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),pe)))},he)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=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(!G())return;let e=E();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!Pe.has(e.phase)&&(e=I({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const o=!!(e.wrapper_pause_file&&v(e.wrapper_pause_file));if(e.phase==="staged"&&o){this.schedulePendingReconcile();return}k(e),e=I({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(B(e)&&!H(e)){this.schedulePendingReconcile();return}if(H(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const o=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;Q(e.transaction_id,o),n(`${o}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(B(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if($(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&Re(e)){const o=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;I({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${o}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}if($(e))n(`daemon startup: guardian did not settle ${e.phase} for ${e.target_version} and cannot be resumed; collapsing the transaction by version verdict`),M(),k(e);else{await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}}if(e.phase==="failed"){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"}`);return}const i=se(e),a=R(),r=e.phase==="healthy",d=e.phase==="rolled_back",h=!i||i===e.target_version,c=r?a===e.target_version&&i===e.target_version:!d&&a===e.target_version&&h;if(!c&&h&&!d){if(!$(e)){if((this.options.platform??process.platform)!=="win32"&&i===e.target_version){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(o)||(this.posixGuardianResumeAttempted.add(o),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${i??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${V/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const u=c?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(c?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${i??"unknown"}, target=${e.target_version}; rolled back`),!c){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(o)||(this.recordedRollbackFailures.add(o),this.recordFailure(e.target_version))}if(c){const o=await this.confirmHealthz(we,r?e.target_version:void 0,r?e.old_pid:void 0,r?e.activation_started_at:void 0);if(r&&!o){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(o?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!O(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(u)){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(u,e,!1)}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{f.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},ye))}logThrottled(e,t){const i=Date.now();e===this.guardianWaitLoggedPhase&&i-this.guardianWaitLoggedAt<Ae||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=i,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=A();if(!e)return;U(process.execPath,[e,"activate","--root",_.base],{detached:!0,stdio:"ignore",windowsHide:!0}).unref()}async retryStartupReport(e,t,i){const a=t?.transaction_id,r=()=>{const d=E();if(!(a&&d?.transaction_id!==a)){if(i&&!O(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}P(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let d=2;d<=x;d++){if(this.stopped)return;const h=_e*(d-1)+Math.floor(Math.random()*1e3);if(await new Promise(c=>setTimeout(c,h)),this.stopped)return;if(await this.reportUpgrade(e)){r();return}}n(`startup report (${e.status}) not delivered after ${x} attempts; clearing pending to unblock future checks`),r()}async confirmHealthz(e,t,i,a){let r="19579";try{const c=g(_.data,"health-port");v(c)&&(r=S(c,"utf-8").trim()||r)}catch{}const d=`http://127.0.0.1:${r}/healthz`,h=Date.now()+e;for(;Date.now()<h&&!this.stopped;){try{const c=await fetch(d,{signal:AbortSignal.timeout(2e3)});if(c.ok){if(!t)return!0;const u=await c.json();if(u.status==="ok"&&u.version===t&&typeof u.pid=="number"&&u.pid>0&&u.pid!==i)try{const o=JSON.parse(S(g(_.base,"daemon-status.json"),"utf-8")),y=Date.parse(a??"");return!!(o.state==="running"&&o.pid===u.pid&&typeof o.updated_at=="number"&&Number.isFinite(y)&&o.updated_at>=y)}catch{return!1}}}catch{}await new Promise(c=>setTimeout(c,ve))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){f.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(G()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(l=>this.queryUpgrade(l)))).find(l=>l.available&&l.release)?.release;if(!i)return;const a=R();let r;try{r=ie(i.version)}catch(l){const p=l;await this.reportUpgrade({from_version:a,to_version:String(i.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const d=i.force,h=this.options.platform??process.platform,c=this.options.env??process.env;if(h==="win32"){const l=A(),p=c.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!l||!this.options.requestGracefulShutdown){const m=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...l?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(m),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:m});return}}const u=h==="darwin"&&!!A()&&!!this.options.requestGracefulShutdown;if(h==="darwin"&&!u&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!d&&!this.checkRateLimit(r))return;const o=ne();if(!o.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:o.errorCode,error_msg:o.errorMsg});return}if(!await ae(L,r)){n(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${r}`);const y=Date.now();let w;try{w=ce(a,r,h==="win32"||u?"preparing":void 0).transaction_id,(await re(L,r,void 0,u?{activationMode:"staged"}:{}))?.activationRequired||de(r),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new T("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(h==="win32"){if(!A())throw new T("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");N(process.pid,w),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(u){if(!A())throw new T("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");N(process.pid,w),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(l){const p=l instanceof T?l.code:"NPM_INSTALL_FAILED",b=l instanceof Error?l.message:String(l);n(`upgrade failed: ${p} ${b}`);const m=E();if(w&&m?.transaction_id===w&&(h==="win32"||u?(M(),k(m)):oe(m)&&n("upgrade failed before restart; previous package restored atomically"),P(m)||(n(`failed transaction ${w} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;le(b)||ue(b)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p,error_msg:b,duration_ms:Date.now()-y,upgrade_log:te()})}}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(D/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+D,t=5e3,i=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return f.warn("upgrade",`Idle wait exceeded ${Math.round(D/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(r=>setTimeout(r,t)),Date.now()-a>=i&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=z();if(t.last_failure_at){const d=Date.now()-new Date(t.last_failure_at).getTime();if(d<W)return f.info("upgrade",`In cooldown, ${(W-d)/6e4}m remaining`),!1}const i=t.version_attempts[e]??0;if(i>=ge)return f.info("upgrade",`Version ${e} already tried ${i} times, skipping`),!1;const a=K(),r=t.daily_attempts[a]??0;return r>=fe?(f.info("upgrade",`Already ${r} attempts today, skipping`),!1):!0}recordFailure(e){const t=z(),i=K();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[i]=(t.daily_attempts[i]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const r of Object.keys(t.daily_attempts))r<i&&delete t.daily_attempts[r];const a=Object.keys(t.version_attempts);for(const r of a.slice(0,Math.max(0,a.length-me)))r!==e&&delete t.version_attempts[r];Ee(t)}async queryUpgrade(e){const i=`${F(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:R(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(i,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(C)});if(!a.ok)return f.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const r=await a.json();return r.code!==0?{available:!1}:r.data}catch(a){return f.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...ee()}};return(await Promise.all(this.agentConfigs.map(async a=>{const d=`${F(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(d,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(C)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=g(_.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!v(e)){f.info("upgrade","Guardian not available on this platform, skipping");return}try{const t=U(e,[],{detached:!0,stdio:"ignore"});t.unref(),n(`guardian started (pid ${t.pid})`)}catch(t){f.warn("upgrade",`Failed to start guardian: ${t instanceof Error?t.message:t}`)}}}export{Ce as UpgradeChecker};
|
|
1
|
+
import{closeSync as U,existsSync as y,mkdirSync as N,openSync as Q,readFileSync as A,realpathSync as ee,renameSync as te,writeFileSync as ae}from"node:fs";import{join as f,dirname as re}from"node:path";import{spawn as G}from"node:child_process";import{log as g}from"../log/index.js";import{GRIX_PATHS as m}from"../log/index.js";import{resolveClientVersion as R}from"../util/client-version.js";import{UpgradeError as T,armWindowsUpgradeHandoff as O,armWindowsUpgradeRollback as ie,cleanupPendingBackup as M,collectEnvInfo as ne,discardPendingStaging as k,finalizePendingTransaction as P,getUpgradeLogTail as se,isVersionAvailable as oe,npmInstall as de,normalizeUpgradeVersion as ce,pendingExists as W,preflightCheck as le,readPending as E,readPendingPackageVersion as ue,removeWindowsHandoffFiles as L,rollbackPendingInstall as pe,updatePending as I,upgradeLog as n,verifyInstalledVersion as he,writePending as fe}from"./npm-upgrader.js";import{isRetriableRegistryError as ge,isTransientInstallError as me}from"../installer/npm-registry.js";const _e=360*60*1e3,we=300*1e3,C=1800*1e3,ye=2,ve=3,be=10,x="grix-connector",q=1e4,F=15,Se=2e3,Ae=8e3,Te=1e3,Pe=2e3,Re=120*1e3,V=1e4,ke=6e4,Ee=3,Ie=600*1e3,De=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),H=600*1e3,D=1800*1e3;function $(s,e=H){const t=Date.parse(s.activation_started_at??s.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function B(s){const e=Date.parse(s.verification_started_at??s.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>Re}function $e(s){if((s.guardian_handback_count??0)>=Ee)return!1;const e=Date.parse(s.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=Ie}function Ue(s){if(!Number.isInteger(s)||(s??0)<=0)return!1;try{return process.kill(s,0),!0}catch{return!1}}function j(s){try{return ee(s)}catch{return s}}function z(){try{N(f(m.base,"log"),{recursive:!0});const s=Q(f(m.base,"log","upgrade.log"),"a");return{stdio:["ignore",s,s],logFd:s}}catch{return{stdio:"ignore"}}}function S(){const s=f(m.base,"bin"),e=f(s,"upgrade-guardian.mjs");if(y(e))return j(e);const t=f(s,"upgrade-guardian.fallback.mjs");return y(t)?j(t):null}function K(s){if(!Ue(s.guardian_pid))return!1;try{const t=JSON.parse(A(f(m.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===s.transaction_id&&t.pid===s.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=V)return!0}catch{}const e=Date.parse(s.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=V}function X(s){return new URL(s.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function J(){return f(m.data,"upgrade-state.json")}function Y(){const s=J();if(!y(s))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(A(s,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Ne(s){const e=J();N(re(e),{recursive:!0});const t=e+".tmp";ae(t,JSON.stringify(s),"utf-8"),te(t,e)}function Z(){return new Date().toISOString().slice(0,10)}class He{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,r,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=r,this.options=a}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),_e)))},we)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(r=>this.queryUpgrade(r)))).find(r=>r.available&&r.release)??{available:!1}}async handlePendingOnStartup(){if(!W())return;let e=E();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!De.has(e.phase)&&(e=I({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const o=!!(e.wrapper_pause_file&&y(e.wrapper_pause_file));if(e.phase==="staged"&&o){this.schedulePendingReconcile();return}k(e),e=I({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(K(e)&&!B(e)){this.schedulePendingReconcile();return}if(B(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const o=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;ie(e.transaction_id,o),n(`${o}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(K(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if($(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&$e(e)){const o=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;I({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${o}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}if($(e))n(`daemon startup: guardian did not settle ${e.phase} for ${e.target_version} and cannot be resumed; collapsing the transaction by version verdict`),L(),k(e);else{await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}}if(e.phase==="failed"){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"}`);return}const r=ue(e),a=R(),i=e.phase==="healthy",d=e.phase==="rolled_back",h=!r||r===e.target_version,c=i?a===e.target_version&&r===e.target_version:!d&&a===e.target_version&&h;if(!c&&h&&!d){if(!$(e)){if((this.options.platform??process.platform)!=="win32"&&r===e.target_version){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(o)||(this.posixGuardianResumeAttempted.add(o),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${r??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${H/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const u=c?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(c?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${r??"unknown"}, target=${e.target_version}; rolled back`),!c){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(o)||(this.recordedRollbackFailures.add(o),this.recordFailure(e.target_version))}if(c){const o=await this.confirmHealthz(Ae,i?e.target_version:void 0,i?e.old_pid:void 0,i?e.activation_started_at:void 0);if(i&&!o){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(o?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!M(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(u)){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(u,e,!1)}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{g.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},Pe))}logThrottled(e,t){const r=Date.now();e===this.guardianWaitLoggedPhase&&r-this.guardianWaitLoggedAt<ke||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=r,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=S();if(!e)return;const{stdio:t,logFd:r}=z();try{G(process.execPath,[e,"activate","--root",m.base],{detached:!0,stdio:t,windowsHide:!0}).unref()}finally{if(r!==void 0)try{U(r)}catch{}}}async retryStartupReport(e,t,r){const a=t?.transaction_id,i=()=>{const d=E();if(!(a&&d?.transaction_id!==a)){if(r&&!M(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}P(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let d=2;d<=F;d++){if(this.stopped)return;const h=Se*(d-1)+Math.floor(Math.random()*1e3);if(await new Promise(c=>setTimeout(c,h)),this.stopped)return;if(await this.reportUpgrade(e)){i();return}}n(`startup report (${e.status}) not delivered after ${F} attempts; clearing pending to unblock future checks`),i()}async confirmHealthz(e,t,r,a){let i="19579";try{const c=f(m.data,"health-port");y(c)&&(i=A(c,"utf-8").trim()||i)}catch{}const d=`http://127.0.0.1:${i}/healthz`,h=Date.now()+e;for(;Date.now()<h&&!this.stopped;){try{const c=await fetch(d,{signal:AbortSignal.timeout(2e3)});if(c.ok){if(!t)return!0;const u=await c.json();if(u.status==="ok"&&u.version===t&&typeof u.pid=="number"&&u.pid>0&&u.pid!==r)try{const o=JSON.parse(A(f(m.base,"daemon-status.json"),"utf-8")),v=Date.parse(a??"");return!!(o.state==="running"&&o.pid===u.pid&&typeof o.updated_at=="number"&&Number.isFinite(v)&&o.updated_at>=v)}catch{return!1}}}catch{}await new Promise(c=>setTimeout(c,Te))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){g.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(W()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const r=(await Promise.all(this.agentConfigs.map(l=>this.queryUpgrade(l)))).find(l=>l.available&&l.release)?.release;if(!r)return;const a=R();let i;try{i=ce(r.version)}catch(l){const p=l;await this.reportUpgrade({from_version:a,to_version:String(r.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const d=r.force,h=this.options.platform??process.platform,c=this.options.env??process.env;if(h==="win32"){const l=S(),p=c.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!l||!this.options.requestGracefulShutdown){const _=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...l?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(_),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:_});return}}const u=h==="darwin"&&!!S()&&!!this.options.requestGracefulShutdown;if(h==="darwin"&&!u&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!d&&!this.checkRateLimit(i))return;const o=le();if(!o.ok){await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:o.errorCode,error_msg:o.errorMsg});return}if(!await oe(x,i)){n(`target ${i} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${i}`);const v=Date.now();let w;try{w=fe(a,i,h==="win32"||u?"preparing":void 0).transaction_id,(await de(x,i,void 0,u?{activationMode:"staged"}:{}))?.activationRequired||he(i),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:i,status:"installed",duration_ms:Date.now()-v}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new T("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(h==="win32"){if(!S())throw new T("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");O(process.pid,w),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(u){if(!S())throw new T("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");O(process.pid,w),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(l){const p=l instanceof T?l.code:"NPM_INSTALL_FAILED",b=l instanceof Error?l.message:String(l);n(`upgrade failed: ${p} ${b}`);const _=E();if(w&&_?.transaction_id===w&&(h==="win32"||u?(L(),k(_)):pe(_)&&n("upgrade failed before restart; previous package restored atomically"),P(_)||(n(`failed transaction ${w} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;ge(b)||me(b)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(i),await this.reportUpgrade({from_version:a,to_version:i,status:"failed",error_code:p,error_msg:b,duration_ms:Date.now()-v,upgrade_log:se()})}}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(D/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+D,t=5e3,r=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return g.warn("upgrade",`Idle wait exceeded ${Math.round(D/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(i=>setTimeout(i,t)),Date.now()-a>=r&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=Y();if(t.last_failure_at){const d=Date.now()-new Date(t.last_failure_at).getTime();if(d<C)return g.info("upgrade",`In cooldown, ${(C-d)/6e4}m remaining`),!1}const r=t.version_attempts[e]??0;if(r>=ve)return g.info("upgrade",`Version ${e} already tried ${r} times, skipping`),!1;const a=Z(),i=t.daily_attempts[a]??0;return i>=ye?(g.info("upgrade",`Already ${i} attempts today, skipping`),!1):!0}recordFailure(e){const t=Y(),r=Z();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[r]=(t.daily_attempts[r]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const i of Object.keys(t.daily_attempts))i<r&&delete t.daily_attempts[i];const a=Object.keys(t.version_attempts);for(const i of a.slice(0,Math.max(0,a.length-be)))i!==e&&delete t.version_attempts[i];Ne(t)}async queryUpgrade(e){const r=`${X(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:R(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(r,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(q)});if(!a.ok)return g.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const i=await a.json();return i.code!==0?{available:!1}:i.data}catch(a){return g.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...ne()}};return(await Promise.all(this.agentConfigs.map(async a=>{const d=`${X(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(d,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(q)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=f(m.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!y(e)){g.info("upgrade","Guardian not available on this platform, skipping");return}const{stdio:t,logFd:r}=z();try{const a=G(e,[],{detached:!0,stdio:t});a.unref(),n(`guardian started (pid ${a.pid})`)}catch(a){g.warn("upgrade",`Failed to start guardian: ${a instanceof Error?a.message:a}`)}finally{if(r!==void 0)try{U(r)}catch{}}}}export{He as UpgradeChecker};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{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": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
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",
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
existsSync,
|
|
10
10
|
mkdirSync,
|
|
11
11
|
readFileSync,
|
|
12
|
+
realpathSync,
|
|
12
13
|
renameSync,
|
|
13
14
|
rmSync,
|
|
14
15
|
writeFileSync,
|
|
@@ -45,6 +46,43 @@ const TRANSACTION_PHASES = new Set([
|
|
|
45
46
|
]);
|
|
46
47
|
const RENAME_RETRY_DELAYS_MS = [250, 750, 1_500, 3_000, 5_000];
|
|
47
48
|
|
|
49
|
+
/** 逐侧解析真实路径;解析不了的一侧原样保留,另一侧仍然受益。 */
|
|
50
|
+
function toRealPath(value) {
|
|
51
|
+
try {
|
|
52
|
+
return realpathSync(value);
|
|
53
|
+
} catch {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Windows 路径大小写不敏感(`C:\\Users\\X` 与 `c:\\users\\x` 是同一个文件),
|
|
60
|
+
* 且盘符大小写会随调用方变化;POSIX 必须保持大小写敏感,否则会把两个不同文件
|
|
61
|
+
* 判成同一个。
|
|
62
|
+
*/
|
|
63
|
+
function normalizePathForCompare(value, platform = process.platform) {
|
|
64
|
+
const resolved = resolve(value);
|
|
65
|
+
return platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 判断两个路径是否指向同一个文件。**必须按真实路径比对**:Node 的 ESM 加载器默认
|
|
70
|
+
* 解析软链接,`import.meta.url` 给的是真实路径,而 `process.argv[1]` 是调用方原样
|
|
71
|
+
* 传入的路径。`resolve()` 只做规范化、不解软链,于是 `$GRIX_CONNECTOR_HOME` 只要
|
|
72
|
+
* 含一段软链接(例如 `~/.grix -> /Volumes/disk1/gcf/.grix`),两者就永不相等。
|
|
73
|
+
* 曾导致 guardian 被拉起后 `isMain` 恒为 false、主逻辑一次都没执行:升级包装好、
|
|
74
|
+
* 校验通过、上报 installed,然后 guardian 空转到 10 分钟激活超时,事务被按版本裁决
|
|
75
|
+
* 收口成 rolled_back,错误码还报成误导性的 STARTUP_CRASH。
|
|
76
|
+
*
|
|
77
|
+
* 两侧各自解析、各自归一化,所以"一侧存在一侧不存在"时另一侧的真实路径依然生效;
|
|
78
|
+
* 大小写按平台处理,见 {@link normalizePathForCompare}。
|
|
79
|
+
*/
|
|
80
|
+
export function isSamePath(left, right, platform = process.platform) {
|
|
81
|
+
if (!left || !right) return false;
|
|
82
|
+
return normalizePathForCompare(toRealPath(left), platform)
|
|
83
|
+
=== normalizePathForCompare(toRealPath(right), platform);
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
function pathsFor(rootDir) {
|
|
49
87
|
return {
|
|
50
88
|
pending: join(rootDir, 'data', 'upgrade-pending.json'),
|
|
@@ -739,7 +777,7 @@ function wrapperChildPid(record, runtimeDir, pending, deps = {}) {
|
|
|
739
777
|
&& typeof record?.boot_time === 'number'
|
|
740
778
|
&& Math.abs(record.boot_time - currentBootTime) <= 60_000
|
|
741
779
|
&& typeof record?.cli_path === 'string'
|
|
742
|
-
&&
|
|
780
|
+
&& isSamePath(record.cli_path, expectedCli)
|
|
743
781
|
? record.pid
|
|
744
782
|
: null;
|
|
745
783
|
}
|
|
@@ -1306,6 +1344,9 @@ async function runCli() {
|
|
|
1306
1344
|
? resolve(args[rootIndex + 1])
|
|
1307
1345
|
: resolve(process.env.GRIX_CONNECTOR_HOME || join(homedir(), '.grix'));
|
|
1308
1346
|
const paths = pathsFor(rootDir);
|
|
1347
|
+
// 落一条启动行再做任何判断:guardian 是 detached 起的,早期失败没有任何出口,
|
|
1348
|
+
// 缺这行时"被拉起却什么都没发生"在日志上和"根本没被拉起"完全无法区分。
|
|
1349
|
+
appendLog(paths.log, `${mode}: guardian started pid=${process.pid} node=${process.version} entry=${process.argv[1] ?? ''}`);
|
|
1309
1350
|
const readArg = (name) => {
|
|
1310
1351
|
const index = args.indexOf(name);
|
|
1311
1352
|
return index >= 0 && args[index + 1] ? args[index + 1] : '';
|
|
@@ -1341,5 +1382,5 @@ async function runCli() {
|
|
|
1341
1382
|
}
|
|
1342
1383
|
}
|
|
1343
1384
|
|
|
1344
|
-
const isMain =
|
|
1385
|
+
const isMain = isSamePath(process.argv[1], fileURLToPath(import.meta.url));
|
|
1345
1386
|
if (isMain) process.exitCode = await runCli();
|
|
Binary file
|