grix-connector 4.4.8-beta.1 → 4.4.10-beta.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.
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.4.10-beta.1.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +6 -6
- package/dist/grix.js +7 -8
- package/dist/service/windows-wrapper-migration.js +1 -1
- package/package.json +1 -1
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.4.8-beta.1.tgz +0 -0
|
Binary file
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bridgeVersion": "4.4.
|
|
4
|
-
"tarball": "grix-dsh-bridge-4.4.
|
|
5
|
-
"size":
|
|
6
|
-
"unpackedSize":
|
|
7
|
-
"shasum": "
|
|
8
|
-
"integrity": "sha512
|
|
3
|
+
"bridgeVersion": "4.4.10-beta.1",
|
|
4
|
+
"tarball": "grix-dsh-bridge-4.4.10-beta.1.tgz",
|
|
5
|
+
"size": 22636,
|
|
6
|
+
"unpackedSize": 90347,
|
|
7
|
+
"shasum": "73f363064c7b81e08e6193fd2c280c2e1beac261",
|
|
8
|
+
"integrity": "sha512-QIXEfmOHNdZqGs9A23FkTzC4xvy59aT6iwda0j4mbBP9e9x1A19kCUgukYxg4fFo+9z8SkchSbJ6VW7pYU96oQ=="
|
|
9
9
|
}
|
package/dist/grix.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),S.version&&(console.log(et()),process.exit(0)),S.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
2
|
+
import D from"node:path";import{writeFileSync as re}from"node:fs";import{Manager as xe}from"./manager.js";import{ensureGrixDirs as be,initLogger as Pe,log as i,installProcessLogRotation as Ie,setConsoleOutput as ne}from"./core/log/index.js";import{HealthServer as Ce,bindPortOrFail as oe}from"./core/runtime/index.js";import{writePidFile as ke,removePidFile as O,readDaemonPid as Te}from"./core/runtime/index.js";import{resolveRuntimePaths as W}from"./core/config/index.js";import{supportsNonMitmRelayConfig as _e}from"./core/config/provider-env.js";import{ServiceManager as ae}from"./service/service-manager.js";import{RESTART_EXIT_CODE as ie}from"./core/upgrade/version-store.js";import{migrateWindowsWrapper as K}from"./service/windows-wrapper-migration.js";const Ue=300*1e3;import{killProcessesByCommandLine as $e,isWindowsElevated as Le,shouldSweepWindowsWrappers as Fe}from"./service/process-control.js";import{buildServiceID as Oe}from"./service/service-paths.js";import{acquireDaemonLock as He,isLockHolderSameProcess as Me,readDaemonLock as X,releaseDaemonLock as H}from"./runtime/daemon-lock.js";import{writeDaemonStatus as N,removeDaemonStatus as Ge}from"./runtime/service-state.js";import{AdminServer as Ne,generateToken as Be,writeTokenFile as je}from"./core/admin/index.js";import{initSentry as We,closeSentry as V,reportFatal as q}from"./core/observability/sentry.js";import{initProxyManager as Ke,getProxyManager as f,relayHostsForClientType as se,RelayStateStore as Xe,SecretStore as Ve}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as le,switchAgentRelayMode as Y,reconcileRelayStates as ce}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as de,RelayCredentialError as pe}from"./core/proxy/relay-credential.js";import{RelayFetchError as ge,RelayToggleCoordinator as qe}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as Ye,clearHermesProfileRelay as Je,discoverHermesProfiles as ze}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as Qe,enableRelayCredential as Ze,RelayRouteError as ue}from"./core/proxy/relay-credential-router.js";import{upgradeStatusSnapshot as et}from"./core/upgrade/npm-upgrader.js";import{resolveClientVersion as tt}from"./core/util/client-version.js";import{parseCliArgs as rt}from"./core/util/cli-args.js";const{command:x,flags:R,unknownFlags:J}=rt(process.argv.slice(2));if(J.length>0&&(console.error(`Unknown option${J.length>1?"s":""}: ${J.map(r=>`--${r}`).join(", ")}
|
|
3
|
+
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),R.version&&(console.log(tt()),process.exit(0)),R.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
5
4
|
|
|
6
5
|
Usage: grix-connector <command> [options]
|
|
7
6
|
|
|
@@ -35,9 +34,9 @@ Examples:
|
|
|
35
34
|
grix-connector start # Start as system service
|
|
36
35
|
grix-connector status # Check service status
|
|
37
36
|
grix-connector restart # Restart the service
|
|
38
|
-
`),process.exit(0)),x==="reload"){const r=Te();r||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(r,0)}catch{console.error(`reload failed: daemon process ${r} is not running (stale pid file)`),process.exit(1)}{const d=
|
|
37
|
+
`),process.exit(0)),x==="reload"){const r=Te();r||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(r,0)}catch{console.error(`reload failed: daemon process ${r} is not running (stale pid file)`),process.exit(1)}{const d=X(W().daemonLockFile);d&&d.pid===r&&!Me(d)&&(console.error(`reload failed: pid ${r} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(r,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:r},null,2)),process.exit(0)}catch(d){console.error(`reload failed: ${d instanceof Error?d.message:d}`),process.exit(1)}}const fe=["start","stop","restart","status"];if(x&&fe.includes(x)){process.platform==="win32"&&["start","stop","restart"].includes(x)&&!Le()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
|
|
39
38
|
using Startup folder auto-start instead. For full Task Scheduler integration,
|
|
40
|
-
right-click the terminal and select "Run as administrator".`);const r=
|
|
41
|
-
Valid commands: ${fe.join(", ")}`),process.exit(1));const s=
|
|
42
|
-
`),i.error("main",r.message.replace(/\n/g," \u2014 "));const d=K(s.daemonLockFile);d?.pid===process.pid&&d.instance_id===C&&await G(s.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:C,updated_at:Date.now(),reason:`port_bind_${r.kind}:${r.label}:${r.port}`}).catch(()=>{}),await W(),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(1)}async function I(r){if(J)return;J=!0,i.info("main",`Received ${r}, shutting down...`),M.markShuttingDown();const d=r==="upgrade-switch"?ae:2,R=setTimeout(()=>{i.error("main","Shutdown timed out, forcing exit"),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(d)},1e4);try{await n.stop(),await f()?.stop().catch(()=>{}),await b.stop(),await M.stop(),await W(),await H(s.daemonLockFile),await Me(s.daemonStatusFile).catch(()=>{}),clearTimeout(R),O(),i.info("main","Shutdown complete"),process.exit(r==="upgrade-switch"?ae:0)}catch(u){i.error("main",`Shutdown error: ${u}`),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(d)}}async function nt(){if(be(),Pe(),Ie(s.stdoutLogFile,s.stderrLogFile),re(!1),Le()){const e=Fe(s.rootDir,"win32");await _e(e,{platform:"win32"})}try{C=(await Oe(s.daemonLockFile,s.rootDir)).instance_id}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}if(await je(),Ce(),i.info("main",`grix-connector starting (PID ${process.pid})`),await G(s.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:C,updated_at:Date.now()}),process.env.GRIX_LAUNCHER_MANAGED!=="1"&&await new oe({cliPath:D.resolve(process.argv[1]??""),nodePath:process.execPath}).adoptLauncher(s.rootDir).then(e=>{e&&i.info("main","Service definition now points at the version-store launcher")}).catch(e=>{i.warn("main",`Launcher adoption failed: ${e instanceof Error?e.message:e}`)}),(await ie({rootDir:s.rootDir,requestGracefulShutdown:()=>{I("wrapper-migration")}}).catch(e=>(i.warn("main",`Windows wrapper migration failed: ${e instanceof Error?e.message:e}`),null)))?.handoffStarted)return;const d=parseInt(S["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await ne({label:"health",port:d,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:a=>M.start(a)});e&&await he(e)}const R=D.join(s.dataDir,"health-port");te(R,String(d),"utf-8"),process.on("SIGINT",()=>I("SIGINT")),process.on("SIGTERM",()=>I("SIGTERM")),process.on("SIGHUP",()=>{J||(i.info("main","Received SIGHUP, reloading config..."),n.reload().then(e=>i.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>i.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let u="",p=0,T;process.on("uncaughtException",e=>{const a=e instanceof Error?e.stack??e.message:String(e);a===u?(p++,(p<=3||p%100===0)&&i.error("main",`Uncaught exception (x${p}): ${a}`)):(p>3&&i.error("main",`Previous exception repeated ${p} times total`),u=a,p=1,i.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),T||(T=setTimeout(()=>{p>3&&i.error("main",`Previous exception repeated ${p} times total`),u="",p=0,T=void 0},1e4).unref())),!ye(e)&&(X(e,"uncaughtException"),I("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!ye(e)&&(X(e,"unhandledRejection"),I("unhandledRejection"))});const U=Ke(s.dataDir);await U.clearRuntimeState();const P=new We(D.join(s.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),B=new Xe(D.join(s.dataDir,"secrets"));n.setRelaySecretResolver(e=>B.get(e));const _={hasDirectConfig:e=>n.hasDirectRelayConfig(e),clearDirectConfig:(e,a)=>n.setAgentProvider(e,void 0,{deferRestart:a?.deferRestart===!0})},N=()=>({pm:f(),agents:n,direct:_,store:P,secrets:B}),$=U.getRelayAgents();if($.length>0)try{await U.startIfAnyRelayAgent(),i.info("main",`mitm proxy started for relay agents: ${$.join(", ")} (per-agent; others stay direct)`)}catch(e){i.error("main",`mitm proxy FAILED to start (relay agents: ${$.join(", ")}): ${e}. These agents will REFUSE to start (they must not silently fall back to your own account). Fix the proxy or turn relay off for them. Other agents are unaffected.`)}else i.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');let L=null;if(f()){const e=async()=>{const t=f();t&&await le(t,n)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};n.setRelayEnvSettledHandler(()=>{const t=f();t&&ce(t,_,P),e().catch(o=>{i.warn("main",`failed to stop idle mitm proxy: ${o}`)})});const a=()=>{const t=f(),o=t.getRelayAgents(),l=t.getDegradedRelayAgents();return{enabled:o.length>0,relayAgents:o,staleRelayAgents:n.getAgentsWithStaleRelayEnv(),degradedRelayAgents:l,relayState:P.list(),runtime:t.getRuntimeInfo(),config:t.getConfigSnapshot()}},c=new Ve,A={status:()=>a(),setAgentRelay:async(t,o,l)=>{const g=f(),w={info:h=>i.info("main",h),warn:h=>i.warn("main",h)},y=h=>n.getAgentsStatus().find(E=>E.name===h),m=y(t);if(!o){c.cancel(t);const h=await c.runExclusive(t,async()=>{const E=await V(N(),w,{agentName:t,target:"off"});return{...a(),restarted:E.restarted,busy:E.busy,...E.pending?{pending:E.pending}:{}}});return n.reportRelayStateLocalChange(t),h}if(!m)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});if(!Ue(m.clientType)&&se(m.clientType).length===0)throw Object.assign(new Error(`client type "${m.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const F=await c.runExclusive(t,async()=>{const h=c.epoch(t),E=y(t);if(!E)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});const Q=String(E.agentId??"").trim();if(!Q)throw new pe(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const Z=c.waitCancelled(t,h);let k;try{k=await Promise.race([n.fetchRelayCredential(t,{model:l}),Z.promise.then(()=>{throw new ge("relay enable cancelled by a later disable","CANCELLED")})])}finally{Z.dispose()}c.assertCurrent(t,h);const{restarted:Re,busy:Ae,pending:ee}=await de(g,n,y,w,{localName:t,agentId:Q,virtualKey:k.apiKey,anthropicBaseUrl:k.anthropicBaseUrl,openaiBaseUrl:k.openaiBaseUrl,model:l,...k.directRelay!==void 0?{directRelay:k.directRelay}:{}},(Ee,ve,De)=>n.setAgentProvider(Ee,ve,{deferRestart:De?.deferRestart===!0}),{store:P,secrets:B,direct:_});if(c.epoch(t)!==h)throw await V(N(),w,{agentName:t,target:"off"}),new ge("relay enable cancelled by a later disable","CANCELLED");return{...a(),restarted:Re,busy:Ae,...ee?{pending:ee}:{}}});return n.reportRelayStateLocalChange(t),F},setAgentRelayCredential:async(t,o)=>{const l=f(),g=n.getAgentsStatus(),{restarted:w,busy:y,pending:m}=await de(l,n,v=>g.find(F=>F.name===v),{info:v=>i.info("main",v),warn:v=>i.warn("main",v)},{localName:t,agentId:o.agentId,virtualKey:o.virtualKey,anthropicBaseUrl:o.anthropicBaseUrl,openaiBaseUrl:o.openaiBaseUrl,model:o.model,...o.directRelay!==void 0?{directRelay:o.directRelay}:{}},(v,F,h)=>n.setAgentProvider(v,F,{deferRestart:h?.deferRestart===!0}),{store:P,secrets:B,direct:_});return n.reportRelayStateLocalChange(t),{...a(),restarted:w,busy:y,...m?{pending:m}:{}}},disableAll:async()=>{const t=f(),o={info:g=>i.info("main",g),warn:g=>i.warn("main",g)},l=new Set([...t.getRelayAgents(),...n.getDirectRelayConfigAgentNames(),...Object.keys(P.list())]);for(const g of l)c.cancel(g);for(const g of l)await c.runExclusive(g,()=>V(N(),o,{agentName:g,target:"off"})),n.reportRelayStateLocalChange(g);return await le(t,n),a()},setRoute:async(t,o)=>{const l=o,g=f();return g.setRoute({routeKey:t,targetBaseUrl:l.targetBaseUrl,...l.headers?{headers:l.headers}:{},...l.model?{model:l.model}:{},...l.modelMap?{modelMap:l.modelMap}:{},...l.passthrough?{passthrough:!0}:{},...l.relayOnly?{relayOnly:!0}:{},...l.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await g.persistConfig(),a()},deleteRoute:async t=>{const o=f();o.deleteRoute(t),await o.persistConfig()},setDefaultRoute:async t=>{const o=f();return o.setDefaultRouteKey(t),await o.persistConfig(),a()},setInterceptHosts:async t=>{const o=f();return o.setInterceptHosts(t),await o.persistConfig(),a()}};L=A,b.setProxyHandler(A),n.setRelayStateApplier({getLocalState:t=>{const o=f();if(o.getRelayAgents().includes(t)){const g=n.getAgentsStatus().find(m=>m.name===t),w=`grix-gateway-${String(g?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:o.getConfigSnapshot().routes?.find(m=>m.routeKey===w)?.model?.trim()||void 0}}const l=n.getAgentProvider(t);return l?.api_key?.trim()?{enabled:!0,model:l.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(t,o,l)=>{const g=n.getAgentsStatus().find(m=>m.name===t),w=String(g?.agentId??"").trim();if(!w)throw new pe(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const y=await A.setAgentRelayCredential(t,{agentId:w,virtualKey:o.apiKey,anthropicBaseUrl:o.anthropicBaseUrl,openaiBaseUrl:o.openaiBaseUrl,model:l??o.model,...o.directRelay!==void 0?{directRelay:o.directRelay}:{}});return{restarted:y.restarted===!0,busy:y.busy===!0,...y.pending?{pending:y.pending}:{}}},applyDisable:async t=>{const o=await A.setAgentRelay(t,!1);return{restarted:o.restarted===!0,busy:o.busy===!0,...o.pending?{pending:o.pending}:{}}}})}if(M.setStatusProvider(()=>n.getAgentsStatus()),M.setMetaProvider(()=>{const e=n.getAgentsStatus();return{ws:{connected:e.filter(a=>a.wsConnected===!0).length,total:e.length},upgrade:Ze()}}),await n.start(rt),$.length>0){const e=n.getAgentsStatus();for(const a of $){const c=e.find(t=>t.name===a);if(!c)continue;const A=se(c.clientType);A.length!==0&&await U.setAgentRelayEnabled(a,!0,{relayHosts:A}).catch(t=>{i.warn("main",`failed to reconcile relay hosts for "${a}": ${t}`)})}}ce(U,_,P);const Y=parseInt(S["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);b.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e),reload:()=>n.reload()}),b.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),b.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,a)=>n.probeOne(e,a)}),b.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)});const z={listAgents:()=>n.getAgentsStatus(),enableManaged:(e,a,c)=>{if(!L)throw new ue(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return L.setAgentRelayCredential(e,{agentId:a,virtualKey:c.virtualKey,anthropicBaseUrl:c.anthropicBaseUrl,openaiBaseUrl:c.openaiBaseUrl,model:c.model,...c.directRelay!==void 0?{directRelay:c.directRelay}:{}})},disableManaged:e=>{if(!L)throw new ue(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return L.setAgentRelay(e,!1)},enableHermesProfile:(e,a)=>qe({agentId:e,baseUrl:a.openaiBaseUrl??"",virtualKey:a.virtualKey,model:a.model??""}),disableHermesProfile:e=>Je(e)};b.setRelayCredentialHandler({listHermesProfiles:()=>Ye(),enable:async(e,a)=>{const{target:c,result:A}=await Qe(z,e,a);return{target:c,...A}},disable:async e=>{const{target:a,result:c}=await ze(z,e);return{target:a,...c}}});{const e=await ne({label:"admin",port:Y,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:a=>b.start(a)});e&&await he(e)}const we=D.join(s.dataDir,"admin-token"),Se=D.join(s.dataDir,"admin-port");Ne(we,me),te(Se,String(Y),"utf-8"),await G(s.daemonStatusFile,{state:"running",pid:process.pid,instance_id:C,updated_at:Date.now()}),process.send&&process.send("ready"),i.info("main","grix-connector ready")}nt().catch(async r=>{const d=`Fatal: ${r instanceof Error?r.stack??r.message:String(r)}`;try{process.stderr.write(`${d}
|
|
43
|
-
`)}catch{}
|
|
39
|
+
right-click the terminal and select "Run as administrator".`);const r=W(),d=R["config-dir"]??(R.profile?D.join(r.configDir,R.profile):void 0),S=D.resolve(process.argv[1]||`${r.rootDir}/dist/grix.js`),u=new ae({cliPath:S,nodePath:process.execPath});try{let p;switch(x){case"start":(await u.status({rootDir:r.rootDir})).installed?p=await u.start({rootDir:r.rootDir}):p=await u.install({rootDir:r.rootDir,configDir:d});break;case"stop":p=await u.stop({rootDir:r.rootDir});break;case"restart":(await u.status({rootDir:r.rootDir})).installed?p=await u.restart({rootDir:r.rootDir}):p=await u.install({rootDir:r.rootDir,configDir:d});break;case"status":p=await u.status({rootDir:r.rootDir});break}console.log(JSON.stringify(p,null,2)),process.exit(0)}catch(p){console.error(`${x} failed: ${p instanceof Error?p.message:p}`),process.exit(1)}}else x&&(console.error(`Unknown command: ${x}
|
|
40
|
+
Valid commands: ${fe.join(", ")}`),process.exit(1));const s=W(),nt=R["config-dir"]??(R.profile?`${s.configDir}/${R.profile}`:void 0),n=new xe({requestGracefulShutdown:r=>{P(r)},onTransactionSettled:()=>{K({rootDir:s.rootDir,requestGracefulShutdown:()=>{P("wrapper-migration")}}).then(r=>{r.handoffStarted&&i.info("main","Wrapper handoff started after the upgrade transaction settled")}).catch(r=>{i.warn("main",`Post-upgrade wrapper migration failed: ${r instanceof Error?r.message:r}`)})}}),M=new Ce,me=Be(),b=new Ne(me);let B=!1,k;async function he(r){process.stderr.write(r.message+`
|
|
41
|
+
`),i.error("main",r.message.replace(/\n/g," \u2014 "));const d=X(s.daemonLockFile);d?.pid===process.pid&&d.instance_id===k&&await N(s.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:k,updated_at:Date.now(),reason:`port_bind_${r.kind}:${r.label}:${r.port}`}).catch(()=>{}),await V(),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(1)}async function P(r){if(B)return;B=!0,i.info("main",`Received ${r}, shutting down...`),M.markShuttingDown();const d=r==="upgrade-switch"?ie:2,S=setTimeout(()=>{i.error("main","Shutdown timed out, forcing exit"),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(d)},1e4);try{await n.stop(),await f()?.stop().catch(()=>{}),await b.stop(),await M.stop(),await V(),await H(s.daemonLockFile),await Ge(s.daemonStatusFile).catch(()=>{}),clearTimeout(S),O(),i.info("main","Shutdown complete"),process.exit(r==="upgrade-switch"?ie:0)}catch(u){i.error("main",`Shutdown error: ${u}`),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(d)}}async function ot(){if(be(),Pe(),Ie(s.stdoutLogFile,s.stderrLogFile),ne(!1),Fe()){const e=Oe(s.rootDir,"win32");await $e(e,{platform:"win32"})}try{k=(await He(s.daemonLockFile,s.rootDir)).instance_id}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}if(await We(),ke(),i.info("main",`grix-connector starting (PID ${process.pid})`),await N(s.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:k,updated_at:Date.now()}),process.env.GRIX_LAUNCHER_MANAGED!=="1"&&await new ae({cliPath:D.resolve(process.argv[1]??""),nodePath:process.execPath}).adoptLauncher(s.rootDir).then(e=>{e&&i.info("main","Service definition now points at the version-store launcher")}).catch(e=>{i.warn("main",`Launcher adoption failed: ${e instanceof Error?e.message:e}`)}),(await K({rootDir:s.rootDir,requestGracefulShutdown:()=>{P("wrapper-migration")}}).catch(e=>(i.warn("main",`Windows wrapper migration failed: ${e instanceof Error?e.message:e}`),null)))?.handoffStarted)return;process.platform==="win32"&&process.env.GRIX_LAUNCHER_MANAGED!=="1"&&setInterval(()=>{B||K({rootDir:s.rootDir,requestGracefulShutdown:()=>{P("wrapper-migration")}}).then(o=>{o.handoffStarted&&i.info("main","Wrapper handoff started by the periodic migration retry")}).catch(o=>{i.warn("main",`Periodic wrapper migration failed: ${o instanceof Error?o.message:o}`)})},Ue).unref();const d=parseInt(R["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await oe({label:"health",port:d,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:o=>M.start(o)});e&&await he(e)}const S=D.join(s.dataDir,"health-port");re(S,String(d),"utf-8"),process.on("SIGINT",()=>P("SIGINT")),process.on("SIGTERM",()=>P("SIGTERM")),process.on("SIGHUP",()=>{B||(i.info("main","Received SIGHUP, reloading config..."),n.reload().then(e=>i.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>i.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let u="",p=0,T;process.on("uncaughtException",e=>{const o=e instanceof Error?e.stack??e.message:String(e);o===u?(p++,(p<=3||p%100===0)&&i.error("main",`Uncaught exception (x${p}): ${o}`)):(p>3&&i.error("main",`Previous exception repeated ${p} times total`),u=o,p=1,i.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),T||(T=setTimeout(()=>{p>3&&i.error("main",`Previous exception repeated ${p} times total`),u="",p=0,T=void 0},1e4).unref())),!ye(e)&&(q(e,"uncaughtException"),P("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!ye(e)&&(q(e,"unhandledRejection"),P("unhandledRejection"))});const _=Ke(s.dataDir);await _.clearRuntimeState();const I=new Xe(D.join(s.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),G=new Ve(D.join(s.dataDir,"secrets"));n.setRelaySecretResolver(e=>G.get(e));const U={hasDirectConfig:e=>n.hasDirectRelayConfig(e),clearDirectConfig:(e,o)=>n.setAgentProvider(e,void 0,{deferRestart:o?.deferRestart===!0})},j=()=>({pm:f(),agents:n,direct:U,store:I,secrets:G}),$=_.getRelayAgents();if($.length>0)try{await _.startIfAnyRelayAgent(),i.info("main",`mitm proxy started for relay agents: ${$.join(", ")} (per-agent; others stay direct)`)}catch(e){i.error("main",`mitm proxy FAILED to start (relay agents: ${$.join(", ")}): ${e}. These agents will REFUSE to start (they must not silently fall back to your own account). Fix the proxy or turn relay off for them. Other agents are unaffected.`)}else i.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');let L=null;if(f()){const e=async()=>{const t=f();t&&await le(t,n)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};n.setRelayEnvSettledHandler(()=>{const t=f();t&&ce(t,U,I),e().catch(a=>{i.warn("main",`failed to stop idle mitm proxy: ${a}`)})});const o=()=>{const t=f(),a=t.getRelayAgents(),l=t.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:n.getAgentsWithStaleRelayEnv(),degradedRelayAgents:l,relayState:I.list(),runtime:t.getRuntimeInfo(),config:t.getConfigSnapshot()}},c=new qe,A={status:()=>o(),setAgentRelay:async(t,a,l)=>{const g=f(),w={info:h=>i.info("main",h),warn:h=>i.warn("main",h)},y=h=>n.getAgentsStatus().find(E=>E.name===h),m=y(t);if(!a){c.cancel(t);const h=await c.runExclusive(t,async()=>{const E=await Y(j(),w,{agentName:t,target:"off"});return{...o(),restarted:E.restarted,busy:E.busy,...E.pending?{pending:E.pending}:{}}});return n.reportRelayStateLocalChange(t),h}if(!m)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});if(!_e(m.clientType)&&se(m.clientType).length===0)throw Object.assign(new Error(`client type "${m.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const F=await c.runExclusive(t,async()=>{const h=c.epoch(t),E=y(t);if(!E)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});const Z=String(E.agentId??"").trim();if(!Z)throw new pe(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const ee=c.waitCancelled(t,h);let C;try{C=await Promise.race([n.fetchRelayCredential(t,{model:l}),ee.promise.then(()=>{throw new ge("relay enable cancelled by a later disable","CANCELLED")})])}finally{ee.dispose()}c.assertCurrent(t,h);const{restarted:Se,busy:Ae,pending:te}=await de(g,n,y,w,{localName:t,agentId:Z,virtualKey:C.apiKey,anthropicBaseUrl:C.anthropicBaseUrl,openaiBaseUrl:C.openaiBaseUrl,model:l,...C.directRelay!==void 0?{directRelay:C.directRelay}:{}},(Ee,ve,De)=>n.setAgentProvider(Ee,ve,{deferRestart:De?.deferRestart===!0}),{store:I,secrets:G,direct:U});if(c.epoch(t)!==h)throw await Y(j(),w,{agentName:t,target:"off"}),new ge("relay enable cancelled by a later disable","CANCELLED");return{...o(),restarted:Se,busy:Ae,...te?{pending:te}:{}}});return n.reportRelayStateLocalChange(t),F},setAgentRelayCredential:async(t,a)=>{const l=f(),g=n.getAgentsStatus(),{restarted:w,busy:y,pending:m}=await de(l,n,v=>g.find(F=>F.name===v),{info:v=>i.info("main",v),warn:v=>i.warn("main",v)},{localName:t,agentId:a.agentId,virtualKey:a.virtualKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}},(v,F,h)=>n.setAgentProvider(v,F,{deferRestart:h?.deferRestart===!0}),{store:I,secrets:G,direct:U});return n.reportRelayStateLocalChange(t),{...o(),restarted:w,busy:y,...m?{pending:m}:{}}},disableAll:async()=>{const t=f(),a={info:g=>i.info("main",g),warn:g=>i.warn("main",g)},l=new Set([...t.getRelayAgents(),...n.getDirectRelayConfigAgentNames(),...Object.keys(I.list())]);for(const g of l)c.cancel(g);for(const g of l)await c.runExclusive(g,()=>Y(j(),a,{agentName:g,target:"off"})),n.reportRelayStateLocalChange(g);return await le(t,n),o()},setRoute:async(t,a)=>{const l=a,g=f();return g.setRoute({routeKey:t,targetBaseUrl:l.targetBaseUrl,...l.headers?{headers:l.headers}:{},...l.model?{model:l.model}:{},...l.modelMap?{modelMap:l.modelMap}:{},...l.passthrough?{passthrough:!0}:{},...l.relayOnly?{relayOnly:!0}:{},...l.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await g.persistConfig(),o()},deleteRoute:async t=>{const a=f();a.deleteRoute(t),await a.persistConfig()},setDefaultRoute:async t=>{const a=f();return a.setDefaultRouteKey(t),await a.persistConfig(),o()},setInterceptHosts:async t=>{const a=f();return a.setInterceptHosts(t),await a.persistConfig(),o()}};L=A,b.setProxyHandler(A),n.setRelayStateApplier({getLocalState:t=>{const a=f();if(a.getRelayAgents().includes(t)){const g=n.getAgentsStatus().find(m=>m.name===t),w=`grix-gateway-${String(g?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(m=>m.routeKey===w)?.model?.trim()||void 0}}const l=n.getAgentProvider(t);return l?.api_key?.trim()?{enabled:!0,model:l.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(t,a,l)=>{const g=n.getAgentsStatus().find(m=>m.name===t),w=String(g?.agentId??"").trim();if(!w)throw new pe(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const y=await A.setAgentRelayCredential(t,{agentId:w,virtualKey:a.apiKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:l??a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}});return{restarted:y.restarted===!0,busy:y.busy===!0,...y.pending?{pending:y.pending}:{}}},applyDisable:async t=>{const a=await A.setAgentRelay(t,!1);return{restarted:a.restarted===!0,busy:a.busy===!0,...a.pending?{pending:a.pending}:{}}}})}if(M.setStatusProvider(()=>n.getAgentsStatus()),M.setMetaProvider(()=>{const e=n.getAgentsStatus();return{ws:{connected:e.filter(o=>o.wsConnected===!0).length,total:e.length},upgrade:et()}}),await n.start(nt),$.length>0){const e=n.getAgentsStatus();for(const o of $){const c=e.find(t=>t.name===o);if(!c)continue;const A=se(c.clientType);A.length!==0&&await _.setAgentRelayEnabled(o,!0,{relayHosts:A}).catch(t=>{i.warn("main",`failed to reconcile relay hosts for "${o}": ${t}`)})}}ce(_,U,I);const z=parseInt(R["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);b.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e),reload:()=>n.reload()}),b.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),b.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,o)=>n.probeOne(e,o)}),b.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)});const Q={listAgents:()=>n.getAgentsStatus(),enableManaged:(e,o,c)=>{if(!L)throw new ue(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return L.setAgentRelayCredential(e,{agentId:o,virtualKey:c.virtualKey,anthropicBaseUrl:c.anthropicBaseUrl,openaiBaseUrl:c.openaiBaseUrl,model:c.model,...c.directRelay!==void 0?{directRelay:c.directRelay}:{}})},disableManaged:e=>{if(!L)throw new ue(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return L.setAgentRelay(e,!1)},enableHermesProfile:(e,o)=>Ye({agentId:e,baseUrl:o.openaiBaseUrl??"",virtualKey:o.virtualKey,model:o.model??""}),disableHermesProfile:e=>Je(e)};b.setRelayCredentialHandler({listHermesProfiles:()=>ze(),enable:async(e,o)=>{const{target:c,result:A}=await Ze(Q,e,o);return{target:c,...A}},disable:async e=>{const{target:o,result:c}=await Qe(Q,e);return{target:o,...c}}});{const e=await oe({label:"admin",port:z,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:o=>b.start(o)});e&&await he(e)}const we=D.join(s.dataDir,"admin-token"),Re=D.join(s.dataDir,"admin-port");je(we,me),re(Re,String(z),"utf-8"),await N(s.daemonStatusFile,{state:"running",pid:process.pid,instance_id:k,updated_at:Date.now()}),process.send&&process.send("ready"),i.info("main","grix-connector ready")}ot().catch(async r=>{const d=`Fatal: ${r instanceof Error?r.stack??r.message:String(r)}`;try{process.stderr.write(`${d}
|
|
42
|
+
`)}catch{}ne(!1),i.error("main",d),q(r,"startup");const S=X(s.daemonLockFile);S?.pid===process.pid&&S.instance_id===k&&await N(s.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:S.instance_id,updated_at:Date.now(),reason:"startup_error"}).catch(()=>{}),await V(),H(s.daemonLockFile).catch(()=>{}),O(),process.exit(1)});const at=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function ye(r){return r instanceof Error&&"code"in r?at.has(r.code):!1}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as P,mkdirSync as
|
|
1
|
+
import{existsSync as P,mkdirSync as R,copyFileSync as W,renameSync as y,rmSync as v,readFileSync as M,writeFileSync as O}from"node:fs";import L from"node:os";import i from"node:path";import{log as l}from"../core/log/index.js";import{readJSONFile as N,writeJSONFileAtomic as H}from"../core/util/json-file.js";import{WINDOWS_WRAPPER_TEMPLATE_MARKER as T,WINDOWS_WRAPPER_TEMPLATE_VERSION as u,buildWindowsWrapperScript as k,resolveWindowsWrapperPath as G}from"./platform-adapter.js";import{findProcessesByCommandLine as V,spawnDetached as C}from"./process-control.js";import{buildServiceID as U,resolveServiceInstallRecordPath as X}from"./service-paths.js";import{LAUNCHER_FILE as Y}from"../core/upgrade/version-store.js";const b=3,q=360*60*1e3,J=600*1e3,B={handoff_attempts:0,last_attempt_at:0,refreshed_template:0};function K(r){for(const e of r.split(/\r?\n/,8)){const t=e.indexOf(T);if(t<0)continue;const n=Number.parseInt(e.slice(t+T.length).trim(),10);if(Number.isFinite(n)&&n>0)return n}return 0}function z(r){const e=p=>({refresh:!1,handoff:!1,reason:p});if(r.platform!=="win32")return e("not windows");if(r.upgradePending)return e("upgrade transaction in flight");if(r.installedTemplate<0)return e("no installed wrapper");const t=r.installedTemplate<u;if(r.supervised&&!r.launcherAdoption)return{refresh:t,handoff:!1,reason:t?"refresh under supervision":"already current"};if(!r.wrapperProcessAlive)return{refresh:t,handoff:!1,reason:"daemon not running under a wrapper"};if(r.state.handoff_attempts>=b)return{refresh:t,handoff:!1,reason:"handoff attempts exhausted"};const n=r.launcherAdoption?J:q;return r.state.last_attempt_at>0&&r.now-r.state.last_attempt_at<n?{refresh:t,handoff:!1,reason:"handoff retry throttled"}:{refresh:t,handoff:!0,reason:r.launcherAdoption?"wrapper must re-read the launcher command line":"unsupervised daemon under a stale wrapper"}}function F(r){return i.join(r,"data","wrapper-migration.json")}function Q(r){const e=N(F(r));return!e||typeof e!="object"?{...B}:{handoff_attempts:Number.isFinite(e.handoff_attempts)?Number(e.handoff_attempts):0,last_attempt_at:Number.isFinite(e.last_attempt_at)?Number(e.last_attempt_at):0,refreshed_template:Number.isFinite(e.refreshed_template)?Number(e.refreshed_template):0}}async function I(r,e){await H(F(r),e)}function Z(r){try{return K(M(r,"utf8"))}catch{return-1}}function x(r){const e=k({nodePath:r.nodePath,cliPath:r.cliPath,configDir:r.configDir,rootDir:r.rootDir});R(i.dirname(r.wrapperPath),{recursive:!0});const t=`${r.wrapperPath}.tmp-${process.pid}`;try{O(t,e,"utf8"),y(t,r.wrapperPath)}finally{v(t,{force:!0})}const n=i.join(r.packageDir,"scripts","upgrade-guardian.mjs");if(!P(n))return;const p=i.join(r.rootDir,"bin");R(p,{recursive:!0});const c=i.join(p,"upgrade-guardian.mjs"),o=i.join(p,"upgrade-guardian.fallback.mjs"),f=`${c}.tmp-${process.pid}`;try{W(n,f),y(f,c),W(n,o)}finally{v(f,{force:!0})}}async function fr(r){const e=r.platform??process.platform,t=r.env??process.env,n=r.homeDir??L.homedir(),p=r.now??Date.now(),c=a=>({plan:{refresh:!1,handoff:!1,reason:a},refreshed:!1,handoffStarted:!1});if(e!=="win32")return c("not windows");const o=N(X(r.rootDir));if(!o?.node_path||!o.cli_path)return c("no service install record");const f=o.service_id||U(r.rootDir,"win32"),m=G(f,n),_=Z(m),S=P(i.join(r.rootDir,"data","upgrade-pending.json")),D=t.GRIX_WRAPPER_SUPERVISED==="1",A=i.basename(o.cli_path)===Y&&t.GRIX_LAUNCHER_MANAGED!=="1",h=Q(r.rootDir);let w=!1;if((!D||A)&&!S&&_>=0){const a=r.findWrapperProcesses??V;try{w=(await a(`${f}-wrapper.vbs`)).filter($=>$!==process.pid).length>0}catch{w=!1}}const s=z({platform:e,supervised:D,launcherAdoption:A,installedTemplate:_,wrapperProcessAlive:w,upgradePending:S,state:h,now:p});let d=!1;if(s.refresh)try{x({wrapperPath:m,rootDir:r.rootDir,nodePath:o.node_path,cliPath:o.cli_path,configDir:o.config_dir||void 0,packageDir:i.dirname(i.dirname(o.cli_path))}),d=!0,l.info("wrapper-migration",`Refreshed Windows wrapper ${_} -> ${u}: ${m}`)}catch(a){return l.warn("wrapper-migration",`Failed to refresh Windows wrapper: ${a instanceof Error?a.message:a}`),{plan:s,refreshed:!1,handoffStarted:!1}}if(!s.handoff||!r.requestGracefulShutdown)return s.reason&&!s.handoff&&l.info("wrapper-migration",`No wrapper handoff: ${s.reason}`),d&&await I(r.rootDir,{...h,refreshed_template:u}).catch(()=>{}),{plan:s,refreshed:d,handoffStarted:!1};const g=i.join(r.rootDir,"bin","upgrade-guardian.mjs");if(!P(g))return l.warn("wrapper-migration",`Wrapper handoff skipped: guardian missing at ${g}`),{plan:s,refreshed:d,handoffStarted:!1};await I(r.rootDir,{handoff_attempts:h.handoff_attempts+1,last_attempt_at:p,refreshed_template:d?u:h.refreshed_template}).catch(()=>{});const j=r.spawnHelper??((a,E)=>{C(a,E)});try{j(o.node_path,[g,"adopt-wrapper","--root",r.rootDir,"--wrapper",m,"--service-id",f,"--daemon-pid",String(process.pid)])}catch(a){return l.warn("wrapper-migration",`Failed to spawn wrapper adopter: ${a instanceof Error?a.message:a}`),{plan:s,refreshed:d,handoffStarted:!1}}return l.info("wrapper-migration",`Handing off to the refreshed Windows wrapper (attempt ${h.handoff_attempts+1}/${b}); shutting down gracefully`),await r.requestGracefulShutdown(),{plan:s,refreshed:d,handoffStarted:!0}}export{J as LAUNCHER_ADOPTION_RETRY_INTERVAL_MS,b as WRAPPER_HANDOFF_MAX_ATTEMPTS,q as WRAPPER_HANDOFF_RETRY_INTERVAL_MS,fr as migrateWindowsWrapper,K as parseWrapperTemplateVersion,z as planWrapperMigration,Z as readInstalledWrapperTemplate,Q as readWrapperMigrationState,x as refreshWrapperAssets,F as resolveWrapperMigrationStatePath};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.10-beta.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",
|
|
Binary file
|