grix-connector 3.1.8 → 3.1.10
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,2 +1,2 @@
|
|
|
1
|
-
import{execFile as f}from"node:child_process";import{promisify as y}from"node:util";import{log as
|
|
2
|
-
${m??""}`.trim())})})}function
|
|
1
|
+
import{execFile as f}from"node:child_process";import{promisify as y}from"node:util";import{log as g}from"../log/logger.js";import{probeUrls as h}from"./speed-test.js";const w=y(f),T=1e4,c=[{id:"official",label:"npm \u5B98\u65B9",url:"https://registry.npmjs.org"},{id:"npmmirror",label:"npmmirror (\u6DD8\u5B9D\u955C\u50CF)",url:"https://registry.npmmirror.com"}];async function x(){const t=await h(c.map(r=>({url:r.url,label:r.label})),5e3),e=[];for(const r of t)if(r.reachable){const n=c.find(o=>o.url===r.url);n&&e.push(n)}for(const r of t)if(!r.reachable){const n=c.find(o=>o.url===r.url);n&&!e.includes(n)&&e.push(n)}for(const r of c)e.includes(r)||e.push(r);return e}async function U(){try{const t=process.platform==="win32",e=t?"cmd.exe":"npm",r=t?["/c","npm","config","get","registry"]:["config","get","registry"],{stdout:n}=await w(e,r,{timeout:T,encoding:"utf-8"});return n.trim()}catch{return"https://registry.npmjs.org"}}async function A(t,e,r){const n=await x();if(n.length===0)throw new Error("All npm registries are unreachable. Check your network connection.");let o="";for(const s of n)try{return g.info("installer",`npm install ${t} using ${s.label} (${s.url})`),{output:await R(t,s.url,e,r),registry:s.label}}catch(i){const l=i instanceof Error?i.message:String(i);if(o=l,g.info("installer",`${s.label} failed: ${l.slice(0,200)}`),!I(l))throw i}throw new Error(`npm install failed on all registries. Last error: ${o}`)}function I(t){return/ECONNRESET|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ERR_SOCKET_TIMEOUT|getaddrinfo|socket hang up|network|fetch failed|timed out|tunneling socket|self.signed|404|ETARGET|No matching version|notarget/i.test(t)}function M(t){return/\bEBUSY\b|resource busy or locked|EPERM[^\n]*\brename\b|\bELOCKED\b/i.test(t)}function R(t,e,r,n){return new Promise((o,s)=>{const i=["install","-g",t,"--registry",e,"--prefer-online","--no-audit","--no-fund"],l=process.platform==="win32",p=l?"cmd.exe":"npm",E=l?["/c","npm",...i]:i;f(p,E,{timeout:r,maxBuffer:n},(a,d,m)=>{if(a){const u=m?.trim()||a.message,b=a.killed===!0?`ETIMEDOUT: npm install exceeded ${r}ms (registry likely unreachable): ${u}`:u;s(new Error(b));return}o(`${d??""}
|
|
2
|
+
${m??""}`.trim())})})}function S(t){return c.map(e=>({label:e.label,command:`npm install -g ${t} --registry ${e.url}`}))}export{c as NPM_REGISTRIES,U as getCurrentRegistry,S as getMirrorInstallCommands,I as isRetriableRegistryError,M as isTransientInstallError,A as npmInstallWithMirror,x as probeRegistries};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execFileSync as
|
|
2
|
-
`;try{
|
|
1
|
+
import{execFile as y,execFileSync as u}from"node:child_process";import{promisify as E}from"node:util";import{existsSync as a,readFileSync as m,renameSync as I,statfsSync as N,unlinkSync as w,writeFileSync as M}from"node:fs";import{join as p}from"node:path";import{GRIX_PATHS as d}from"../log/index.js";import{appendRotatingFileSync as v}from"../log/rotation.js";import{resolveClientVersion as x}from"../util/client-version.js";import{getMachineName as F}from"../util/host-info.js";import{resolveInstallId as L}from"../util/install-id.js";import{npmInstallWithMirror as P,NPM_REGISTRIES as $}from"../installer/npm-registry.js";const T=E(y);class s extends Error{code;constructor(r,t){super(t),this.name="UpgradeError",this.code=r}}function g(){return p(d.log,"upgrade.log")}function l(){return p(d.data,"upgrade-pending.json")}function K(){return a(l())}function j(){const e=l();if(!a(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function B(e,r){const t={from_version:e,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0},i=l(),o=i+".tmp";M(o,JSON.stringify(t),"utf-8"),I(o,i)}function H(){const e=l();if(a(e))try{w(e)}catch{}}function f(e){const r=`[${new Date().toISOString()}] ${e}
|
|
2
|
+
`;try{v(g(),r)}catch{}}function J(e=4096){const r=g();if(!a(r))return"";try{const t=m(r,"utf-8");return t.length<=e?t:t.slice(-e)}catch{return""}}function h(){try{const e=process.platform==="win32";return u(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new s("NPM_NOT_FOUND","npm is not available or timed out")}}const c=1e6;function _(){if(process.platform==="win32")return c;let e;try{e=u("npm",["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return c}try{const r=N(e),t=Math.floor(Number(r.bavail)*Number(r.bsize)/(1024*1024));return!Number.isFinite(t)||t<0?c:Math.min(t,c)}catch{return c}}function W(){let e;try{e=h()}catch(t){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:t instanceof Error?t.message:"npm not available"}}const r=_();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:r}:{ok:!0,npmVersion:e,diskFreeMb:r}}function z(){let e="";try{e=h()}catch{}let r=c;try{r=_()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch,host_name:F(),install_id:L()}}async function X(e,r,t=12e4){const i=`${e}@${r}`;f(`npm install -g ${i} starting (with mirror fallback)`);try{const{registry:o}=await P(i,t,10485760);f(`npm install succeeded via ${o}`)}catch(o){const n=o instanceof Error?o.message:String(o);throw f(`npm install failed: ${n}`),/timed out|ETIMEDOUT/i.test(n)?new s("NPM_TIMEOUT",`npm install timed out after ${t/1e3}s (tried all mirrors): ${n}`):n.includes("EACCES")||n.includes("permission denied")?new s("NPM_INSTALL_FAILED",`Permission denied: ${n}`):n.includes("ENOSPC")||n.includes("no space left")?new s("DISK_FULL",`Disk full: ${n}`):n.includes("404")||n.includes("not found")?new s("NPM_INSTALL_FAILED",`Package not found: ${n}`):new s("NPM_INSTALL_FAILED",n)}}function q(e){const r=x();if(r!==e)throw new s("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}async function Q(e,r){let t=!1;for(const i of $)try{const{stdout:o}=await T("npm",["view",`${e}@${r}`,"version","--registry",i.url,"--no-audit","--no-fund"],{timeout:8e3});if(o.trim())return!0}catch(o){const n=o,S=n.stderr||n.message||String(o);/ETARGET|No matching version|notarget|E404/i.test(S)&&(t=!0)}return!t}export{s as UpgradeError,_ as checkDiskSpace,h as checkNpmAvailable,z as collectEnvInfo,J as getUpgradeLogTail,Q as isVersionAvailable,X as npmInstall,K as pendingExists,W as preflightCheck,j as readPending,H as removePending,f as upgradeLog,q as verifyInstalledVersion,B as writePending};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as
|
|
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 +1 @@
|
|
|
1
|
-
function a(
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
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",
|
|
@@ -61,8 +61,11 @@ sleep 15
|
|
|
61
61
|
ELAPSED=0
|
|
62
62
|
while [ $ELAPSED -lt $HEALTH_TIMEOUT ]; do
|
|
63
63
|
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# healthz 通过=新版健康。这里不删 pending:删了会抢在新进程的
|
|
65
|
+
# handlePendingOnStartup 之前,使其读不到 pending、success 回执发不出去
|
|
66
|
+
# (后端只见 installed 不见 success)。交由新进程上报 success 后自行删 pending,
|
|
67
|
+
# guardian 只负责崩溃回滚。
|
|
68
|
+
log "healthz passed, upgrade healthy; leaving pending for in-process success report"
|
|
66
69
|
exit 0
|
|
67
70
|
fi
|
|
68
71
|
sleep 3
|
|
@@ -72,8 +75,8 @@ done
|
|
|
72
75
|
# Extra grace period for slow systems
|
|
73
76
|
sleep 15
|
|
74
77
|
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
# 同上:不删 pending,交由进程内 handlePendingOnStartup 上报 success 并删除。
|
|
79
|
+
log "healthz passed (delayed), upgrade healthy; leaving pending for in-process success report"
|
|
77
80
|
exit 0
|
|
78
81
|
fi
|
|
79
82
|
|
|
@@ -83,7 +86,10 @@ log "healthz timeout, crash_count=$CRASH_COUNT"
|
|
|
83
86
|
|
|
84
87
|
if [ $CRASH_COUNT -ge $MAX_CRASHES ]; then
|
|
85
88
|
log "rollback: installing ${NPM_PACKAGE}@${FROM_VERSION}"
|
|
86
|
-
|
|
89
|
+
# P5: 先用默认源,失败再显式回退官方 npm(默认源可能是淘宝镜像,旧版若没同步同样会失败)。
|
|
90
|
+
if npm install -g "${NPM_PACKAGE}@${FROM_VERSION}" --prefer-online --no-audit --no-fund >> "$LOG_FILE" 2>&1 \
|
|
91
|
+
|| { log "rollback via default registry failed, retrying official npm"; \
|
|
92
|
+
npm install -g "${NPM_PACKAGE}@${FROM_VERSION}" --registry https://registry.npmjs.org --prefer-online --no-audit --no-fund >> "$LOG_FILE" 2>&1; }; then
|
|
87
93
|
log "rollback succeeded"
|
|
88
94
|
rm -f "$PENDING_FILE"
|
|
89
95
|
else
|