grix-connector 4.9.2-beta.1 → 4.9.2

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.
Files changed (48) hide show
  1. package/dist/adapter/claude/claude-adapter.js +21 -21
  2. package/dist/adapter/claude/claude-bridge-server.js +1 -0
  3. package/dist/adapter/claude/claude-tools.js +1 -0
  4. package/dist/adapter/claude/claude-worker-client.js +1 -0
  5. package/dist/adapter/claude/mcp-http-launcher.js +2 -0
  6. package/dist/adapter/claude/result-timeout.js +1 -0
  7. package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
  8. package/dist/adapter/deepseek/index.js +1 -0
  9. package/dist/adapter/qwen/index.js +1 -0
  10. package/dist/adapter/qwen/qwen-adapter.js +4 -0
  11. package/dist/aibot/client.js +1 -0
  12. package/dist/aibot/index.js +1 -0
  13. package/dist/aibot/types.js +0 -0
  14. package/dist/assets/dsh-bridge/grix-dsh-bridge-4.9.2.tgz +0 -0
  15. package/dist/assets/dsh-bridge/manifest.json +6 -6
  16. package/dist/bridge/bridge.js +2 -8
  17. package/dist/bridge/send-controller.js +1 -1
  18. package/dist/core/access/allowlist-gate.js +1 -0
  19. package/dist/core/access/allowlist-store.js +1 -0
  20. package/dist/core/access/index.js +1 -0
  21. package/dist/core/file-ops/handler.js +1 -0
  22. package/dist/core/file-ops/list-files.js +1 -0
  23. package/dist/core/file-ops/types.js +0 -0
  24. package/dist/grix.js +8 -8
  25. package/dist/launcher.mjs +1 -103
  26. package/dist/log.js +3 -0
  27. package/dist/main.js +31 -0
  28. package/dist/mcp/stream-http/config.js +1 -0
  29. package/dist/mcp/stream-http/connection-binding.js +1 -0
  30. package/dist/mcp/stream-http/event-tool-executor.js +1 -0
  31. package/dist/mcp/stream-http/gateway.js +1 -0
  32. package/dist/mcp/stream-http/index.js +1 -0
  33. package/dist/mcp/stream-http/security.js +1 -0
  34. package/dist/mcp/stream-http/session-manager.js +1 -0
  35. package/dist/mcp/stream-http/tool-executor.js +1 -0
  36. package/dist/mcp/stream-http/tool-registry.js +1 -0
  37. package/dist/mcp/stream-http/tool-schemas.js +1 -0
  38. package/dist/runtime/daemon-lock.js +2 -2
  39. package/dist/service/platform-adapter.js +13 -13
  40. package/dist/service/process-control.js +1 -1
  41. package/dist/session/index.js +1 -0
  42. package/dist/session/manager.js +1 -0
  43. package/dist/transport/index.js +1 -0
  44. package/dist/transport/json-rpc.js +3 -0
  45. package/openclaw-plugin/index.js +1 -1
  46. package/package.json +1 -1
  47. package/scripts/launcher.mjs +1 -103
  48. package/dist/assets/dsh-bridge/grix-dsh-bridge-4.9.2-beta.1.tgz +0 -0
package/dist/grix.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import b from"node:path";import{writeFileSync as ae}from"node:fs";import{Manager as Ce}from"./manager.js";import{ensureGrixDirs as Te,initLogger as $e,log as i,installProcessLogRotation as Le,setConsoleOutput as ie}from"./core/log/index.js";import{HealthServer as Ue,bindPortOrFail as se}from"./core/runtime/index.js";import{writePidFile as He,removePidFile as T,readDaemonPid as ce}from"./core/runtime/index.js";import{killOrphanedClaudeProcesses as Oe}from"./core/runtime/index.js";import{resolveRuntimePaths as V}from"./core/config/index.js";import{supportsNonMitmRelayConfig as Fe}from"./core/config/provider-env.js";import{ServiceManager as le}from"./service/service-manager.js";import{RESTART_EXIT_CODE as Me,UNHEALTHY_EXIT_CODE as Ne,markManagedStop as de,resolveLauncherStore as Y,writeCrashNote as Ge}from"./core/upgrade/version-store.js";import{migrateWindowsWrapper as q}from"./service/windows-wrapper-migration.js";const Be=300*1e3;import{killProcessesByCommandLine as je,isWindowsElevated as We,shouldSweepWindowsWrappers as Ke,getProcessCommandLine as Xe}from"./service/process-control.js";import{buildServiceID as Ve}from"./service/service-paths.js";import{isInsideManagedDaemonTree as Ye}from"./service/platform-adapter.js";import{acquireDaemonLock as qe,createHealthzHolderProbe as Je,DaemonAlreadyServingError as ze,findLiveLockHolder as Qe,isLockHolderSameProcess as Ze,readDaemonLock as J,releaseDaemonLock as F}from"./runtime/daemon-lock.js";import{writeDaemonStatus as G,removeDaemonStatus as et}from"./runtime/service-state.js";import{readDuplicateDaemonRecord as tt,recordDuplicateDaemon as rt}from"./runtime/duplicate-daemon.js";import{RemoteAdminSettingsStore as nt}from"./core/config/remote-admin-settings.js";import{AdminServer as ot,generateToken as at,writeTokenFile as it}from"./core/admin/index.js";import{initSentry as st,closeSentry as B,reportFatal as z}from"./core/observability/sentry.js";import{initProxyManager as ct,getProxyManager as f,relayHostsForClientType as pe,RelayStateStore as lt,SecretStore as dt}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as me,switchAgentRelayMode as Q,reconcileRelayStates as ge}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as ue,RelayCredentialError as fe}from"./core/proxy/relay-credential.js";import{RelayFetchError as he,RelayToggleCoordinator as pt}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as mt,clearHermesProfileRelay as gt,discoverHermesProfiles as ut}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as ft,enableRelayCredential as ht,RelayRouteError as ye}from"./core/proxy/relay-credential-router.js";import{upgradeStatusSnapshot as yt}from"./core/upgrade/npm-upgrader.js";import{resolveClientVersion as wt}from"./core/util/client-version.js";import{parseCliArgs as St}from"./core/util/cli-args.js";const{command:I,flags:D,unknownFlags:Z}=St(process.argv.slice(2));if(Z.length>0&&(console.error(`Unknown option${Z.length>1?"s":""}: ${Z.map(t=>`--${t}`).join(", ")}
3
- Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),D.version&&(console.log(wt()),process.exit(0)),D.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import b from"node:path";import{writeFileSync as ae}from"node:fs";import{Manager as _e}from"./manager.js";import{ensureGrixDirs as ke,initLogger as Ce,log as i,installProcessLogRotation as Te,setConsoleOutput as ie}from"./core/log/index.js";import{HealthServer as $e,bindPortOrFail as se}from"./core/runtime/index.js";import{writePidFile as Le,removePidFile as H,readDaemonPid as Ue}from"./core/runtime/index.js";import{killOrphanedClaudeProcesses as Oe}from"./core/runtime/index.js";import{resolveRuntimePaths as X}from"./core/config/index.js";import{supportsNonMitmRelayConfig as He}from"./core/config/provider-env.js";import{ServiceManager as ce}from"./service/service-manager.js";import{RESTART_EXIT_CODE as Fe,UNHEALTHY_EXIT_CODE as Me,markManagedStop as le,resolveLauncherStore as V,writeCrashNote as Ne}from"./core/upgrade/version-store.js";import{migrateWindowsWrapper as q}from"./service/windows-wrapper-migration.js";const Ge=300*1e3;import{killProcessesByCommandLine as Be,isWindowsElevated as je,shouldSweepWindowsWrappers as We,getProcessCommandLine as Ke}from"./service/process-control.js";import{buildServiceID as Xe}from"./service/service-paths.js";import{isInsideManagedDaemonTree as Ve}from"./service/platform-adapter.js";import{acquireDaemonLock as qe,createHealthzHolderProbe as Ye,DaemonAlreadyServingError as Je,isLockHolderSameProcess as ze,readDaemonLock as Y,releaseDaemonLock as F}from"./runtime/daemon-lock.js";import{writeDaemonStatus as G,removeDaemonStatus as Qe}from"./runtime/service-state.js";import{readDuplicateDaemonRecord as Ze,recordDuplicateDaemon as et}from"./runtime/duplicate-daemon.js";import{RemoteAdminSettingsStore as tt}from"./core/config/remote-admin-settings.js";import{AdminServer as rt,generateToken as nt,writeTokenFile as ot}from"./core/admin/index.js";import{initSentry as at,closeSentry as J,reportFatal as z}from"./core/observability/sentry.js";import{initProxyManager as it,getProxyManager as f,relayHostsForClientType as de,RelayStateStore as st,SecretStore as ct}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as pe,switchAgentRelayMode as Q,reconcileRelayStates as ge}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as me,RelayCredentialError as ue}from"./core/proxy/relay-credential.js";import{RelayFetchError as fe,RelayToggleCoordinator as lt}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as dt,clearHermesProfileRelay as pt,discoverHermesProfiles as gt}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as mt,enableRelayCredential as ut,RelayRouteError as he}from"./core/proxy/relay-credential-router.js";import{upgradeStatusSnapshot as ft}from"./core/upgrade/npm-upgrader.js";import{resolveClientVersion as ht}from"./core/util/client-version.js";import{parseCliArgs as yt}from"./core/util/cli-args.js";const{command:I,flags:A,unknownFlags:Z}=yt(process.argv.slice(2));if(Z.length>0&&(console.error(`Unknown option${Z.length>1?"s":""}: ${Z.map(t=>`--${t}`).join(", ")}
3
+ Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),A.version&&(console.log(ht()),process.exit(0)),A.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
4
4
 
5
5
  Usage: grix-connector <command> [options]
6
6
 
@@ -34,10 +34,10 @@ Examples:
34
34
  grix-connector start # Start as system service
35
35
  grix-connector status # Check service status
36
36
  grix-connector restart # Restart the service
37
- `),process.exit(0)),I==="reload"){const t=ce();t||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(t,0)}catch{console.error(`reload failed: daemon process ${t} is not running (stale pid file)`),process.exit(1)}{const s=J(V().daemonLockFile);s&&s.pid===t&&!Ze(s)&&(console.error(`reload failed: pid ${t} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(t,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:t},null,2)),process.exit(0)}catch(s){console.error(`reload failed: ${s instanceof Error?s.message:s}`),process.exit(1)}}const we="detected connector-managed session; restart delegated to launchd, this process may be terminated by the restart",Se=["start","stop","restart","status"];if(I&&Se.includes(I)){process.platform==="win32"&&["start","stop","restart"].includes(I)&&!We()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
37
+ `),process.exit(0)),I==="reload"){const t=Ue();t||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(t,0)}catch{console.error(`reload failed: daemon process ${t} is not running (stale pid file)`),process.exit(1)}{const l=Y(X().daemonLockFile);l&&l.pid===t&&!ze(l)&&(console.error(`reload failed: pid ${t} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(t,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:t},null,2)),process.exit(0)}catch(l){console.error(`reload failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}const ye="detected connector-managed session; restart delegated to launchd, this process may be terminated by the restart",we=["start","stop","restart","status"];if(I&&we.includes(I)){process.platform==="win32"&&["start","stop","restart"].includes(I)&&!je()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
38
38
  using Startup folder auto-start instead. For full Task Scheduler integration,
39
- right-click the terminal and select "Run as administrator".`);const t=V(),s=D["config-dir"]??(D.profile?b.join(t.configDir,D.profile):void 0),m=b.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),u=new le({cliPath:m,nodePath:process.execPath});try{let l;switch(I){case"start":{const h=await u.healLegacyState({rootDir:t.rootDir});(await u.status({rootDir:t.rootDir})).installed?l=await u.start({rootDir:t.rootDir}):l=await u.install({rootDir:t.rootDir,configDir:s??h.configDir}),h.healed&&(l={...l,legacy_heal:h.reasons})}break;case"stop":l=await u.stop({rootDir:t.rootDir});break;case"restart":if(!(await u.status({rootDir:t.rootDir})).installed)l=await u.install({rootDir:t.rootDir,configDir:s});else{const S=process.platform==="darwin"&&Ye();S&&console.warn(we),l=await u.restart({rootDir:t.rootDir}),S&&(l={...l,mode:"kickstart",note:we})}break;case"status":l=await u.status({rootDir:t.rootDir});{const h=tt(t.dataDir);h&&(l={...l,duplicate_daemon:h})}break}console.log(JSON.stringify(l,null,2)),process.exit(0)}catch(l){console.error(`${I} failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}else I&&(console.error(`Unknown command: ${I}
40
- Valid commands: ${Se.join(", ")}`),process.exit(1));const c=V(),Rt=D["config-dir"]??(D.profile?`${c.configDir}/${D.profile}`:void 0),o=new Ce({requestGracefulShutdown:t=>{k(t)},onTransactionSettled:()=>{q({rootDir:c.rootDir,requestGracefulShutdown:()=>{k("wrapper-migration")}}).then(t=>{t.handoffStarted&&i.info("main","Wrapper handoff started after the upgrade transaction settled")}).catch(t=>{i.warn("main",`Post-upgrade wrapper migration failed: ${t instanceof Error?t.message:t}`)})}}),M=new Ue,Re=at(),x=new ot(Re);let j=!1,$;function W(t,s){const m=Y(),u=process.env.GRIX_LAUNCHER_VERSION;if(!m||!u)return;const l=s instanceof Error?s.stack??s.message:String(s);Ge(m,u,`${t}: ${l}`)}function Ee(t,s){rt(c.dataDir,{holder_pid:t.pid,holder_command_line:t.command_line,yielded_pid:process.pid,yielded_command_line:Xe(process.pid)}),process.stderr.write(`${s}
41
- `),i.warn("main",s);const m=Y();m&&de(m)}function Et(t){const s=`another grix-connector daemon (pid ${t.pid}) is already serving ${c.rootDir} and answering /healthz; this process yields instead of taking over. A second supervisor is starting duplicate daemons \u2014 check for a leftover wrapper, a duplicate logon task, or a desktop app launching its own copy.`;Ee(t,s),process.exit(0)}function Dt(t,s){const m=`${s.label} \u7AEF\u53E3 ${s.port} \u5DF2\u88AB\u5360\u7528\uFF0C\u4E14 daemon \u9501\u4ECD\u7531\u53E6\u4E00\u4E2A\u5B58\u6D3B\u5B9E\u4F8B (pid ${t.pid}) \u6301\u6709\uFF1A\u672C\u8FDB\u7A0B\u6309\u7BA1\u7406\u6027\u9000\u51FA\u8BA9\u4F4D\uFF0C\u4E0D\u8BA1\u4E3A\u5D29\u6E83\u3002 \u8FD9\u53F0\u673A\u5668\u4E0A\u6709\u7B2C\u4E8C\u4E2A supervisor \u5728\u91CD\u590D\u62C9\u8D77 daemon\u2014\u2014\u68C0\u67E5\u6B8B\u7559\u7684 wrapper\u3001\u91CD\u590D\u7684\u767B\u5F55\u4EFB\u52A1\uFF0C\u6216\u684C\u9762\u7AEF\u53E6\u8D77\u7684\u526F\u672C\u3002`;Ee(t,m),ce()===process.pid&&T(),process.exit(0)}async function De(t){if(t.kind==="in_use"){const m=Qe(c.daemonLockFile);m&&(await B(),Dt(m,t))}process.stderr.write(t.message+`
42
- `),i.error("main",t.message.replace(/\n/g," \u2014 ")),W("port_bind",t.message);const s=J(c.daemonLockFile);s?.pid===process.pid&&s.instance_id===$&&await G(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:$,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await B(),F(c.daemonLockFile).catch(()=>{}),T(),process.exit(1)}async function k(t){if(j)return;j=!0,i.info("main",`Received ${t}, shutting down...`),M.markShuttingDown();const s=Y();s&&de(s);const m=t==="upgrade-switch"?Me:t==="launcher-unhealthy"?Ne:null,u=m??2,l=t==="uncaughtException"||t==="unhandledRejection",h=setTimeout(()=>{i.error("main","Shutdown timed out, forcing exit"),F(c.daemonLockFile).catch(()=>{}),T(),process.exit(u)},1e4);try{await o.stop(),await f()?.stop().catch(()=>{}),await x.stop(),await M.stop(),await B(),await F(c.daemonLockFile),await et(c.daemonStatusFile).catch(()=>{}),clearTimeout(h),T(),i.info("main","Shutdown complete"),process.exit(m??(l?2:0))}catch(S){i.error("main",`Shutdown error: ${S}`),F(c.daemonLockFile).catch(()=>{}),T(),process.exit(u)}}async function At(){Te(),$e(),Le(c.stdoutLogFile,c.stderrLogFile),ie(!1);try{$=(await qe(c.daemonLockFile,c.rootDir,2,{isHolderServing:Je(c.dataDir)})).instance_id}catch(e){e instanceof ze&&Et(e.holder),console.error(e instanceof Error?e.message:e),process.exit(1)}if(Ke()){const e=Ve(c.rootDir,"win32");await je(e,{platform:"win32"})}await st(),He(),i.info("main",`grix-connector starting (PID ${process.pid})`);try{Oe({info:e=>i.info("main",e),warn:e=>i.warn("main",e)})}catch(e){i.warn("main",`orphan Claude cleanup failed: ${e instanceof Error?e.message:String(e)}`)}if(await G(c.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:$,updated_at:Date.now()}),process.env.GRIX_LAUNCHER_MANAGED!=="1"&&await new le({cliPath:b.resolve(process.argv[1]??""),nodePath:process.execPath}).adoptLauncher(c.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 q({rootDir:c.rootDir,requestGracefulShutdown:()=>{k("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(()=>{j||q({rootDir:c.rootDir,requestGracefulShutdown:()=>{k("wrapper-migration")}}).then(n=>{n.handoffStarted&&i.info("main","Wrapper handoff started by the periodic migration retry")}).catch(n=>{i.warn("main",`Periodic wrapper migration failed: ${n instanceof Error?n.message:n}`)})},Be).unref();const s=parseInt(D["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await se({label:"health",port:s,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:n=>M.start(n)});e&&await De(e)}const m=b.join(c.dataDir,"health-port");ae(m,String(s),"utf-8"),process.on("SIGINT",()=>k("SIGINT")),process.on("SIGTERM",()=>k("SIGTERM")),process.on("SIGHUP",()=>{j||(i.info("main","Received SIGHUP, reloading config..."),o.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="",l=0,h;process.on("uncaughtException",e=>{const n=e instanceof Error?e.stack??e.message:String(e);n===u?(l++,(l<=3||l%100===0)&&i.error("main",`Uncaught exception (x${l}): ${n}`)):(l>3&&i.error("main",`Previous exception repeated ${l} times total`),u=n,l=1,i.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),h||(h=setTimeout(()=>{l>3&&i.error("main",`Previous exception repeated ${l} times total`),u="",l=0,h=void 0},1e4).unref())),!Ae(e)&&(z(e,"uncaughtException"),W("uncaughtException",e),k("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!Ae(e)&&(z(e,"unhandledRejection"),W("unhandledRejection",e),k("unhandledRejection"))});const S=ct(c.dataDir);await S.clearRuntimeState();const _=new lt(b.join(c.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),N=new dt(b.join(c.dataDir,"secrets"));o.setRelaySecretResolver(e=>N.get(e));const L={hasDirectConfig:e=>o.hasDirectRelayConfig(e),clearDirectConfig:(e,n)=>o.setAgentProvider(e,void 0,{deferRestart:n?.deferRestart===!0})},K=()=>({pm:f(),agents:o,direct:L,store:_,secrets:N}),U=S.getRelayAgents();if(U.length>0)try{await S.startIfAnyRelayAgent(),i.info("main",`mitm proxy started for relay agents: ${U.join(", ")} (per-agent; others stay direct)`)}catch(e){i.error("main",`mitm proxy FAILED to start (relay agents: ${U.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 H=null;if(f()){const e=async()=>{const r=f();r&&await me(r,o)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};o.setRelayEnvSettledHandler(()=>{const r=f();r&&ge(r,L,_),e().catch(a=>{i.warn("main",`failed to stop idle mitm proxy: ${a}`)})});const n=()=>{const r=f(),a=r.getRelayAgents(),d=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:o.getAgentsWithStaleRelayEnv(),degradedRelayAgents:d,relayState:_.list(),runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}},p=new pt,A={status:()=>n(),setAgentRelay:async(r,a,d)=>{const g=f(),E={info:w=>i.info("main",w),warn:w=>i.warn("main",w)},R=w=>o.getAgentsStatus().find(v=>v.name===w),y=R(r);if(!a){p.cancel(r);const w=await p.runExclusive(r,async()=>{const v=await Q(K(),E,{agentName:r,target:"off"});return{...n(),restarted:v.restarted,busy:v.busy,...v.pending?{pending:v.pending}:{}}});return o.reportRelayStateLocalChange(r),w}if(!y)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});if(!Fe(y.clientType)&&pe(y.clientType).length===0)throw Object.assign(new Error(`client type "${y.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const O=await p.runExclusive(r,async()=>{const w=p.epoch(r),v=R(r);if(!v)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});const re=String(v.agentId??"").trim();if(!re)throw new fe(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const ne=p.waitCancelled(r,w);let C;try{C=await Promise.race([o.fetchRelayCredential(r,{model:d}),ne.promise.then(()=>{throw new he("relay enable cancelled by a later disable","CANCELLED")})])}finally{ne.dispose()}p.assertCurrent(r,w);const{restarted:xe,busy:Pe,pending:oe}=await ue(g,o,R,E,{localName:r,agentId:re,virtualKey:C.apiKey,anthropicBaseUrl:C.anthropicBaseUrl,openaiBaseUrl:C.openaiBaseUrl,model:d,...C.directRelay!==void 0?{directRelay:C.directRelay}:{}},(Ie,ke,_e)=>o.setAgentProvider(Ie,ke,{deferRestart:_e?.deferRestart===!0}),{store:_,secrets:N,direct:L});if(p.epoch(r)!==w)throw await Q(K(),E,{agentName:r,target:"off"}),new he("relay enable cancelled by a later disable","CANCELLED");return{...n(),restarted:xe,busy:Pe,...oe?{pending:oe}:{}}});return o.reportRelayStateLocalChange(r),O},setAgentRelayCredential:async(r,a)=>{const d=f(),g=o.getAgentsStatus(),{restarted:E,busy:R,pending:y}=await ue(d,o,P=>g.find(O=>O.name===P),{info:P=>i.info("main",P),warn:P=>i.warn("main",P)},{localName:r,agentId:a.agentId,virtualKey:a.virtualKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}},(P,O,w)=>o.setAgentProvider(P,O,{deferRestart:w?.deferRestart===!0}),{store:_,secrets:N,direct:L});return o.reportRelayStateLocalChange(r),{...n(),restarted:E,busy:R,...y?{pending:y}:{}}},disableAll:async()=>{const r=f(),a={info:g=>i.info("main",g),warn:g=>i.warn("main",g)},d=new Set([...r.getRelayAgents(),...o.getDirectRelayConfigAgentNames(),...Object.keys(_.list())]);for(const g of d)p.cancel(g);for(const g of d)await p.runExclusive(g,()=>Q(K(),a,{agentName:g,target:"off"})),o.reportRelayStateLocalChange(g);return await me(r,o),n()},setRoute:async(r,a)=>{const d=a,g=f();return g.setRoute({routeKey:r,targetBaseUrl:d.targetBaseUrl,...d.headers?{headers:d.headers}:{},...d.model?{model:d.model}:{},...d.modelMap?{modelMap:d.modelMap}:{},...d.passthrough?{passthrough:!0}:{},...d.relayOnly?{relayOnly:!0}:{},...d.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await g.persistConfig(),n()},deleteRoute:async r=>{const a=f();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=f();return a.setDefaultRouteKey(r),await a.persistConfig(),n()},setInterceptHosts:async r=>{const a=f();return a.setInterceptHosts(r),await a.persistConfig(),n()}};H=A,x.setProxyHandler(A),o.setRelayStateApplier({getLocalState:r=>{const a=f();if(a.getRelayAgents().includes(r)){const g=o.getAgentsStatus().find(y=>y.name===r),E=`grix-gateway-${String(g?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(y=>y.routeKey===E)?.model?.trim()||void 0}}const d=o.getAgentProvider(r);return d?.api_key?.trim()?{enabled:!0,model:d.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(r,a,d)=>{const g=o.getAgentsStatus().find(y=>y.name===r),E=String(g?.agentId??"").trim();if(!E)throw new fe(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const R=await A.setAgentRelayCredential(r,{agentId:E,virtualKey:a.apiKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:d??a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}});return{restarted:R.restarted===!0,busy:R.busy===!0,...R.pending?{pending:R.pending}:{}}},applyDisable:async r=>{const a=await A.setAgentRelay(r,!1);return{restarted:a.restarted===!0,busy:a.busy===!0,...a.pending?{pending:a.pending}:{}}}})}M.setStatusProvider(()=>o.getAgentsStatus()),M.setMetaProvider(()=>{const e=o.getAgentsStatus();return{ws:{connected:e.filter(n=>n.wsConnected===!0).length,total:e.length},upgrade:yt()}});const X=new nt(b.join(c.dataDir,"remote-admin.json"));if(o.setRemoteAdminGate(()=>X.isEnabled()),await o.start(Rt),U.length>0){const e=o.getAgentsStatus();for(const n of U){const p=e.find(r=>r.name===n);if(!p)continue;const A=pe(p.clientType);A.length!==0&&await S.setAgentRelayEnabled(n,!0,{relayHosts:A}).catch(r=>{i.warn("main",`failed to reconcile relay hosts for "${n}": ${r}`)})}}ge(S,L,_);const ee=parseInt(D["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);x.setAgentHandler({list:()=>o.getAgentsStatus(),add:e=>o.addAgent(e),remove:e=>o.removeAgent(e),restart:e=>o.restartAgent(e),reload:()=>o.reload()}),x.setUpgradeHandler({check:()=>o.checkUpgrade(),trigger:()=>o.triggerUpgrade()}),x.setProbeHandler({probeAll:e=>o.probeAll(e),probeOne:(e,n)=>o.probeOne(e,n)}),x.setRemoteAdminHandler({isEnabled:()=>X.isEnabled(),setEnabled:e=>{const n=X.setEnabled(e);return i.info("main",`remote admin ${n?"enabled":"disabled"} via admin API`),n}}),x.setInstallHandler({listInstallable:()=>o.listInstallable(),installAgent:e=>o.installAgent(e),getInstallProgress:e=>o.getInstallProgress(e)});const te={listAgents:()=>o.getAgentsStatus(),enableManaged:(e,n,p)=>{if(!H)throw new ye(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return H.setAgentRelayCredential(e,{agentId:n,virtualKey:p.virtualKey,anthropicBaseUrl:p.anthropicBaseUrl,openaiBaseUrl:p.openaiBaseUrl,model:p.model,...p.directRelay!==void 0?{directRelay:p.directRelay}:{}})},disableManaged:e=>{if(!H)throw new ye(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return H.setAgentRelay(e,!1)},enableHermesProfile:(e,n)=>mt({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>gt(e)};x.setRelayCredentialHandler({listHermesProfiles:()=>ut(),enable:async(e,n)=>{const{target:p,result:A}=await ht(te,e,n);return{target:p,...A}},disable:async e=>{const{target:n,result:p}=await ft(te,e);return{target:n,...p}}});{const e=await se({label:"admin",port:ee,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:n=>x.start(n)});e&&await De(e)}const ve=b.join(c.dataDir,"admin-token"),be=b.join(c.dataDir,"admin-port");it(ve,Re),ae(be,String(ee),"utf-8"),await G(c.daemonStatusFile,{state:"running",pid:process.pid,instance_id:$,updated_at:Date.now()}),process.send&&process.send("ready"),i.info("main","grix-connector ready")}At().catch(async t=>{const s=`Fatal: ${t instanceof Error?t.stack??t.message:String(t)}`;try{process.stderr.write(`${s}
43
- `)}catch{}ie(!1),i.error("main",s),z(t,"startup"),W("startup",t);const m=J(c.daemonLockFile);m?.pid===process.pid&&m.instance_id===$&&await G(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:m.instance_id,updated_at:Date.now(),reason:"startup_error"}).catch(()=>{}),await B(),F(c.daemonLockFile).catch(()=>{}),T(),process.exit(1)});const vt=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function Ae(t){return t instanceof Error&&"code"in t?vt.has(t.code):!1}
39
+ right-click the terminal and select "Run as administrator".`);const t=X(),l=A["config-dir"]??(A.profile?b.join(t.configDir,A.profile):void 0),u=b.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),m=new ce({cliPath:u,nodePath:process.execPath});try{let s;switch(I){case"start":{const h=await m.healLegacyState({rootDir:t.rootDir});(await m.status({rootDir:t.rootDir})).installed?s=await m.start({rootDir:t.rootDir}):s=await m.install({rootDir:t.rootDir,configDir:l??h.configDir}),h.healed&&(s={...s,legacy_heal:h.reasons})}break;case"stop":s=await m.stop({rootDir:t.rootDir});break;case"restart":if(!(await m.status({rootDir:t.rootDir})).installed)s=await m.install({rootDir:t.rootDir,configDir:l});else{const S=process.platform==="darwin"&&Ve();S&&console.warn(ye),s=await m.restart({rootDir:t.rootDir}),S&&(s={...s,mode:"kickstart",note:ye})}break;case"status":s=await m.status({rootDir:t.rootDir});{const h=Ze(t.dataDir);h&&(s={...s,duplicate_daemon:h})}break}console.log(JSON.stringify(s,null,2)),process.exit(0)}catch(s){console.error(`${I} failed: ${s instanceof Error?s.message:s}`),process.exit(1)}}else I&&(console.error(`Unknown command: ${I}
40
+ Valid commands: ${we.join(", ")}`),process.exit(1));const c=X(),wt=A["config-dir"]??(A.profile?`${c.configDir}/${A.profile}`:void 0),o=new _e({requestGracefulShutdown:t=>{_(t)},onTransactionSettled:()=>{q({rootDir:c.rootDir,requestGracefulShutdown:()=>{_("wrapper-migration")}}).then(t=>{t.handoffStarted&&i.info("main","Wrapper handoff started after the upgrade transaction settled")}).catch(t=>{i.warn("main",`Post-upgrade wrapper migration failed: ${t instanceof Error?t.message:t}`)})}}),M=new $e,Se=nt(),x=new rt(Se);let B=!1,T;function j(t,l){const u=V(),m=process.env.GRIX_LAUNCHER_VERSION;if(!u||!m)return;const s=l instanceof Error?l.stack??l.message:String(l);Ne(u,m,`${t}: ${s}`)}function St(t){const l=`another grix-connector daemon (pid ${t.pid}) is already serving ${c.rootDir} and answering /healthz; this process yields instead of taking over. A second supervisor is starting duplicate daemons \u2014 check for a leftover wrapper, a duplicate logon task, or a desktop app launching its own copy.`;et(c.dataDir,{holder_pid:t.pid,holder_command_line:t.command_line,yielded_pid:process.pid,yielded_command_line:Ke(process.pid)}),process.stderr.write(`${l}
41
+ `),i.warn("main",l);const u=V();u&&le(u),process.exit(0)}async function Re(t){process.stderr.write(t.message+`
42
+ `),i.error("main",t.message.replace(/\n/g," \u2014 ")),j("port_bind",t.message);const l=Y(c.daemonLockFile);l?.pid===process.pid&&l.instance_id===T&&await G(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:T,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await J(),F(c.daemonLockFile).catch(()=>{}),H(),process.exit(1)}async function _(t){if(B)return;B=!0,i.info("main",`Received ${t}, shutting down...`),M.markShuttingDown();const l=V();l&&le(l);const u=t==="upgrade-switch"?Fe:t==="launcher-unhealthy"?Me:null,m=u??2,s=t==="uncaughtException"||t==="unhandledRejection",h=setTimeout(()=>{i.error("main","Shutdown timed out, forcing exit"),F(c.daemonLockFile).catch(()=>{}),H(),process.exit(m)},1e4);try{await o.stop(),await f()?.stop().catch(()=>{}),await x.stop(),await M.stop(),await J(),await F(c.daemonLockFile),await Qe(c.daemonStatusFile).catch(()=>{}),clearTimeout(h),H(),i.info("main","Shutdown complete"),process.exit(u??(s?2:0))}catch(S){i.error("main",`Shutdown error: ${S}`),F(c.daemonLockFile).catch(()=>{}),H(),process.exit(m)}}async function Rt(){ke(),Ce(),Te(c.stdoutLogFile,c.stderrLogFile),ie(!1);try{T=(await qe(c.daemonLockFile,c.rootDir,2,{isHolderServing:Ye(c.dataDir)})).instance_id}catch(e){e instanceof Je&&St(e.holder),console.error(e instanceof Error?e.message:e),process.exit(1)}if(We()){const e=Xe(c.rootDir,"win32");await Be(e,{platform:"win32"})}await at(),Le(),i.info("main",`grix-connector starting (PID ${process.pid})`);try{Oe({info:e=>i.info("main",e),warn:e=>i.warn("main",e)})}catch(e){i.warn("main",`orphan Claude cleanup failed: ${e instanceof Error?e.message:String(e)}`)}if(await G(c.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:T,updated_at:Date.now()}),process.env.GRIX_LAUNCHER_MANAGED!=="1"&&await new ce({cliPath:b.resolve(process.argv[1]??""),nodePath:process.execPath}).adoptLauncher(c.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 q({rootDir:c.rootDir,requestGracefulShutdown:()=>{_("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||q({rootDir:c.rootDir,requestGracefulShutdown:()=>{_("wrapper-migration")}}).then(n=>{n.handoffStarted&&i.info("main","Wrapper handoff started by the periodic migration retry")}).catch(n=>{i.warn("main",`Periodic wrapper migration failed: ${n instanceof Error?n.message:n}`)})},Ge).unref();const l=parseInt(A["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await se({label:"health",port:l,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:n=>M.start(n)});e&&await Re(e)}const u=b.join(c.dataDir,"health-port");ae(u,String(l),"utf-8"),process.on("SIGINT",()=>_("SIGINT")),process.on("SIGTERM",()=>_("SIGTERM")),process.on("SIGHUP",()=>{B||(i.info("main","Received SIGHUP, reloading config..."),o.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 m="",s=0,h;process.on("uncaughtException",e=>{const n=e instanceof Error?e.stack??e.message:String(e);n===m?(s++,(s<=3||s%100===0)&&i.error("main",`Uncaught exception (x${s}): ${n}`)):(s>3&&i.error("main",`Previous exception repeated ${s} times total`),m=n,s=1,i.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),h||(h=setTimeout(()=>{s>3&&i.error("main",`Previous exception repeated ${s} times total`),m="",s=0,h=void 0},1e4).unref())),!Ee(e)&&(z(e,"uncaughtException"),j("uncaughtException",e),_("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!Ee(e)&&(z(e,"unhandledRejection"),j("unhandledRejection",e),_("unhandledRejection"))});const S=it(c.dataDir);await S.clearRuntimeState();const k=new st(b.join(c.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),N=new ct(b.join(c.dataDir,"secrets"));o.setRelaySecretResolver(e=>N.get(e));const $={hasDirectConfig:e=>o.hasDirectRelayConfig(e),clearDirectConfig:(e,n)=>o.setAgentProvider(e,void 0,{deferRestart:n?.deferRestart===!0})},W=()=>({pm:f(),agents:o,direct:$,store:k,secrets:N}),L=S.getRelayAgents();if(L.length>0)try{await S.startIfAnyRelayAgent(),i.info("main",`mitm proxy started for relay agents: ${L.join(", ")} (per-agent; others stay direct)`)}catch(e){i.error("main",`mitm proxy FAILED to start (relay agents: ${L.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 U=null;if(f()){const e=async()=>{const r=f();r&&await pe(r,o)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};o.setRelayEnvSettledHandler(()=>{const r=f();r&&ge(r,$,k),e().catch(a=>{i.warn("main",`failed to stop idle mitm proxy: ${a}`)})});const n=()=>{const r=f(),a=r.getRelayAgents(),d=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:o.getAgentsWithStaleRelayEnv(),degradedRelayAgents:d,relayState:k.list(),runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}},p=new lt,D={status:()=>n(),setAgentRelay:async(r,a,d)=>{const g=f(),E={info:w=>i.info("main",w),warn:w=>i.warn("main",w)},R=w=>o.getAgentsStatus().find(v=>v.name===w),y=R(r);if(!a){p.cancel(r);const w=await p.runExclusive(r,async()=>{const v=await Q(W(),E,{agentName:r,target:"off"});return{...n(),restarted:v.restarted,busy:v.busy,...v.pending?{pending:v.pending}:{}}});return o.reportRelayStateLocalChange(r),w}if(!y)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});if(!He(y.clientType)&&de(y.clientType).length===0)throw Object.assign(new Error(`client type "${y.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const O=await p.runExclusive(r,async()=>{const w=p.epoch(r),v=R(r);if(!v)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});const re=String(v.agentId??"").trim();if(!re)throw new ue(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const ne=p.waitCancelled(r,w);let C;try{C=await Promise.race([o.fetchRelayCredential(r,{model:d}),ne.promise.then(()=>{throw new fe("relay enable cancelled by a later disable","CANCELLED")})])}finally{ne.dispose()}p.assertCurrent(r,w);const{restarted:ve,busy:be,pending:oe}=await me(g,o,R,E,{localName:r,agentId:re,virtualKey:C.apiKey,anthropicBaseUrl:C.anthropicBaseUrl,openaiBaseUrl:C.openaiBaseUrl,model:d,...C.directRelay!==void 0?{directRelay:C.directRelay}:{}},(xe,Pe,Ie)=>o.setAgentProvider(xe,Pe,{deferRestart:Ie?.deferRestart===!0}),{store:k,secrets:N,direct:$});if(p.epoch(r)!==w)throw await Q(W(),E,{agentName:r,target:"off"}),new fe("relay enable cancelled by a later disable","CANCELLED");return{...n(),restarted:ve,busy:be,...oe?{pending:oe}:{}}});return o.reportRelayStateLocalChange(r),O},setAgentRelayCredential:async(r,a)=>{const d=f(),g=o.getAgentsStatus(),{restarted:E,busy:R,pending:y}=await me(d,o,P=>g.find(O=>O.name===P),{info:P=>i.info("main",P),warn:P=>i.warn("main",P)},{localName:r,agentId:a.agentId,virtualKey:a.virtualKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}},(P,O,w)=>o.setAgentProvider(P,O,{deferRestart:w?.deferRestart===!0}),{store:k,secrets:N,direct:$});return o.reportRelayStateLocalChange(r),{...n(),restarted:E,busy:R,...y?{pending:y}:{}}},disableAll:async()=>{const r=f(),a={info:g=>i.info("main",g),warn:g=>i.warn("main",g)},d=new Set([...r.getRelayAgents(),...o.getDirectRelayConfigAgentNames(),...Object.keys(k.list())]);for(const g of d)p.cancel(g);for(const g of d)await p.runExclusive(g,()=>Q(W(),a,{agentName:g,target:"off"})),o.reportRelayStateLocalChange(g);return await pe(r,o),n()},setRoute:async(r,a)=>{const d=a,g=f();return g.setRoute({routeKey:r,targetBaseUrl:d.targetBaseUrl,...d.headers?{headers:d.headers}:{},...d.model?{model:d.model}:{},...d.modelMap?{modelMap:d.modelMap}:{},...d.passthrough?{passthrough:!0}:{},...d.relayOnly?{relayOnly:!0}:{},...d.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await g.persistConfig(),n()},deleteRoute:async r=>{const a=f();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=f();return a.setDefaultRouteKey(r),await a.persistConfig(),n()},setInterceptHosts:async r=>{const a=f();return a.setInterceptHosts(r),await a.persistConfig(),n()}};U=D,x.setProxyHandler(D),o.setRelayStateApplier({getLocalState:r=>{const a=f();if(a.getRelayAgents().includes(r)){const g=o.getAgentsStatus().find(y=>y.name===r),E=`grix-gateway-${String(g?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(y=>y.routeKey===E)?.model?.trim()||void 0}}const d=o.getAgentProvider(r);return d?.api_key?.trim()?{enabled:!0,model:d.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(r,a,d)=>{const g=o.getAgentsStatus().find(y=>y.name===r),E=String(g?.agentId??"").trim();if(!E)throw new ue(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const R=await D.setAgentRelayCredential(r,{agentId:E,virtualKey:a.apiKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:d??a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}});return{restarted:R.restarted===!0,busy:R.busy===!0,...R.pending?{pending:R.pending}:{}}},applyDisable:async r=>{const a=await D.setAgentRelay(r,!1);return{restarted:a.restarted===!0,busy:a.busy===!0,...a.pending?{pending:a.pending}:{}}}})}M.setStatusProvider(()=>o.getAgentsStatus()),M.setMetaProvider(()=>{const e=o.getAgentsStatus();return{ws:{connected:e.filter(n=>n.wsConnected===!0).length,total:e.length},upgrade:ft()}});const K=new tt(b.join(c.dataDir,"remote-admin.json"));if(o.setRemoteAdminGate(()=>K.isEnabled()),await o.start(wt),L.length>0){const e=o.getAgentsStatus();for(const n of L){const p=e.find(r=>r.name===n);if(!p)continue;const D=de(p.clientType);D.length!==0&&await S.setAgentRelayEnabled(n,!0,{relayHosts:D}).catch(r=>{i.warn("main",`failed to reconcile relay hosts for "${n}": ${r}`)})}}ge(S,$,k);const ee=parseInt(A["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);x.setAgentHandler({list:()=>o.getAgentsStatus(),add:e=>o.addAgent(e),remove:e=>o.removeAgent(e),restart:e=>o.restartAgent(e),reload:()=>o.reload()}),x.setUpgradeHandler({check:()=>o.checkUpgrade(),trigger:()=>o.triggerUpgrade()}),x.setProbeHandler({probeAll:e=>o.probeAll(e),probeOne:(e,n)=>o.probeOne(e,n)}),x.setRemoteAdminHandler({isEnabled:()=>K.isEnabled(),setEnabled:e=>{const n=K.setEnabled(e);return i.info("main",`remote admin ${n?"enabled":"disabled"} via admin API`),n}}),x.setInstallHandler({listInstallable:()=>o.listInstallable(),installAgent:e=>o.installAgent(e),getInstallProgress:e=>o.getInstallProgress(e)});const te={listAgents:()=>o.getAgentsStatus(),enableManaged:(e,n,p)=>{if(!U)throw new he(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return U.setAgentRelayCredential(e,{agentId:n,virtualKey:p.virtualKey,anthropicBaseUrl:p.anthropicBaseUrl,openaiBaseUrl:p.openaiBaseUrl,model:p.model,...p.directRelay!==void 0?{directRelay:p.directRelay}:{}})},disableManaged:e=>{if(!U)throw new he(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return U.setAgentRelay(e,!1)},enableHermesProfile:(e,n)=>dt({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>pt(e)};x.setRelayCredentialHandler({listHermesProfiles:()=>gt(),enable:async(e,n)=>{const{target:p,result:D}=await ut(te,e,n);return{target:p,...D}},disable:async e=>{const{target:n,result:p}=await mt(te,e);return{target:n,...p}}});{const e=await se({label:"admin",port:ee,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:n=>x.start(n)});e&&await Re(e)}const Ae=b.join(c.dataDir,"admin-token"),De=b.join(c.dataDir,"admin-port");ot(Ae,Se),ae(De,String(ee),"utf-8"),await G(c.daemonStatusFile,{state:"running",pid:process.pid,instance_id:T,updated_at:Date.now()}),process.send&&process.send("ready"),i.info("main","grix-connector ready")}Rt().catch(async t=>{const l=`Fatal: ${t instanceof Error?t.stack??t.message:String(t)}`;try{process.stderr.write(`${l}
43
+ `)}catch{}ie(!1),i.error("main",l),z(t,"startup"),j("startup",t);const u=Y(c.daemonLockFile);u?.pid===process.pid&&u.instance_id===T&&await G(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:u.instance_id,updated_at:Date.now(),reason:"startup_error"}).catch(()=>{}),await J(),F(c.daemonLockFile).catch(()=>{}),H(),process.exit(1)});const Et=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function Ee(t){return t instanceof Error&&"code"in t?Et.has(t.code):!1}
package/dist/launcher.mjs CHANGED
@@ -15,23 +15,15 @@
15
15
  // 崩溃时间戳落在 `<store>/crashes.json`,launcher 自身被重拉也不会把计数归零。
16
16
  // 收到 SIGTERM/SIGINT/SIGHUP 时转发给子进程并随其退出(服务停止语义)。
17
17
  //
18
- // 同一个 store 只允许一个 launcher:启动时抢 store 级单例锁,抢不到就退出,
19
- // 避免两个 launcher 各自拉起一个 daemon、把对方的 daemon 记成崩溃、再用共享的
20
- // crashes.json 叠加计数,几秒内把健康版本"三振出局"回滚掉。
21
- //
22
18
  // 本文件由 daemon 在启动时按 LAUNCHER_TEMPLATE_VERSION 比对并原子重写,改动行为时必须 +1。
23
- export const LAUNCHER_TEMPLATE_VERSION = 3;
19
+ export const LAUNCHER_TEMPLATE_VERSION = 2;
24
20
 
25
21
  import { spawn } from 'node:child_process';
26
- import { createHash } from 'node:crypto';
27
22
  import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, appendFileSync } from 'node:fs';
28
- import { connect, createServer } from 'node:net';
29
23
  import { dirname, join } from 'node:path';
30
24
  import { fileURLToPath } from 'node:url';
31
25
 
32
26
  const RESTART_EXIT_CODE = 75;
33
- const LOCK_RETRY_ATTEMPTS = 20;
34
- const LOCK_RETRY_INTERVAL_MS = 250;
35
27
  const RAPID_CRASH_WINDOW_MS = 5 * 60 * 1000;
36
28
  const RAPID_CRASH_LIMIT = 3;
37
29
  const MAX_BACKOFF_MS = 30_000;
@@ -43,79 +35,6 @@ function resolveStoreDir() {
43
35
  return dirname(real);
44
36
  }
45
37
 
46
- /**
47
- * store 级单例锁的端点。Windows 用命名管道(进程消失即随之消失,不留残留);
48
- * POSIX 用 store 里的 unix socket 文件——端口派生会和机器上无关服务撞车,
49
- * 一旦撞上 launcher 就永远起不来,socket 路径没有这个风险。
50
- */
51
- function launcherLockEndpoint(store, platform = process.platform) {
52
- if (platform === 'win32') {
53
- const digest = createHash('sha256').update(store).digest('hex');
54
- return `\\\\.\\pipe\\grix-launcher-${digest.slice(0, 24)}`;
55
- }
56
- return join(store, 'launcher.lock.sock');
57
- }
58
-
59
- function listenOnce(server, endpoint) {
60
- return new Promise((resolve, reject) => {
61
- const onError = (error) => {
62
- if (error?.code === 'EADDRINUSE' || error?.code === 'EACCES') resolve(error.code);
63
- else reject(error);
64
- };
65
- server.once('error', onError);
66
- server.once('listening', () => {
67
- server.removeListener('error', onError);
68
- server.on('error', () => { /* 锁只需活到进程结束,后续错误无需处理 */ });
69
- // 锁只用监听端点本身表态,探活连上来就断掉,别攒着不放。
70
- server.on('connection', (socket) => socket.destroy());
71
- resolve(null);
72
- });
73
- server.listen(endpoint);
74
- });
75
- }
76
-
77
- /** unix socket 文件在 launcher 被强杀后会留下,连得上才说明真有 launcher 在跑。 */
78
- function isEndpointAlive(endpoint) {
79
- return new Promise((resolve) => {
80
- let settled = false;
81
- const socket = connect(endpoint);
82
- const done = (alive) => {
83
- if (settled) return;
84
- settled = true;
85
- try { socket.destroy(); } catch { /* already gone */ }
86
- resolve(alive);
87
- };
88
- socket.once('connect', () => done(true));
89
- socket.once('error', () => done(false));
90
- setTimeout(() => done(false), 1000).unref?.();
91
- });
92
- }
93
-
94
- /**
95
- * 抢 store 级单例锁。拿到返回持锁的 server(进程退出时由系统释放),
96
- * 已被别的 launcher 持有返回 null。
97
- *
98
- * 锁机制本身出错(既不是占用也不是权限)时向上抛:调用方按放行处理——
99
- * launcher 起不来比偶尔多一个 launcher 严重得多。
100
- */
101
- export async function acquireLauncherStoreLock(store, deps = {}) {
102
- const endpoint = deps.lockEndpoint ?? launcherLockEndpoint(store, deps.platform);
103
- const create = deps.createServer ?? createServer;
104
- const isSocketPath = typeof endpoint === 'string' && !endpoint.startsWith('\\\\');
105
- let server = create();
106
- let blocked = await listenOnce(server, endpoint);
107
- if (blocked && isSocketPath) {
108
- if (await isEndpointAlive(endpoint)) return null;
109
- // 空壳 socket:上一轮 launcher 被强杀留下的,删掉再抢一次。
110
- try { rmSync(endpoint, { force: true }); } catch { return null; }
111
- server = create();
112
- blocked = await listenOnce(server, endpoint);
113
- }
114
- if (blocked) return null;
115
- server.unref?.();
116
- return server;
117
- }
118
-
119
38
  function readPointer(store, name) {
120
39
  try {
121
40
  const value = readFileSync(join(store, name), 'utf8').trim();
@@ -264,27 +183,6 @@ async function main() {
264
183
  const store = resolveStoreDir();
265
184
  const args = process.argv.slice(2);
266
185
  mkdirSync(join(store, 'health'), { recursive: true });
267
-
268
- // 升级交接时新旧 launcher 会短暂重叠,退几拍再判定,别把交接当成重复实例。
269
- let storeLock = null;
270
- let lockError = null;
271
- for (let attempt = 0; attempt < LOCK_RETRY_ATTEMPTS && !storeLock; attempt++) {
272
- try {
273
- storeLock = await acquireLauncherStoreLock(store);
274
- } catch (error) {
275
- lockError = error;
276
- break;
277
- }
278
- if (!storeLock) await sleep(LOCK_RETRY_INTERVAL_MS);
279
- }
280
- if (lockError) {
281
- log(store, `store lock unavailable (${lockError instanceof Error ? lockError.message : lockError}); continuing without it`);
282
- } else if (!storeLock) {
283
- // 退出码 0:supervisor 见 0 不会当成崩溃,也不会去动 crashes.json。
284
- log(store, 'another launcher already holds this store; exiting without starting a daemon');
285
- process.exit(0);
286
- }
287
-
288
186
  let backoffMs = 1000;
289
187
  log(store, `started node=${process.version} store=${store} template=${LAUNCHER_TEMPLATE_VERSION}`);
290
188
 
package/dist/log.js ADDED
@@ -0,0 +1,3 @@
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
+ `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
package/dist/main.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import x from"node:path";import{Manager as E}from"./manager.js";import{ensureGrixDirs as k,initLogger as O,log as a}from"./core/log/index.js";import{HealthServer as I}from"./core/runtime/index.js";import{writePidFile as R,removePidFile as l}from"./core/runtime/index.js";import{resolveRuntimePaths as g}from"./core/config/index.js";import{ServiceManager as T}from"./service/service-manager.js";import{acquireDaemonLock as $,releaseDaemonLock as p}from"./runtime/daemon-lock.js";import{writeDaemonStatus as f,removeDaemonStatus as F}from"./runtime/service-state.js";const n=process.argv.slice(2),m=[],s={};for(let e=0;e<n.length;e++)n[e].startsWith("--")&&n[e+1]&&!n[e+1].startsWith("--")?(s[n[e].slice(2)]=n[e+1],e++):n[e].startsWith("--")?s[n[e].slice(2)]="true":m.push(n[e]);if(s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
+
4
+ Usage: grix-connector [options]
5
+ grix-connector service <action> [options]
6
+
7
+ Actions (service):
8
+ install Install and start as OS service
9
+ start Start the OS service
10
+ stop Stop the OS service
11
+ restart Restart the OS service
12
+ uninstall Uninstall the OS service
13
+ status Show service and daemon status
14
+
15
+ Options:
16
+ --config-dir <path> Config directory (default: ~/.grix/config)
17
+ --profile <name> Profile name for config subdirectory
18
+ --health-port <port> Health check port (default: 19579)
19
+ --help Show this help message
20
+
21
+ Platform services:
22
+ macOS: launchd (LaunchAgent)
23
+ Linux: systemd --user
24
+ Windows: Task Scheduler
25
+
26
+ Examples:
27
+ grix-connector # Run in foreground
28
+ grix-connector service install # Install as OS service
29
+ grix-connector service status # Check service status
30
+ grix-connector service uninstall # Remove OS service
31
+ `),process.exit(0)),m[0]==="service"){const e=m[1],t=["install","start","stop","restart","uninstall","status"];(!e||!t.includes(e))&&(console.error(`Usage: grix-connector service <${t.join("|")}>`),process.exit(1));const o=g(),w=s["config-dir"]??(s.profile?`${o.configDir}/${s.profile}`:void 0),D=x.resolve(process.argv[1]||`${o.rootDir}/dist/main.js`),c=new T({cliPath:D,nodePath:process.execPath});try{let r;switch(e){case"install":r=await c.install({rootDir:o.rootDir,configDir:w});break;case"start":r=await c.start({rootDir:o.rootDir});break;case"stop":r=await c.stop({rootDir:o.rootDir});break;case"restart":r=await c.restart({rootDir:o.rootDir});break;case"uninstall":r=await c.uninstall({rootDir:o.rootDir});break;case"status":r=await c.status({rootDir:o.rootDir});break}console.log(JSON.stringify(r,null,2)),process.exit(0)}catch(r){console.error(`service ${e} failed: ${r instanceof Error?r.message:r}`),process.exit(1)}}const i=g(),N=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),h=new E,d=new I;let S=!1;async function u(e){if(S)return;S=!0,a.info("main",`Received ${e}, shutting down...`),d.markShuttingDown();const t=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)},1e4);try{await h.stop(),await d.stop(),await p(i.daemonLockFile),await F(i.daemonStatusFile).catch(()=>{}),clearTimeout(t),l(),a.info("main","Shutdown complete"),process.exit(0)}catch(o){a.error("main",`Shutdown error: ${o}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)}}async function P(){k(),O();try{await $(i.daemonLockFile,i.rootDir)}catch(t){console.error(t instanceof Error?t.message:t),process.exit(1)}R(),a.info("main",`grix-connector starting (PID ${process.pid})`),await f(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const e=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await d.start(e),process.on("SIGINT",()=>u("SIGINT")),process.on("SIGTERM",()=>u("SIGTERM")),process.on("uncaughtException",t=>{a.error("main",`Uncaught exception: ${t instanceof Error?t.stack:t}`),!v(t)&&u("uncaughtException")}),process.on("unhandledRejection",t=>{a.error("main",`Unhandled rejection: ${t}`),!v(t)&&u("unhandledRejection")}),d.setStatusProvider(()=>h.getAgentsStatus()),await h.start(N),await f(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}P().catch(e=>{a.error("main",`Fatal: ${e}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(1)});const A=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH"]);function v(e){return e instanceof Error&&"code"in e?A.has(e.code):!1}
@@ -0,0 +1 @@
1
+ import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
@@ -0,0 +1 @@
1
+ const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
@@ -0,0 +1 @@
1
+ import{splitTextForAibotProtocol as v}from"../../core/protocol/protocol-text.js";const d=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);function g(n){return d.has(n)}function p(n,e,t){switch(e){case"grix_reply":return f(n,t);case"grix_complete":return x(n,t);case"grix_event_ack":return C(n,t);case"grix_composing":return S(n,t);case"grix_access_control":return m(n,t);case"grix_status":return y(n);default:return r(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${e}`)}}function f(n,e){const t=String(e.event_id??""),s=String(e.session_id??""),i=String(e.text??""),a=e.quoted_message_id,l=e.is_final===!0;if(!s)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id");if(!i)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: text");const _=v(i),c=`reply_${t||"proactive"}_${Date.now()}`;for(let o=0;o<_.length;o++)n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:_[o],chunk_seq:o+1,is_finish:!1,client_msg_id:c,quoted_message_id:o===0?a:void 0});return n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:"",chunk_seq:_.length+1,is_finish:!0,client_msg_id:c}),l&&t&&n.sendEventResult({event_id:t,status:"responded"}),u({ok:!0,chunks:_.length,client_msg_id:c})}function x(n,e){const t=String(e.event_id??""),s=String(e.status??""),i=e.msg;return t?["responded","canceled","failed"].includes(s)?(n.sendEventResult({event_id:t,status:s,msg:i}),u({ok:!0,event_id:t,status:s})):r("status \u5FC5\u987B\u4E3A responded\u3001canceled \u6216 failed"):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function C(n,e){const t=String(e.event_id??""),s=e.session_id;return t?(n.sendEventAck({event_id:t,session_id:s,received_at:Date.now()}),u({ok:!0,event_id:t})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function S(n,e){const t=String(e.session_id??""),s=e.active===!0,i=e.event_id;return t?(n.sendSessionActivitySet({session_id:t,kind:"composing",active:s,ref_event_id:i,ttl_ms:s?3e4:void 0}),u({ok:!0,session_id:t,active:s})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id")}function m(n,e){const t=String(e.action??""),s={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!s)return r(`\u672A\u77E5 access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{content:[{type:"text",text:"__ASYNC_ACCESS_CONTROL__"}],_async:{verb:s,payload:i}}}function y(n){const e=n.status,t=n.getStatusSnapshot();return u({connected:e==="ready",status:e,connected_at:t.connectedAt})}function u(n){return{content:[{type:"text",text:JSON.stringify(n)}],isError:!1}}function r(n){return{content:[{type:"text",text:n}],isError:!0}}export{d as EVENT_TOOL_NAMES,p as executeEventTool,g as isEventTool};
@@ -0,0 +1 @@
1
+ import{createServer as N}from"node:http";import{Server as O}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as j}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{ListToolsRequestSchema as R,CallToolRequestSchema as E}from"@modelcontextprotocol/sdk/types.js";import{createSecurityPolicy as h}from"./security.js";import{SessionManagerImpl as q}from"./session-manager.js";import{ToolRegistryImpl as A}from"./tool-registry.js";import{ToolExecutorImpl as C}from"./tool-executor.js";function J(n,r){let a=null,p=!1,m,c;const S=new A,v=new C;let s=null;return{async start(){m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]}),c=new q({maxSessions:1,sessionTimeoutMs:n.sessionTimeoutMs,onSessionExpired:e=>{w(e)}}),a=N((e,t)=>{g(e,t)}),await new Promise((e,t)=>{a.on("error",t),a.listen(n.port,n.bind,()=>{a.removeListener("error",t),p=!0;const i=a.address();i&&typeof i=="object"&&(n.port=i.port,m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]})),e()})})},async stop(){if(p=!1,a&&(await new Promise(e=>{a.close(()=>e())}),a=null),s){try{await s.transport.close()}catch{}try{await s.mcpServer.close()}catch{}s=null}c&&(await c.closeAll(),c.dispose())},getStatus(){return{listening:p,url:`http://${n.bind}:${n.port}${n.endpoint}`,activeSessions:s?1:0}},pushEvent(e){s&&y(s.mcpServer,"notifications/message",{event_id:e.event_id,session_id:e.session_id,sender_id:e.sender_id??"",content:e.content,msg_type:e.msg_type??1,msg_id:e.msg_id??"",session_type:e.session_type??1,quoted_message_id:e.quoted_message_id??"",attachments:e.attachments??[],context_messages:e.context_messages??[]},s.transport)},pushStop(e){s&&y(s.mcpServer,"notifications/event_stop",{event_id:e.event_id,stop_id:e.stop_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushRevoke(e){s&&y(s.mcpServer,"notifications/event_revoke",{event_id:e.event_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushLocalAction(e){s&&y(s.mcpServer,"notifications/local_action",e,s.transport)}};async function g(e,t){if(new URL(e.url??"/",`http://${e.headers.host??"localhost"}`).pathname!==n.endpoint){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found"}));return}const i=m.validateRequest(e);if(!i.ok){t.writeHead(i.statusCode??403,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:i.message}));return}const o=e.method?.toUpperCase();if(o==="GET"){const d=f(e);if(!d||!s||s.sessionId!==d){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing or invalid session"}));return}c.touchActivity(d),await s.transport.handleRequest(e,t);return}if(o==="DELETE"){await x(e,t);return}if(o==="POST"){await _(e,t);return}t.writeHead(405,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Method Not Allowed"}))}async function _(e,t){const i=await b(e);let o;try{o=JSON.parse(i)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON body"}));return}const d=I(o),l=e.headers.accept??"";if(!l.includes("application/json")||!l.includes("text/event-stream")){t.writeHead(406,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Acceptable: Accept header must include both application/json and text/event-stream"}));return}if(d)await T(e,t,o);else{const u=f(e);if(!u){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==u){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}c.touchActivity(u),await s.transport.handleRequest(e,t,o)}}async function T(e,t,i){if(s){t.writeHead(503,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Service Unavailable: gateway already has an active session"}));return}const o=c.createSession().sessionId,d=new j({sessionIdGenerator:()=>o,enableJsonResponse:!1,onsessioninitialized:()=>{},onsessionclosed:()=>{w(o)}}),l=new O({name:"grix-mcp-server",version:"1.0.0"},{capabilities:{tools:{}}});H(l,o),await l.connect(d),s={transport:d,mcpServer:l,sessionId:o},await d.handleRequest(e,t,i);try{c.markReady(o)}catch{}}async function x(e,t){const i=f(e);if(!i){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==i){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}await s.transport.handleRequest(e,t)}function H(e,t){const i=S.getTools();e.setRequestHandler(R,async()=>({tools:i.map(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema}))})),e.setRequestHandler(E,async o=>{const{name:d,arguments:l}=o.params,u=c.getSession(t);return!u||u.state!=="ready"?{content:[{type:"text",text:`Session \u72B6\u6001\u4E0D\u53EF\u7528: ${u?.state??"unknown"}`}],isError:!0}:r.status!=="ready"?{content:[{type:"text",text:`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`}],isError:!0}:(c.touchActivity(t),v.execute(r,d,l??{},n.invokeTimeoutMs))})}async function w(e){if(s?.sessionId===e){try{await s.mcpServer.close()}catch{}s=null}try{await c.closeSession(e)}catch{}}}function y(n,r,a,p){return p?p.send({jsonrpc:"2.0",method:r,params:a}):Promise.resolve()}function f(n){const r=n.headers["mcp-session-id"];return Array.isArray(r)?r[0]:r||void 0}function I(n){return n&&typeof n=="object"&&"method"in n?n.method==="initialize":Array.isArray(n)?n.some(r=>r&&typeof r=="object"&&r.method==="initialize"):!1}function b(n){return new Promise((r,a)=>{const p=[];n.on("data",m=>p.push(m)),n.on("end",()=>r(Buffer.concat(p).toString("utf-8"))),n.on("error",a)})}export{J as createMcpGateway};
@@ -0,0 +1 @@
1
+ import{createMcpGateway as a}from"./gateway.js";import{createSecurityPolicy as e}from"./security.js";import{SessionManagerImpl as r}from"./session-manager.js";import{createDefaultGatewayConfig as t}from"./config.js";export{r as SessionManagerImpl,t as createDefaultGatewayConfig,a as createMcpGateway,e as createSecurityPolicy};
@@ -0,0 +1 @@
1
+ function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -0,0 +1 @@
1
+ import{randomUUID as n}from"node:crypto";const i={initializing:0,ready:1,closing:2,closed:3},o=6e4;class a{sessions=new Map;config;scanTimer=null;constructor(s){this.config=s,this.startScanTimer()}createSession(){if(this.getActiveCount()>=this.config.maxSessions)throw new Error(`Session limit exceeded: max ${this.config.maxSessions} active sessions`);const s=Date.now(),t={sessionId:n(),createdAt:s,lastActivityAt:s,state:"initializing"};return this.sessions.set(t.sessionId,t),t}getSession(s){return this.sessions.get(s)}markReady(s){const t=this.sessions.get(s);if(!t)throw new Error(`Session not found: ${s}`);this.transitionState(t,"ready")}async closeSession(s){const t=this.sessions.get(s);if(!t)throw new Error(`Session not found: ${s}`);t.state!=="closed"&&(t.state!=="closing"&&this.transitionState(t,"closing"),this.transitionState(t,"closed"))}touchActivity(s){const t=this.sessions.get(s);t&&(t.lastActivityAt=Date.now())}getActiveCount(){let s=0;for(const t of this.sessions.values())t.state!=="closed"&&s++;return s}async closeAll(){const s=[];for(const t of this.sessions.values())t.state!=="closed"&&s.push(this.closeSession(t.sessionId));await Promise.all(s)}dispose(){this.scanTimer!==null&&(clearInterval(this.scanTimer),this.scanTimer=null)}startScanTimer(){this.scanTimer=setInterval(()=>{this.scanExpiredSessions()},o)}scanExpiredSessions(){const s=Date.now();for(const t of this.sessions.values())t.state==="closed"||t.state==="closing"||s-t.lastActivityAt>this.config.sessionTimeoutMs&&this.expireSession(t.sessionId)}async expireSession(s){await this.closeSession(s),await this.config.onSessionExpired?.(s)}transitionState(s,t){const e=i[s.state];if(i[t]<=e)throw new Error(`Invalid state transition: cannot transition from '${s.state}' to '${t}'`);s.state=t}}export{a as SessionManagerImpl};
@@ -0,0 +1 @@
1
+ import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
@@ -0,0 +1 @@
1
+ import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
@@ -0,0 +1 @@
1
+ const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
@@ -1,2 +1,2 @@
1
- import{openSync as x,closeSync as D,existsSync as y,readFileSync as s,unlinkSync as h,writeSync as E}from"node:fs";import{mkdir as L}from"node:fs/promises";import{randomUUID as _}from"node:crypto";import f from"node:path";import c from"node:process";import{getProcessCommandLine as m,isProcessRunning as l}from"../service/process-control.js";function w(r){try{const e=JSON.parse(r);if(typeof e?.pid=="number"&&typeof e.acquired_at=="number")return e}catch{}return null}function p(r){if(!y(r))return null;try{return w(s(r,"utf8"))}catch{return null}}function M(r){const e=p(r);return e!==null&&l(e.pid)}class k extends Error{holder;constructor(e){super(`another grix-connector daemon (pid ${e.pid}) is already serving this root; yielding`),this.holder=e,this.name="DaemonAlreadyServingError"}}function O(r,e=2e3,n=2){return async t=>{let u="19579";try{const a=f.join(r,"health-port");y(a)&&(u=s(a,"utf8").trim()||u)}catch{}for(let a=0;a<n;a+=1)try{const d=await fetch(`http://127.0.0.1:${u}/healthz`,{signal:AbortSignal.timeout(e)});if(!d.ok)return!1;const i=await d.json();return i.status==="ok"&&i.pid===t.pid}catch{}return!1}}function S(r){const e=new Int32Array(new SharedArrayBuffer(4));Atomics.wait(e,0,0,r)}function g(r){const e=typeof r.command_line=="string"&&r.command_line?r.command_line:null;if(e===null)return!0;const n=m(r.pid);return n===null?!0:n===e}function j(r){const e=typeof r.command_line=="string"&&r.command_line?r.command_line:null;if(e===null)return!1;const n=m(r.pid);return n!==null&&n===e}function z(r,e={}){const n=e.selfPid??c.pid,t=(e.readLock??p)(r);return!t||t.pid===n||!(e.isRunning??l)(t.pid)?null:(e.isSameProcess??g)(t)?t:null}function v(r){try{return c.kill(r,0),!0}catch(e){return e?.code!=="EPERM"}}function I(r){if(r<=0||r===c.pid)return!0;try{c.kill(r,"SIGTERM")}catch(t){return t?.code!=="EPERM"}const e=Date.now()+3e3;for(;Date.now()<e;){if(!l(r))return!0;S(100)}try{c.kill(r,"SIGKILL")}catch{}const n=Date.now()+1e3;for(;Date.now()<n;){if(!l(r))return!0;S(50)}return!l(r)}async function G(r,e,n=2,t={}){await L(f.dirname(r),{recursive:!0});const u={pid:c.pid,acquired_at:Date.now(),root_dir:f.resolve(e),instance_id:_(),command_line:m(c.pid)},a=`${JSON.stringify(u,null,2)}
2
- `;for(let d=0;d<=n;d+=1)try{const i=x(r,"wx");try{E(i,a)}finally{D(i)}return u}catch(i){if(!(i instanceof Error)||i.code!=="EEXIST")throw i;let o=null;try{o=w(s(r,"utf8"))}catch{}if(o&&l(o.pid)){if(o.pid===c.pid)throw new Error(`daemon already running (pid ${o.pid}, acquired at ${new Date(o.acquired_at).toISOString()})`);if(v(o.pid)&&g(o)){if(t.isHolderServing&&await t.isHolderServing(o))throw new k(o);if(!I(o.pid))throw new Error(`\u65E0\u6CD5\u7EC8\u6B62\u8001\u8FDB\u7A0B (pid ${o.pid})\uFF0C\u8BF7\u624B\u52A8\u5904\u7406`)}}try{h(r)}catch{}}throw new Error("failed to acquire daemon lock after retries")}async function J(r){const e=p(r);if(e&&e.pid===c.pid)try{h(r)}catch{}}export{k as DaemonAlreadyServingError,G as acquireDaemonLock,O as createHealthzHolderProbe,z as findLiveLockHolder,M as isDaemonLocked,j as isLockHolderIdentityVerified,g as isLockHolderSameProcess,p as readDaemonLock,J as releaseDaemonLock};
1
+ import{openSync as g,closeSync as D,existsSync as p,readFileSync as s,unlinkSync as y,writeSync as E}from"node:fs";import{mkdir as x}from"node:fs/promises";import{randomUUID as _}from"node:crypto";import f from"node:path";import i from"node:process";import{getProcessCommandLine as m,isProcessRunning as l}from"../service/process-control.js";function h(r){try{const e=JSON.parse(r);if(typeof e?.pid=="number"&&typeof e.acquired_at=="number")return e}catch{}return null}function w(r){if(!p(r))return null;try{return h(s(r,"utf8"))}catch{return null}}function O(r){const e=w(r);return e!==null&&l(e.pid)}class k extends Error{holder;constructor(e){super(`another grix-connector daemon (pid ${e.pid}) is already serving this root; yielding`),this.holder=e,this.name="DaemonAlreadyServingError"}}function j(r,e=2e3,n=2){return async c=>{let u="19579";try{const a=f.join(r,"health-port");p(a)&&(u=s(a,"utf8").trim()||u)}catch{}for(let a=0;a<n;a+=1)try{const d=await fetch(`http://127.0.0.1:${u}/healthz`,{signal:AbortSignal.timeout(e)});if(!d.ok)return!1;const o=await d.json();return o.status==="ok"&&o.pid===c.pid}catch{}return!1}}function S(r){const e=new Int32Array(new SharedArrayBuffer(4));Atomics.wait(e,0,0,r)}function L(r){const e=typeof r.command_line=="string"&&r.command_line?r.command_line:null;if(e===null)return!0;const n=m(r.pid);return n===null?!0:n===e}function z(r){const e=typeof r.command_line=="string"&&r.command_line?r.command_line:null;if(e===null)return!1;const n=m(r.pid);return n!==null&&n===e}function I(r){try{return i.kill(r,0),!0}catch(e){return e?.code!=="EPERM"}}function v(r){if(r<=0||r===i.pid)return!0;try{i.kill(r,"SIGTERM")}catch(c){return c?.code!=="EPERM"}const e=Date.now()+3e3;for(;Date.now()<e;){if(!l(r))return!0;S(100)}try{i.kill(r,"SIGKILL")}catch{}const n=Date.now()+1e3;for(;Date.now()<n;){if(!l(r))return!0;S(50)}return!l(r)}async function G(r,e,n=2,c={}){await x(f.dirname(r),{recursive:!0});const u={pid:i.pid,acquired_at:Date.now(),root_dir:f.resolve(e),instance_id:_(),command_line:m(i.pid)},a=`${JSON.stringify(u,null,2)}
2
+ `;for(let d=0;d<=n;d+=1)try{const o=g(r,"wx");try{E(o,a)}finally{D(o)}return u}catch(o){if(!(o instanceof Error)||o.code!=="EEXIST")throw o;let t=null;try{t=h(s(r,"utf8"))}catch{}if(t&&l(t.pid)){if(t.pid===i.pid)throw new Error(`daemon already running (pid ${t.pid}, acquired at ${new Date(t.acquired_at).toISOString()})`);if(I(t.pid)&&L(t)){if(c.isHolderServing&&await c.isHolderServing(t))throw new k(t);if(!v(t.pid))throw new Error(`\u65E0\u6CD5\u7EC8\u6B62\u8001\u8FDB\u7A0B (pid ${t.pid})\uFF0C\u8BF7\u624B\u52A8\u5904\u7406`)}}try{y(r)}catch{}}throw new Error("failed to acquire daemon lock after retries")}async function J(r){const e=w(r);if(e&&e.pid===i.pid)try{y(r)}catch{}}export{k as DaemonAlreadyServingError,G as acquireDaemonLock,j as createHealthzHolderProbe,O as isDaemonLocked,z as isLockHolderIdentityVerified,L as isLockHolderSameProcess,w as readDaemonLock,J as releaseDaemonLock};