grix-connector 4.11.12 → 4.11.14
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.11.12.tgz → grix-dsh-bridge-4.11.14.tgz} +0 -0
- package/dist/assets/dsh-bridge/manifest.json +4 -4
- package/dist/core/observability/sentry-worker.js +1 -0
- package/dist/core/observability/sentry.js +2 -1
- package/dist/grix.js +8 -8
- package/dist/runtime/duplicate-daemon.js +2 -2
- package/dist/runtime/startup-phases.js +1 -0
- package/dist/service/platform-adapter.js +18 -18
- package/dist/service/service-manager.js +2 -3
- package/package.json +1 -1
|
Binary file
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bridgeVersion": "4.11.
|
|
4
|
-
"tarball": "grix-dsh-bridge-4.11.
|
|
3
|
+
"bridgeVersion": "4.11.14",
|
|
4
|
+
"tarball": "grix-dsh-bridge-4.11.14.tgz",
|
|
5
5
|
"size": 23419,
|
|
6
6
|
"unpackedSize": 94155,
|
|
7
|
-
"shasum": "
|
|
8
|
-
"integrity": "sha512-
|
|
7
|
+
"shasum": "276e924c3a30a3660d3ac7a8a93c5dd1c60fa8eb",
|
|
8
|
+
"integrity": "sha512-a/DBEpVYRzQVtBZJDHqMpMAZf4bnByfaDhYEa5FtW1a2k6H2KACWaMzYwYFu5kq8YxxuHdwg5710HJmWiwsCAg=="
|
|
9
9
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parentPort as c,workerData as l}from"node:worker_threads";import*as r from"@sentry/node";const i=c;if(!i)throw new Error("sentry-worker must run inside a worker thread");const d=e=>i.postMessage(e),s=l;r.init({dsn:s.dsn,release:s.release,environment:s.environment,sendDefaultPii:!1,initialScope:{tags:{component:"grix-connector"}},defaultIntegrations:!1,tracesSampleRate:0});const a=new Map;r.getClient()?.on("afterSendEvent",(e,t)=>{const n=e.event_id===void 0?void 0:a.get(e.event_id);if(n===void 0)return;a.delete(e.event_id);const o=t.statusCode;d({type:"sent",key:n,ok:o!==void 0&&o>=200&&o<300})}),i.on("message",e=>{if(e.type==="capture"){const t=new Error(e.error.message);t.name=e.error.name,e.error.stack&&(t.stack=e.error.stack);const n=r.captureException(t,{level:e.level,tags:e.tags,contexts:e.contexts,fingerprint:e.fingerprint});a.set(n,e.key)}else e.type==="close"&&r.close(e.timeoutMs).finally(()=>d({type:"closed"}))}),d({type:"ready"});
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{log as
|
|
1
|
+
import{Worker as w}from"node:worker_threads";import{log as a}from"../log/logger.js";import{resolveClientVersion as k}from"../util/client-version.js";import{PersistentSentryDeduplicator as h}from"./sentry-event-dedup.js";let i="off",c=null,m="",f=Promise.resolve();const d=[],l=new Map;let R=1,u=null;const g=new h,I="https://e8e202d7625372b1314b3ff4e85a7ff9@o119262.ingest.us.sentry.io/4511410543329280";function _(){if(!process.env.GRIX_SENTRY_DISABLE)return process.env.SENTRY_DSN||process.env.GRIX_SENTRY_DSN||I}function M(){return i!=="off"||(f=v()),f}function C(){return f}function v(){const e=_();if(!e)return Promise.resolve();const r=performance.now();m=`grix-connector@${k()}`;const t={dsn:e,release:m,environment:process.env.SENTRY_ENVIRONMENT||process.env.NODE_ENV||"production"};return new Promise(n=>{let o;try{o=new w(new URL("./sentry-worker.js",import.meta.url),{workerData:t})}catch(s){a.warn("sentry",`Sentry worker \u542F\u52A8\u5931\u8D25\uFF0C\u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528: ${p(s)}`),n();return}c=o,i="starting",o.on("message",s=>{if(s.type==="ready"){if(c!==o)return;i="ready",o.unref(),a.info("sentry",`ready in ${Math.round(performance.now()-r)}ms (worker)`);for(const y of d.splice(0))N(y);n()}else if(s.type==="sent"){const y=l.get(s.key);if(!y)return;l.delete(s.key),g.recordSendResult(y,s.ok)}else s.type==="closed"&&u?.()}),o.on("error",s=>{E(o,`Sentry worker \u51FA\u9519\uFF0C\u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528: ${p(s)}`),n()}),o.on("exit",s=>{E(o,`Sentry worker \u5DF2\u9000\u51FA\uFF08code ${s}\uFF09\uFF0C\u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528`),n()})})}function E(e,r){u?.(),c===e&&(c=null,i="off",d.length=0,l.clear(),a.warn("sentry",r))}function N(e){try{c?.postMessage(e)}catch(r){l.delete(e.key),a.error("sentry",`\u63D0\u4EA4 Sentry \u4E8B\u4EF6\u5931\u8D25: ${p(r)}`)}}function x(e){const r=[];for(const t of(e??"").split(`
|
|
2
|
+
`)){if(r.length>=50)break;if(t.length>1024)continue;const n=/^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/.exec(t);n&&r.push({function:n[1],filename:n[2],lineno:Number(n[3]),colno:Number(n[4])})}return r.reverse()}function S(e){const r={name:String(e.error.name),message:String(e.error.message),stack:typeof e.error.stack=="string"?e.error.stack:void 0},t={release:m,platform:"node",level:e.level,fingerprint:e.fingerprint,exception:{values:[{type:r.name,value:r.message,stacktrace:{frames:x(r.stack)}}]}};if(!L(t))return!1;const n={...e,type:"capture",key:R++,error:r};return l.set(n.key,t),i==="ready"?N(n):d.push(n),!0}function p(e){return e instanceof Error?e.message:String(e)}function A(e){if(typeof e.message=="string"&&e.message)return e.message;const r=e.exception?.values;if(!r?.length)return"";const t=r[0],n=typeof t?.value=="string"?t.value:"",o=typeof t?.type=="string"?t.type:"";return n||o}function W(){return i==="ready"}function L(e,r,t=g){const n=A(e);return n&&T(n)?null:t.shouldSend(e)?e:null}const D=new Set(["INSTALL_FAILED","INSTALL_TIMEOUT","FALLBACK_EXHAUSTED","PREREQ_INSTALL_FAILED","VERIFICATION_FAILED","ENVIRONMENT_UNSUPPORTED","INTERNAL"]);function $(e){if(e.ok)return!1;const r=e.error?.code;return!!r&&D.has(r)}function U(e){if(i==="off"||!$(e))return;const r=e.error?.code??"INTERNAL",t=e.error?.message??"unknown install failure",n=e.environment;try{S({kind:"install",level:"error",error:new Error(`Agent install failed [${e.agentType}/${r}]: ${t}`),tags:{agent_type:e.agentType,error_code:r,phase:e.phase},contexts:{install:{agentType:e.agentType,code:r,phase:e.phase,durationMs:e.durationMs,os:n?.platform,osVersion:n?.osVersion,arch:n?.arch,nodeVersion:n?.nodeVersion,npmVersion:n?.npmVersion,isDocker:n?.isDocker,isCI:n?.isCI,outputTail:(e.output??"").slice(-2e3)}},fingerprint:["agent-install-failure",e.agentType,r]})&&a.info("sentry",`\u5DF2\u63D0\u4EA4\u5B89\u88C5\u5931\u8D25\u7ED9 Sentry worker: ${e.agentType}/${r}`)}catch(o){a.error("sentry",`\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25\u65F6\u51FA\u9519: ${p(o)}`)}}const b=[/No config files found/,/spawn wmic ENOENT/i];function T(e){const r=e instanceof Error?e.message:String(e);return b.some(t=>t.test(r))}function B(e){const r=e instanceof Error?e.message:String(e);return/spawn wmic ENOENT/i.test(r)}function Y(e,r){if(i!=="off")try{if(T(e))return;const t=e instanceof Error?e:new Error(String(e));S({kind:r,level:"fatal",error:t,tags:{crash_type:r}})&&a.info("sentry",`\u5DF2\u63D0\u4EA4\u5D29\u6E83\u7ED9 Sentry worker: ${r}`)}catch(t){a.error("sentry",`\u4E0A\u62A5\u5D29\u6E83\u65F6\u51FA\u9519: ${p(t)}`)}}async function X(e=2e3){await f;const r=c;i!=="ready"||!r||(c=null,i="off",await new Promise(t=>{const n=setTimeout(t,e);u=()=>{clearTimeout(n),t()};try{const o={type:"close",timeoutMs:e};r.postMessage(o)}catch{u()}}),u=null,r.terminate().catch(()=>{}))}export{X as closeSentry,A as extractEventMessage,L as filterSentryEvent,M as initSentry,T as isNonReportable,W as isSentryEnabled,B as isWindowsWmicProbeError,Y as reportFatal,U as reportInstallFailure,$ as shouldReportInstallFailure,C as whenSentryReady};
|
package/dist/grix.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import b from"node:path";import{writeFileSync as
|
|
3
|
-
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),
|
|
2
|
+
import b from"node:path";import{writeFileSync as se}from"node:fs";import{Manager as $e}from"./manager.js";import{ensureGrixDirs as Le,initLogger as Ue,getLoggerStatus as He,log as i,installProcessLogRotation as Oe,setConsoleOutput as ce}from"./core/log/index.js";import{HealthServer as Fe,bindPortOrFail as le}from"./core/runtime/index.js";import{writePidFile as Ne,removePidFile as T,readDaemonPid as de}from"./core/runtime/index.js";import{killOrphanedClaudeProcesses as Ge}from"./core/runtime/index.js";import{resolveRuntimePaths as Y}from"./core/config/index.js";import{supportsNonMitmRelayConfig as Me}from"./core/config/provider-env.js";import{ServiceManager as pe}from"./service/service-manager.js";import{RESTART_EXIT_CODE as Be,UNHEALTHY_EXIT_CODE as je,markManagedStop as ge,resolveLauncherStore as q,writeCrashNote as We}from"./core/upgrade/version-store.js";import{migrateWindowsWrapper as J}from"./service/windows-wrapper-migration.js";const Ke=300*1e3;import{killProcessesByCommandLine as Xe,isWindowsElevated as Ve,shouldSweepWindowsWrappers as Ye,getProcessCommandLine as qe}from"./service/process-control.js";import{buildServiceID as Je}from"./service/service-paths.js";import{isInsideManagedDaemonTree as ze}from"./service/platform-adapter.js";import{acquireDaemonLock as Qe,createHealthzHolderProbe as Ze,DaemonAlreadyServingError as et,findLiveLockHolder as tt,isLockHolderSameProcess as rt,readDaemonLock as z,releaseDaemonLock as N}from"./runtime/daemon-lock.js";import{writeDaemonStatus as B,removeDaemonStatus as nt}from"./runtime/service-state.js";import{readRecentDuplicateDaemonRecord as ot,recordDuplicateDaemon as at}from"./runtime/duplicate-daemon.js";import{createStartupPhaseTimer as it}from"./runtime/startup-phases.js";import{RemoteAdminSettingsStore as st}from"./core/config/remote-admin-settings.js";import{AdminServer as ct,generateToken as lt,writeTokenFile as dt}from"./core/admin/index.js";import{initSentry as pt,whenSentryReady as Q,closeSentry as j,reportFatal as Z,isWindowsWmicProbeError as gt}from"./core/observability/sentry.js";import{initProxyManager as mt,getProxyManager as h,relayHostsForClientType as me,RelayStateStore as ut,SecretStore as ft}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as ue,switchAgentRelayMode as ee,reconcileRelayStates as fe}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as he,RelayCredentialError as ye}from"./core/proxy/relay-credential.js";import{RelayFetchError as we,RelayToggleCoordinator as ht}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as yt,clearHermesProfileRelay as wt,discoverHermesProfiles as St}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as Rt,enableRelayCredential as Et,RelayRouteError as Se}from"./core/proxy/relay-credential-router.js";import{upgradeStatusSnapshot as Dt}from"./core/upgrade/npm-upgrader.js";import{resolveClientVersion as At}from"./core/util/client-version.js";import{parseCliArgs as vt}from"./core/util/cli-args.js";const{command:I,flags:E,unknownFlags:te}=vt(process.argv.slice(2));if(te.length>0&&(console.error(`Unknown option${te.length>1?"s":""}: ${te.map(t=>`--${t}`).join(", ")}
|
|
3
|
+
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),E.version&&(console.log(At()),process.exit(0)),E.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=
|
|
37
|
+
`),process.exit(0)),I==="reload"){const t=de();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=z(Y().daemonLockFile);s&&s.pid===t&&!rt(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 Re="detected connector-managed session; restart delegated to launchd, this process may be terminated by the restart",Ee=["start","stop","restart","status"];if(I&&Ee.includes(I)){process.platform==="win32"&&["start","stop","restart"].includes(I)&&!Ve()&&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=
|
|
40
|
-
Valid commands: ${
|
|
41
|
-
`),i.warn("main",s);const g=
|
|
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 _(t){if(j)return;j=!0,i.info("main",`Received ${t}, shutting down...`),M.markShuttingDown();const s=Y();s&&de(s);const g=t==="upgrade-switch"?Ne:t==="launcher-unhealthy"?Ge:null,u=g??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 tt(c.daemonStatusFile).catch(()=>{}),clearTimeout(h),T(),i.info("main","Shutdown complete"),process.exit(g??(l?2:0))}catch(S){i.error("main",`Shutdown error: ${S}`),F(c.daemonLockFile).catch(()=>{}),T(),process.exit(u)}}async function bt(){Te(),$e(),Ue(c.stdoutLogFile,c.stderrLogFile),ie(!1);try{$=(await Je(c.daemonLockFile,c.rootDir,2,{isHolderServing:ze(c.dataDir)})).instance_id}catch(e){e instanceof Qe&&At(e.holder),console.error(e instanceof Error?e.message:e),process.exit(1)}if(Xe()){const e=Ye(c.rootDir,"win32");await We(e,{platform:"win32"})}await ct(),Oe(),i.info("main",`grix-connector starting (PID ${process.pid})`);try{Fe({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:()=>{_("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:()=>{_("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}`)})},je).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 g=b.join(c.dataDir,"health-port");ae(g,String(s),"utf-8"),process.on("SIGINT",()=>_("SIGINT")),process.on("SIGTERM",()=>_("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),_("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!Ae(e)&&(z(e,"unhandledRejection"),W("unhandledRejection",e),_("unhandledRejection"))});const S=dt(c.dataDir);await S.clearRuntimeState();const k=new pt(b.join(c.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),N=new gt(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:k,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 ge(r,o)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};o.setRelayEnvSettledHandler(()=>{const r=f();r&&me(r,L,k),e().catch(a=>{i.warn("main",`failed to stop idle mitm proxy: ${a}`)})});const n=()=>{const r=f(),a=r.getRelayAgents(),p=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:o.getAgentsWithStaleRelayEnv(),degradedRelayAgents:p,relayState:k.list(),runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}},d=new mt,A={status:()=>n(),setAgentRelay:async(r,a,p)=>{const m=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){d.cancel(r);const w=await d.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(!Me(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 d.runExclusive(r,async()=>{const w=d.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=d.waitCancelled(r,w);let C;try{C=await Promise.race([o.fetchRelayCredential(r,{model:p}),ne.promise.then(()=>{throw new he("relay enable cancelled by a later disable","CANCELLED")})])}finally{ne.dispose()}d.assertCurrent(r,w);const{restarted:xe,busy:Pe,pending:oe}=await ue(m,o,R,E,{localName:r,agentId:re,virtualKey:C.apiKey,anthropicBaseUrl:C.anthropicBaseUrl,openaiBaseUrl:C.openaiBaseUrl,model:p,...C.directRelay!==void 0?{directRelay:C.directRelay}:{}},(Ie,_e,ke)=>o.setAgentProvider(Ie,_e,{deferRestart:ke?.deferRestart===!0}),{store:k,secrets:N,direct:L});if(d.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 p=f(),m=o.getAgentsStatus(),{restarted:E,busy:R,pending:y}=await ue(p,o,P=>m.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:L});return o.reportRelayStateLocalChange(r),{...n(),restarted:E,busy:R,...y?{pending:y}:{}}},disableAll:async()=>{const r=f(),a={info:m=>i.info("main",m),warn:m=>i.warn("main",m)},p=new Set([...r.getRelayAgents(),...o.getDirectRelayConfigAgentNames(),...Object.keys(k.list())]);for(const m of p)d.cancel(m);for(const m of p)await d.runExclusive(m,()=>Q(K(),a,{agentName:m,target:"off"})),o.reportRelayStateLocalChange(m);return await ge(r,o),n()},setRoute:async(r,a)=>{const p=a,m=f();return m.setRoute({routeKey:r,targetBaseUrl:p.targetBaseUrl,...p.headers?{headers:p.headers}:{},...p.model?{model:p.model}:{},...p.modelMap?{modelMap:p.modelMap}:{},...p.passthrough?{passthrough:!0}:{},...p.relayOnly?{relayOnly:!0}:{},...p.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await m.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 m=o.getAgentsStatus().find(y=>y.name===r),E=`grix-gateway-${String(m?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(y=>y.routeKey===E)?.model?.trim()||void 0}}const p=o.getAgentProvider(r);return p?.api_key?.trim()?{enabled:!0,model:p.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(r,a,p)=>{const m=o.getAgentsStatus().find(y=>y.name===r),E=String(m?.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:p??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(),n=Le();return{ws:{connected:e.filter(d=>d.wsConnected===!0).length,total:e.length},upgrade:St(),log:{dir:n.logDir,file:n.logFile,primary_dir:n.primaryLogDir,degraded:n.degraded,using_fallback:n.usingFallback,...n.degradeReason?{degrade_reason:n.degradeReason}:{}}}});const X=new ot(b.join(c.dataDir,"remote-admin.json"));if(o.setRemoteAdminGate(()=>X.isEnabled()),await o.start(Dt),U.length>0){const e=o.getAgentsStatus();for(const n of U){const d=e.find(r=>r.name===n);if(!d)continue;const A=pe(d.clientType);A.length!==0&&await S.setAgentRelayEnabled(n,!0,{relayHosts:A}).catch(r=>{i.warn("main",`failed to reconcile relay hosts for "${n}": ${r}`)})}}me(S,L,k);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,d)=>{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:d.virtualKey,anthropicBaseUrl:d.anthropicBaseUrl,openaiBaseUrl:d.openaiBaseUrl,model:d.model,...d.directRelay!==void 0?{directRelay:d.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)=>ut({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>ft(e)};x.setRelayCredentialHandler({listHermesProfiles:()=>ht(),enable:async(e,n)=>{const{target:d,result:A}=await wt(te,e,n);return{target:d,...A}},disable:async e=>{const{target:n,result:d}=await yt(te,e);return{target:n,...d}}});{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");st(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")}bt().catch(async t=>{const s=`Fatal: ${t instanceof Error?t.stack??t.message:String(t)}`;try{process.stderr.write(`${s}
|
|
43
|
-
`)}catch{}
|
|
39
|
+
right-click the terminal and select "Run as administrator".`);const t=Y(),s=E["config-dir"]??(E.profile?b.join(t.configDir,E.profile):void 0),g=b.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),f=new pe({cliPath:g,nodePath:process.execPath});try{let l;switch(I){case"start":{const m=await f.healLegacyState({rootDir:t.rootDir});(await f.status({rootDir:t.rootDir})).installed?l=await f.start({rootDir:t.rootDir}):l=await f.install({rootDir:t.rootDir,configDir:s??m.configDir}),m.healed&&(l={...l,legacy_heal:m.reasons})}break;case"stop":l=await f.stop({rootDir:t.rootDir});break;case"restart":if(!(await f.status({rootDir:t.rootDir})).installed)l=await f.install({rootDir:t.rootDir,configDir:s});else{const D=process.platform==="darwin"&&ze();D&&console.warn(Re),l=await f.restart({rootDir:t.rootDir}),D&&(l={...l,mode:"kickstart",note:Re})}break;case"status":l=await f.status({rootDir:t.rootDir});{const m=ot(t.dataDir);m&&(l={...l,duplicate_daemon:m})}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: ${Ee.join(", ")}`),process.exit(1));const c=Y(),bt=E["config-dir"]??(E.profile?`${c.configDir}/${E.profile}`:void 0),o=new $e({requestGracefulShutdown:t=>{k(t)},onTransactionSettled:()=>{J({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}`)})}}),G=new Fe,De=lt(),x=new ct(De);let W=!1,$;function K(t,s){const g=q(),f=process.env.GRIX_LAUNCHER_VERSION;if(!g||!f)return;const l=s instanceof Error?s.stack??s.message:String(s);We(g,f,`${t}: ${l}`)}function Ae(t,s){at(c.dataDir,{holder_pid:t.pid,holder_command_line:t.command_line,yielded_pid:process.pid,yielded_command_line:qe(process.pid)}),process.stderr.write(`${s}
|
|
41
|
+
`),i.warn("main",s);const g=q();g&&ge(g)}function xt(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.`;Ae(t,s),process.exit(0)}function Pt(t,s){const g=`${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`;Ae(t,g),de()===process.pid&&T(),process.exit(0)}async function ve(t){if(t.kind==="in_use"){const g=tt(c.daemonLockFile);g&&(await j(),Pt(g,t))}process.stderr.write(t.message+`
|
|
42
|
+
`),i.error("main",t.message.replace(/\n/g," \u2014 ")),K("port_bind",t.message);const s=z(c.daemonLockFile);s?.pid===process.pid&&s.instance_id===$&&await B(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:$,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await j(),N(c.daemonLockFile).catch(()=>{}),T(),process.exit(1)}async function k(t){if(W)return;W=!0,i.info("main",`Received ${t}, shutting down...`),G.markShuttingDown();const s=q();s&&ge(s);const g=t==="upgrade-switch"?Be:t==="launcher-unhealthy"?je:null,f=g??2,l=t==="uncaughtException"||t==="unhandledRejection",m=setTimeout(()=>{i.error("main","Shutdown timed out, forcing exit"),N(c.daemonLockFile).catch(()=>{}),T(),process.exit(f)},1e4);try{await o.stop(),await h()?.stop().catch(()=>{}),await x.stop(),await G.stop(),await j(),await N(c.daemonLockFile),await nt(c.daemonStatusFile).catch(()=>{}),clearTimeout(m),T(),i.info("main","Shutdown complete"),process.exit(g??(l?2:0))}catch(D){i.error("main",`Shutdown error: ${D}`),N(c.daemonLockFile).catch(()=>{}),T(),process.exit(f)}}async function It(){const t=it();t.lap("boot"),Le(),Ue(),Oe(c.stdoutLogFile,c.stderrLogFile),ce(!1);try{$=(await Qe(c.daemonLockFile,c.rootDir,2,{isHolderServing:Ze(c.dataDir)})).instance_id}catch(e){e instanceof et&&xt(e.holder),console.error(e instanceof Error?e.message:e),process.exit(1)}if(t.lap("lock"),Ye()){const e=Je(c.rootDir,"win32");await Xe(e,{platform:"win32"})}t.lap("sweep"),pt(),Ne(),t.lap("pidfile"),i.info("main",`grix-connector starting (PID ${process.pid})`);try{Ge({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)}`)}t.lap("orphan"),await B(c.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:$,updated_at:Date.now()}),process.env.GRIX_LAUNCHER_MANAGED!=="1"&&await new pe({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}`)}),t.lap("adopt");const s=await J({rootDir:c.rootDir,requestGracefulShutdown:()=>{k("wrapper-migration")}}).catch(e=>(i.warn("main",`Windows wrapper migration failed: ${e instanceof Error?e.message:e}`),null));if(t.lap("migrate"),s?.handoffStarted)return;process.platform==="win32"&&process.env.GRIX_LAUNCHER_MANAGED!=="1"&&setInterval(()=>{W||J({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}`)})},Ke).unref();const g=parseInt(E["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await le({label:"health",port:g,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:n=>G.start(n)});e&&await ve(e)}const f=b.join(c.dataDir,"health-port");se(f,String(g),"utf-8"),t.lap("health"),process.on("SIGINT",()=>k("SIGINT")),process.on("SIGTERM",()=>k("SIGTERM")),process.on("SIGHUP",()=>{W||(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 l="",m=0,D;process.on("uncaughtException",e=>{const n=e instanceof Error?e.stack??e.message:String(e);n===l?(m++,(m<=3||m%100===0)&&i.error("main",`Uncaught exception (x${m}): ${n}`)):(m>3&&i.error("main",`Previous exception repeated ${m} times total`),l=n,m=1,i.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),D||(D=setTimeout(()=>{m>3&&i.error("main",`Previous exception repeated ${m} times total`),l="",m=0,D=void 0},1e4).unref())),!be(e)&&(Q().then(()=>Z(e,"uncaughtException")),K("uncaughtException",e),k("uncaughtException"))}),process.on("unhandledRejection",e=>{i.error("main",`Unhandled rejection: ${e}`),!be(e)&&(Q().then(()=>Z(e,"unhandledRejection")),K("unhandledRejection",e),k("unhandledRejection"))});const L=mt(c.dataDir);await L.clearRuntimeState();const _=new ut(b.join(c.dataDir,"proxy","relay-state.json"),{warn:e=>i.warn("main",e)}),M=new ft(b.join(c.dataDir,"secrets"));o.setRelaySecretResolver(e=>M.get(e));const U={hasDirectConfig:e=>o.hasDirectRelayConfig(e),clearDirectConfig:(e,n)=>o.setAgentProvider(e,void 0,{deferRestart:n?.deferRestart===!0})},X=()=>({pm:h(),agents:o,direct:U,store:_,secrets:M}),H=L.getRelayAgents();if(H.length>0)try{await L.startIfAnyRelayAgent(),i.info("main",`mitm proxy started for relay agents: ${H.join(", ")} (per-agent; others stay direct)`)}catch(e){i.error("main",`mitm proxy FAILED to start (relay agents: ${H.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 O=null;if(h()){const e=async()=>{const r=h();r&&await ue(r,o)&&i.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};o.setRelayEnvSettledHandler(()=>{const r=h();r&&fe(r,U,_),e().catch(a=>{i.warn("main",`failed to stop idle mitm proxy: ${a}`)})});const n=()=>{const r=h(),a=r.getRelayAgents(),p=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:o.getAgentsWithStaleRelayEnv(),degradedRelayAgents:p,relayState:_.list(),runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}},d=new ht,A={status:()=>n(),setAgentRelay:async(r,a,p)=>{const u=h(),R={info:w=>i.info("main",w),warn:w=>i.warn("main",w)},S=w=>o.getAgentsStatus().find(v=>v.name===w),y=S(r);if(!a){d.cancel(r);const w=await d.runExclusive(r,async()=>{const v=await ee(X(),R,{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(!Me(y.clientType)&&me(y.clientType).length===0)throw Object.assign(new Error(`client type "${y.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const F=await d.runExclusive(r,async()=>{const w=d.epoch(r),v=S(r);if(!v)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});const oe=String(v.agentId??"").trim();if(!oe)throw new ye(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const ae=d.waitCancelled(r,w);let C;try{C=await Promise.race([o.fetchRelayCredential(r,{model:p}),ae.promise.then(()=>{throw new we("relay enable cancelled by a later disable","CANCELLED")})])}finally{ae.dispose()}d.assertCurrent(r,w);const{restarted:Ie,busy:ke,pending:ie}=await he(u,o,S,R,{localName:r,agentId:oe,virtualKey:C.apiKey,anthropicBaseUrl:C.anthropicBaseUrl,openaiBaseUrl:C.openaiBaseUrl,model:p,...C.directRelay!==void 0?{directRelay:C.directRelay}:{}},(_e,Ce,Te)=>o.setAgentProvider(_e,Ce,{deferRestart:Te?.deferRestart===!0}),{store:_,secrets:M,direct:U});if(d.epoch(r)!==w)throw await ee(X(),R,{agentName:r,target:"off"}),new we("relay enable cancelled by a later disable","CANCELLED");return{...n(),restarted:Ie,busy:ke,...ie?{pending:ie}:{}}});return o.reportRelayStateLocalChange(r),F},setAgentRelayCredential:async(r,a)=>{const p=h(),u=o.getAgentsStatus(),{restarted:R,busy:S,pending:y}=await he(p,o,P=>u.find(F=>F.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,F,w)=>o.setAgentProvider(P,F,{deferRestart:w?.deferRestart===!0}),{store:_,secrets:M,direct:U});return o.reportRelayStateLocalChange(r),{...n(),restarted:R,busy:S,...y?{pending:y}:{}}},disableAll:async()=>{const r=h(),a={info:u=>i.info("main",u),warn:u=>i.warn("main",u)},p=new Set([...r.getRelayAgents(),...o.getDirectRelayConfigAgentNames(),...Object.keys(_.list())]);for(const u of p)d.cancel(u);for(const u of p)await d.runExclusive(u,()=>ee(X(),a,{agentName:u,target:"off"})),o.reportRelayStateLocalChange(u);return await ue(r,o),n()},setRoute:async(r,a)=>{const p=a,u=h();return u.setRoute({routeKey:r,targetBaseUrl:p.targetBaseUrl,...p.headers?{headers:p.headers}:{},...p.model?{model:p.model}:{},...p.modelMap?{modelMap:p.modelMap}:{},...p.passthrough?{passthrough:!0}:{},...p.relayOnly?{relayOnly:!0}:{},...p.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await u.persistConfig(),n()},deleteRoute:async r=>{const a=h();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=h();return a.setDefaultRouteKey(r),await a.persistConfig(),n()},setInterceptHosts:async r=>{const a=h();return a.setInterceptHosts(r),await a.persistConfig(),n()}};O=A,x.setProxyHandler(A),o.setRelayStateApplier({getLocalState:r=>{const a=h();if(a.getRelayAgents().includes(r)){const u=o.getAgentsStatus().find(y=>y.name===r),R=`grix-gateway-${String(u?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(y=>y.routeKey===R)?.model?.trim()||void 0}}const p=o.getAgentProvider(r);return p?.api_key?.trim()?{enabled:!0,model:p.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(r,a,p)=>{const u=o.getAgentsStatus().find(y=>y.name===r),R=String(u?.agentId??"").trim();if(!R)throw new ye(`local agent "${r}" has no agent_id registered`,"MISSING_AGENT_ID");const S=await A.setAgentRelayCredential(r,{agentId:R,virtualKey:a.apiKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:p??a.model,...a.directRelay!==void 0?{directRelay:a.directRelay}:{}});return{restarted:S.restarted===!0,busy:S.busy===!0,...S.pending?{pending:S.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}:{}}}})}G.setStatusProvider(()=>o.getAgentsStatus()),G.setMetaProvider(()=>{const e=o.getAgentsStatus(),n=He();return{ws:{connected:e.filter(d=>d.wsConnected===!0).length,total:e.length},upgrade:Dt(),log:{dir:n.logDir,file:n.logFile,primary_dir:n.primaryLogDir,degraded:n.degraded,using_fallback:n.usingFallback,...n.degradeReason?{degrade_reason:n.degradeReason}:{}}}});const V=new st(b.join(c.dataDir,"remote-admin.json"));if(o.setRemoteAdminGate(()=>V.isEnabled()),t.lap("setup"),await o.start(bt),t.lap("agents"),H.length>0){const e=o.getAgentsStatus();for(const n of H){const d=e.find(r=>r.name===n);if(!d)continue;const A=me(d.clientType);A.length!==0&&await L.setAgentRelayEnabled(n,!0,{relayHosts:A}).catch(r=>{i.warn("main",`failed to reconcile relay hosts for "${n}": ${r}`)})}}fe(L,U,_);const re=parseInt(E["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:()=>V.isEnabled(),setEnabled:e=>{const n=V.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 ne={listAgents:()=>o.getAgentsStatus(),enableManaged:(e,n,d)=>{if(!O)throw new Se(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return O.setAgentRelayCredential(e,{agentId:n,virtualKey:d.virtualKey,anthropicBaseUrl:d.anthropicBaseUrl,openaiBaseUrl:d.openaiBaseUrl,model:d.model,...d.directRelay!==void 0?{directRelay:d.directRelay}:{}})},disableManaged:e=>{if(!O)throw new Se(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return O.setAgentRelay(e,!1)},enableHermesProfile:(e,n)=>yt({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>wt(e)};x.setRelayCredentialHandler({listHermesProfiles:()=>St(),enable:async(e,n)=>{const{target:d,result:A}=await Et(ne,e,n);return{target:d,...A}},disable:async e=>{const{target:n,result:d}=await Rt(ne,e);return{target:n,...d}}});{const e=await le({label:"admin",port:re,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:n=>x.start(n)});e&&await ve(e)}const xe=b.join(c.dataDir,"admin-token"),Pe=b.join(c.dataDir,"admin-port");dt(xe,De),se(Pe,String(re),"utf-8"),t.lap("admin"),await B(c.daemonStatusFile,{state:"running",pid:process.pid,instance_id:$,updated_at:Date.now()}),i.info("main",`startup phases: ${t.summary()}`),process.send&&process.send("ready"),i.info("main","grix-connector ready")}It().catch(async t=>{const s=`Fatal: ${t instanceof Error?t.stack??t.message:String(t)}`;try{process.stderr.write(`${s}
|
|
43
|
+
`)}catch{}ce(!1),i.error("main",s),K("startup",t);const g=z(c.daemonLockFile);g?.pid===process.pid&&g.instance_id===$&&await B(c.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:g.instance_id,updated_at:Date.now(),reason:"startup_error"}).catch(()=>{}),await Q(),Z(t,"startup"),await j(),N(c.daemonLockFile).catch(()=>{}),T(),process.exit(1)});const kt=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function be(t){return gt(t)?!0:t instanceof Error&&"code"in t?kt.has(t.code):!1}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`,"utf-8")}catch{}}export{
|
|
1
|
+
import{existsSync as u,readFileSync as i,writeFileSync as l}from"node:fs";import a from"node:path";const s=10080*60*1e3;function o(e){return a.join(e,"duplicate-daemon.json")}function c(e){const r=o(e);if(!u(r))return null;try{const t=JSON.parse(i(r,"utf-8"));return typeof t?.occurrences=="number"?t:null}catch{return null}}function f(e,r=Date.now()){const t=c(e);if(!t)return null;const n=Date.parse(t.last_yield_at);return Number.isFinite(n)&&r-n>s?null:t}function D(e,r){const t=c(e),n={...r,occurrences:(t?.occurrences??0)+1,last_yield_at:new Date().toISOString()};try{l(o(e),`${JSON.stringify(n,null,2)}
|
|
2
|
+
`,"utf-8")}catch{}}export{s as DUPLICATE_DAEMON_STATUS_TTL_MS,c as readDuplicateDaemonRecord,f as readRecentDuplicateDaemonRecord,D as recordDuplicateDaemon,o as resolveDuplicateDaemonRecordPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function o(r=()=>performance.now()){const t=[];let e=0;return{lap(n){const a=r();t.push(`${n}=${Math.round(a-e)}ms`),e=a},summary(){return[...t,`total=${Math.round(r())}ms`].join(" ")}}}export{o as createStartupPhaseTimer};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as v,readFileSync as V}from"node:fs";import{mkdir as P,readdir as I,readFile as S,rm as y,stat as it,writeFile as $}from"node:fs/promises";import p from"node:os";import c from"node:path";import{fileURLToPath as ot,pathToFileURL as at}from"node:url";import{GRIX_HOME_ENV as T}from"../core/config/paths.js";import{isProcessRunning as R,runCommand as f,spawnDetached as _,isWindowsElevated as st,killProcessesByCommandLine as X,findProcessesByCommandLine as ct}from"./process-control.js";import{getServicePrefix as L,parseConfigDirFromPlistXML as lt,parseConfigDirFromSystemdUnit as ut,resolveBareDaemonMarkerPath as x,resolveLinuxUserUnitPath as dt,resolveMacOSLaunchAgentPath as mt}from"./service-paths.js";import{LAUNCHER_FILE as pt,readPointer as ft,versionDir as ht}from"../core/upgrade/version-store.js";function w(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function D(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function Q(t){return String(t??"").replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function z(t){return`"${Q(t).replace(/%/g,"%%")}"`}function O(t){return String(t??"").replace(/%/g,"%%")}function gt(t){return`"${Q(t).replace(/\$/g,()=>"$$").replace(/%/g,"%%")}"`}function j(t=process.env){const r=[];for(const e of["GRIX_HEALTH_PORT","GRIX_ADMIN_PORT"]){const n=String(t[e]??"").trim();/^\d{2,5}$/.test(n)&&r.push([e,n])}return r}function J(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function wt(t){const r=J(t);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
@@ -6,7 +6,7 @@ import{existsSync as C,readFileSync as V}from"node:fs";import{mkdir as v,readdir
|
|
|
6
6
|
<string>${w(t.serviceID)}</string>
|
|
7
7
|
<key>ProgramArguments</key>
|
|
8
8
|
<array>
|
|
9
|
-
${
|
|
9
|
+
${r.map(e=>` <string>${w(e)}</string>`).join(`
|
|
10
10
|
`)}
|
|
11
11
|
</array>
|
|
12
12
|
<key>RunAtLoad</key>
|
|
@@ -22,8 +22,8 @@ ${n.map(e=>` <string>${w(e)}</string>`).join(`
|
|
|
22
22
|
<dict>
|
|
23
23
|
<key>${T}</key>
|
|
24
24
|
<string>${w(t.rootDir)}</string>
|
|
25
|
-
${j().map(([e,
|
|
26
|
-
<string>${
|
|
25
|
+
${j().map(([e,n])=>` <key>${e}</key>
|
|
26
|
+
<string>${n}</string>
|
|
27
27
|
`).join("")}${t.environmentPath?` <key>PATH</key>
|
|
28
28
|
<string>${w(t.environmentPath)}</string>
|
|
29
29
|
`:""} </dict>
|
|
@@ -33,16 +33,16 @@ ${j().map(([e,r])=>` <key>${e}</key>
|
|
|
33
33
|
<string>${w(t.stderrPath)}</string>
|
|
34
34
|
</dict>
|
|
35
35
|
</plist>
|
|
36
|
-
`}function yt(t){const
|
|
36
|
+
`}function yt(t){const r=J(t).map(e=>gt(e)).join(" ");return`[Unit]
|
|
37
37
|
Description=grix-connector daemon (${t.serviceID})
|
|
38
38
|
After=network.target
|
|
39
39
|
|
|
40
40
|
[Service]
|
|
41
41
|
Type=simple
|
|
42
|
-
ExecStart=${
|
|
42
|
+
ExecStart=${r}
|
|
43
43
|
WorkingDirectory=${O(c.dirname(t.cliPath))}
|
|
44
44
|
Environment=${z(`${T}=${t.rootDir}`)}
|
|
45
|
-
${j().map(([e,
|
|
45
|
+
${j().map(([e,n])=>`Environment=${e}=${n}
|
|
46
46
|
`).join("")}${t.environmentPath?`Environment=${z(`PATH=${t.environmentPath}`)}
|
|
47
47
|
`:""}
|
|
48
48
|
Restart=always
|
|
@@ -52,8 +52,8 @@ StandardError=append:${O(t.stderrPath)}
|
|
|
52
52
|
|
|
53
53
|
[Install]
|
|
54
54
|
WantedBy=default.target
|
|
55
|
-
`}function
|
|
56
|
-
`);return{path:e,content:
|
|
55
|
+
`}function k(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return c.join(r,".grix",`${e}-wrapper.vbs`)}function M(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),n=c.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return c.join(n,`${e}.lnk`)}function m(t){return String(t??"").replace(/"/g,'""')}function A(t){return`'${String(t??"").replace(/'/g,"''")}'`}function $t(t,r){const e=c.join(c.dirname(t),c.basename(r).replace(/\.lnk$/,"-create.vbs")),n=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${m(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${m(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
|
|
56
|
+
`);return{path:e,content:n}}async function Et(t){const{wrapperPath:r,shortcutPath:e,runCommand:n}=t,i=$t(r,e);let o="";for(let l=1;l<=3;l+=1){try{await P(c.dirname(e),{recursive:!0}),await $(i.path,i.content,"utf8");const a=await n("wscript.exe",[i.path,"//B","//NoLogo"],{allowFailure:!0}),s=String(a?.stderr??"").trim()||String(a?.stdout??"").trim(),u=Number(a?.exitCode??0);o=u===0?"wscript exited 0 but the shortcut was not created":`wscript exited with code ${u}${s?`: ${s}`:""}`}catch(a){o=a instanceof Error?a.message:String(a)}finally{await y(i.path,{force:!0}).catch(()=>{})}if(v(e))return!0}return console.warn(`Warning: failed to create Windows logon-autostart shortcut at ${e} after 3 attempts. Last error: ${o}. The daemon will NOT start automatically after the next reboot/logon. If the error points to blocked writes, check Controlled Folder Access / antivirus for the Startup folder; otherwise create the shortcut manually.`),!1}async function Pt(t){try{const r=await t("whoami",["/user","/fo","csv","/nh"],{allowFailure:!0}),e=String(r?.stdout??"").match(/"(S-1-[0-9-]+)"/);if(e)return e[1]}catch{}return null}function St(t){const r=`//B //NoLogo "${t.wrapperPath}"`;return`<?xml version="1.0" encoding="UTF-16"?>
|
|
57
57
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
58
58
|
<RegistrationInfo>
|
|
59
59
|
<Description>grix-connector daemon autostart</Description>
|
|
@@ -92,17 +92,17 @@ WantedBy=default.target
|
|
|
92
92
|
<Actions Context="Author">
|
|
93
93
|
<Exec>
|
|
94
94
|
<Command>wscript.exe</Command>
|
|
95
|
-
<Arguments>${w(
|
|
95
|
+
<Arguments>${w(r)}</Arguments>
|
|
96
96
|
</Exec>
|
|
97
97
|
</Actions>
|
|
98
98
|
</Task>
|
|
99
|
-
`}async function xt(t){const{serviceID:
|
|
100
|
-
`),i=Buffer.from(
|
|
101
|
-
`)}function vt(t,
|
|
102
|
-
${
|
|
99
|
+
`}async function xt(t){const{serviceID:r,wrapperPath:e,runCommand:n}=t,i=await Pt(n)??p.userInfo().username,o=c.join(c.dirname(e),`${r}-task.xml`);try{const a=St({userId:i,wrapperPath:e});return await $(o,"\uFEFF"+a,"utf16le"),await n("schtasks",["/Create","/TN",r,"/XML",o,"/F"]),!0}catch{return!1}finally{await y(o,{force:!0}).catch(()=>{})}}const K=4,kt="grix-wrapper-template:";function Ct(t){const r=c.join(t.rootDir,"data","upgrade-wrapper-child.json"),e=[`"${t.cliPath}"`,...t.configDir?["--config-dir",`"${t.configDir}"`]:[]],n=["$ErrorActionPreference = 'Stop'",`$recordPath = ${A(r)}`,"$exitCode = 1","try {"," [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($recordPath)) | Out-Null",` $p = Start-Process -FilePath ${A(t.nodePath)} -ArgumentList @(${e.map(A).join(", ")}) -WindowStyle Hidden -PassThru`,` $record = @{ pid = $p.Id; launched_at = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds(); boot_time = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - [Environment]::TickCount64; cli_path = ${A(t.cliPath)} }`," $utf8NoBom = New-Object System.Text.UTF8Encoding($false)"," try {"," [IO.File]::WriteAllText($recordPath, ($record | ConvertTo-Json -Compress), $utf8NoBom)"," } catch {",' & "$env:SystemRoot\\System32\\taskkill.exe" /PID $p.Id /T /F 2>$null | Out-Null'," $p.WaitForExit()"," throw"," }"," $p.WaitForExit()"," $exitCode = $p.ExitCode","} finally {"," Remove-Item -LiteralPath $recordPath -Force -ErrorAction SilentlyContinue","}","exit $exitCode"].join(`
|
|
100
|
+
`),i=Buffer.from(n,"utf16le").toString("base64"),l=`"${c.join(process.env.SystemRoot||"C:\\Windows","System32","WindowsPowerShell","v1.0","powershell.exe")}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${i}`,a=c.join(t.rootDir,"bin","upgrade-guardian.mjs"),s=c.join(t.rootDir,"bin","upgrade-guardian.fallback.mjs"),u=c.join(t.rootDir,"data","upgrade-wrapper.pause.json"),d=c.join(t.rootDir,"data","upgrade-pending.json"),h=c.join(t.rootDir,"data"),g=c.join(h,"wrapper.lock"),E=[`"${m(t.nodePath)}"`,`"${m(a)}"`,"activate","--root",`"${m(t.rootDir)}"`].join(" "),F=`${E} --wrapper-handoff`,H=[`"${m(t.nodePath)}"`,`"${m(s)}"`,"activate","--root",`"${m(t.rootDir)}"`].join(" "),et=`${H} --wrapper-handoff`;return[`' ${kt} ${K}`,"Option Explicit","Dim shell, processEnv, fso, command, guardianCommand, handoffGuardianCommand, guardianFallbackCommand, handoffGuardianFallbackCommand, guardianPath, guardianFallbackPath, maintenanceFile, pendingFile, delayMs, rapidCount, guardianExit, wrapperLockDir, wrapperLockFile, wrapperLock",`command = "${m(l)}"`,`guardianCommand = "${m(E)}"`,`handoffGuardianCommand = "${m(F)}"`,`guardianFallbackCommand = "${m(H)}"`,`handoffGuardianFallbackCommand = "${m(et)}"`,`guardianPath = "${m(a)}"`,`guardianFallbackPath = "${m(s)}"`,`maintenanceFile = "${m(u)}"`,`pendingFile = "${m(d)}"`,`wrapperLockDir = "${m(h)}"`,`wrapperLockFile = "${m(g)}"`,'Set shell = CreateObject("WScript.Shell")','Set processEnv = shell.Environment("Process")','Set fso = CreateObject("Scripting.FileSystemObject")',"On Error Resume Next","If Not fso.FolderExists(wrapperLockDir) Then fso.CreateFolder wrapperLockDir","Err.Clear","Set wrapperLock = fso.OpenTextFile(wrapperLockFile, 2, True)","If Err.Number <> 0 Then"," Err.Clear"," If fso.FileExists(wrapperLockFile) Then"," On Error GoTo 0"," WScript.Quit 0"," End If","Else"," wrapperLock.Write CStr(Now())"," Err.Clear","End If","On Error GoTo 0",`processEnv("${T}") = "${m(t.rootDir)}"`,'processEnv("GRIX_WRAPPER_SUPERVISED") = "1"',...j().map(([rt,nt])=>`processEnv("${rt}") = "${nt}"`),"delayMs = 5000","rapidCount = 0","Do"," Do While fso.FileExists(maintenanceFile)"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(guardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(guardianFallbackCommand, 0, True)"," Else"," On Error Resume Next"," fso.DeleteFile maintenanceFile, True"," On Error GoTo 0"," WScript.Sleep 1000"," End If"," If fso.FileExists(maintenanceFile) Then WScript.Sleep 3000"," Loop"," If fso.FileExists(pendingFile) Then"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(guardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(guardianFallbackCommand, 0, True)"," End If"," End If"," shell.Run command, 0, True"," If fso.FileExists(pendingFile) Then"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(handoffGuardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(handoffGuardianFallbackCommand, 0, True)"," End If"," End If"," If fso.FileExists(pendingFile) And Not PendingIsTerminal() Then"," rapidCount = 0"," WScript.Sleep delayMs"," ElseIf fso.FileExists(maintenanceFile) Then"," rapidCount = 0"," Else"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If"," End If","Loop","","Function PendingIsTerminal()"," Dim stream, text"," PendingIsTerminal = False"," On Error Resume Next"," Set stream = fso.OpenTextFile(pendingFile, 1)"," If Err.Number <> 0 Then"," Err.Clear"," On Error GoTo 0"," Exit Function"," End If"," text = stream.ReadAll"," stream.Close"," If Err.Number <> 0 Then"," Err.Clear"," On Error GoTo 0"," Exit Function"," End If"," On Error GoTo 0",' text = Replace(Replace(Replace(text, " ", ""), vbCr, ""), vbLf, "")',' If InStr(text, """phase"":""failed""") > 0 Then PendingIsTerminal = True',' If InStr(text, """phase"":""finalizing""") > 0 Then PendingIsTerminal = True',"End Function",""].join(`\r
|
|
101
|
+
`)}function vt(t,r){return c.join(ht(t,r),"dist","service","platform-adapter.js")}async function Tt(t,r={}){const e={version:K,build:Ct};try{if(c.basename(t)!==pt)return e;const n=c.dirname(t),i=ft(n,"current");if(!i)return e;const o=vt(n,i),l=r.selfModulePath??ot(import.meta.url);if(c.resolve(o)===c.resolve(l)||!v(o))return e;const a=await import(at(o).href),s=Number(a.WINDOWS_WRAPPER_TEMPLATE_VERSION),u=a.buildWindowsWrapperScript;return typeof u!="function"||!Number.isFinite(s)||s<=e.version?e:{version:s,build:u}}catch{return e}}function b(t){return`gui/${t??0}`}function bt(t=process.env){return t.GRIX_LAUNCHER_MANAGED==="1"||!!t.GRIX_CLAUDE_INTERNAL_API_URL}function U(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function N(t,r,e,n){if(t?.trim())return c.resolve(t);const i=r||e;return i?c.resolve(c.dirname(c.dirname(i))):c.join(n,".grix")}async function q(t){try{await it(t)}catch{throw new Error(`launchd plist missing: ${t}`)}}async function Y(t){const{serviceID:r,domain:e,runCommand:n}=t,i=`${e}/${r}`,o=await n("launchctl",["bootout",i],{allowFailure:!0});if(Number(o?.exitCode??-1)!==0){const l=await n("launchctl",["print",i],{allowFailure:!0});if(Number(l?.exitCode??1)===0){await new Promise(s=>setTimeout(s,250));const a=await n("launchctl",["print",i],{allowFailure:!0});if(Number(a?.exitCode??1)===0)throw new Error(`launchctl bootout ${i}: ${U(o)}`)}return}for(let l=0;l<20;l+=1){const a=await n("launchctl",["print",i],{allowFailure:!0});if(Number(a?.exitCode??1)!==0)return;await new Promise(s=>setTimeout(s,250))}throw new Error(`launchctl bootout ${i}: service remains loaded`)}async function B(t){const{serviceID:r,domain:e,definitionPath:n,runCommand:i}=t,o=`${e}/${r}`,l=await i("launchctl",["bootstrap",e,n],{allowFailure:!0});if(Number(l?.exitCode??-1)===0)return;const a=await i("launchctl",["print",o],{allowFailure:!0});Number(a?.exitCode??1)===0&&await Y(t);const s=await i("launchctl",["bootstrap",e,n],{allowFailure:!0});if(Number(s?.exitCode??-1)!==0)throw new Error(`launchctl bootstrap ${e} failed: initial=${U(l)}; retry=${U(s)}`)}function Ft(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:i,stdoutPath:o,stderrPath:l,environmentPath:a="",homeDir:s=p.homedir()}){const u=mt(t,s),d=N(i,o,l,s);return await P(c.dirname(u),{recursive:!0}),await $(u,wt({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:d,stdoutPath:o,stderrPath:l,environmentPath:a}),{encoding:"utf8",mode:384}),{definitionPath:u}},async start({serviceID:t,definitionPath:r,runCommand:e=f,uid:n=process.getuid?.()??0}){const i=b(n);await q(r),await B({serviceID:t,domain:i,definitionPath:r,runCommand:e})},async stop({serviceID:t,runCommand:r=f,uid:e=process.getuid?.()??0}){const n=b(e);await r("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=f,uid:n=process.getuid?.()??0,env:i=process.env,reloadDefinition:o=!1}){const l=b(n),a={serviceID:t,domain:l,definitionPath:r,runCommand:e};if(await q(r),!o&&bt(i)){const s=await e("launchctl",["kickstart","-k",`${l}/${t}`],{allowFailure:!0});if(Number(s?.exitCode??-1)===0)return;await B(a);return}await Y(a),await B(a)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=f,uid:n=process.getuid?.()??0}){const i=b(n);await e("launchctl",["bootout",`${i}/${t}`],{allowFailure:!0}),await y(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=c.join(t,"Library","LaunchAgents"),e=await I(r).catch(()=>[]),n=L("darwin"),i=[];for(const o of e){if(!o.startsWith(n)||!o.endsWith(".plist"))continue;const l=o.slice(0,-6),a=c.join(r,o);let s=null;try{const u=await S(a,"utf8");s=lt(u)}catch{}i.push({serviceID:l,definitionPath:a,configDir:s})}return i},async isServiceLoaded({serviceID:t,runCommand:r=f,uid:e=process.getuid?.()??0}){const n=b(e),i=await r("launchctl",["print",`${n}/${t}`],{allowFailure:!0});return Number(i?.exitCode??1)===0}}}function It(t){return`${t}-wrapper.vbs`}const Rt=5e3;async function G(t,r){const e=await ct(It(t),{runCommandImpl:r,platform:"win32"}).catch(()=>[]),n=new Set([process.pid,process.ppid]);return e.filter(i=>!n.has(i))}async function Lt(t,r,e=Rt){const n=Date.now()+e;let i=await G(t,r);for(;i.length>0&&Date.now()<n;)await new Promise(o=>setTimeout(o,250)),i=await G(t,r);return i}function Dt(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:i,stdoutPath:o,stderrPath:l,runCommand:a=f,homeDir:s=p.homedir()}){const u=k(t,s),d=N(i,o,l,s);await P(c.dirname(u),{recursive:!0});const h=await Tt(e);await $(u,h.build({nodePath:r,cliPath:e,configDir:n,rootDir:d}),"utf8");let g=!1;st()&&(g=await xt({serviceID:t,wrapperPath:u,runCommand:a}));const E=M(t,s);return g?await y(E,{force:!0}).catch(()=>{}):await Et({wrapperPath:u,shortcutPath:E,runCommand:a}),{definitionPath:g?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=f,homeDir:n=p.homedir()}){const i=k(t,n);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}(await G(t,e)).length>0||_("wscript.exe",["//B","//NoLogo",i])},async stop({serviceID:t,runCommand:r=f,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await X(n,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=f,homeDir:n=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const i=`${t}-wrapper.vbs`;await X(i,{platform:"win32"});const o=k(t,n);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}(await Lt(t,e)).length>0||_("wscript.exe",["//B","//NoLogo",o])},async uninstall({serviceID:t,runCommand:r=f,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const n=k(t,e);await y(n,{force:!0});const i=M(t,e);await y(i,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=f}={}){const e=L("win32"),n=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),i=[];if(Number(n?.exitCode??-1)===0){const a=String(n.stdout??"").split(/\r?\n/);for(const s of a){const u=s.match(/^"([^"]+)"/);if(!u)continue;const d=u[1];if(!d.startsWith(e))continue;let h=null;try{const g=k(d,t),F=(await S(g,"utf8")).match(/--config-dir\s+""([^""]+)""/);F&&(h=F[1])}catch{}i.push({serviceID:d,definitionPath:`task:${d}`,configDir:h})}}const o=c.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),l=await I(o).catch(()=>[]);for(const a of l){if(!a.startsWith(e)||!a.endsWith(".lnk"))continue;const s=a.slice(0,-4);if(i.some(d=>d.serviceID===s))continue;let u=null;try{const d=k(s,t),g=(await S(d,"utf8")).match(/--config-dir\s+""([^""]+)""/);g&&(u=g[1])}catch{}i.push({serviceID:s,definitionPath:`startup:${s}`,configDir:u})}return i},async isServiceLoaded({serviceID:t,runCommand:r=f,homeDir:e=p.homedir()}){const n=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(n?.exitCode??1)===0?!0:v(M(t,e))}}}function At(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:i,stdoutPath:o,stderrPath:l,environmentPath:a="",homeDir:s=p.homedir(),runCommand:u=f}){const d=dt(t,s),h=N(i,o,l,s);return await P(c.dirname(d),{recursive:!0}),await $(d,yt({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:h,stdoutPath:o,stderrPath:l,environmentPath:a}),{encoding:"utf8",mode:384}),await u("systemctl",["--user","daemon-reload"]),await u("systemctl",["--user","enable",`${t}.service`]),{definitionPath:d}},async start({serviceID:t,runCommand:r=f}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=f}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=f}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=f}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await y(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=c.join(t,".config","systemd","user"),e=await I(r).catch(()=>[]),n=L("linux"),i=[];for(const o of e){if(!o.startsWith(n)||!o.endsWith(".service"))continue;const l=o.slice(0,-8),a=c.join(r,o);let s=null;try{const u=await S(a,"utf8");s=ut(u)}catch{}i.push({serviceID:l,definitionPath:a,configDir:s})}return i},async isServiceLoaded({serviceID:t,runCommand:r=f}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function C(t){try{const r=V(t,"utf8"),e=JSON.parse(r);if(e&&typeof e.service_id=="string"&&typeof e.node_path=="string"&&typeof e.cli_path=="string"&&typeof e.root_dir=="string")return e}catch{}return null}function Nt(t){return c.join(t,"daemon.lock.json")}function W(t){const r=Nt(t);try{const e=V(r,"utf8"),n=JSON.parse(e);if(typeof n?.pid=="number")return n.pid}catch{}return 0}function Z(t){return c.join(t,".profile")}function Wt(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function _t(t){const r=[D(t.nodePath),D(t.cliPath)];t.configDir&&r.push("--config-dir",D(t.configDir));const e=`env ${T}=${D(t.rootDir)} ${r.join(" ")}`;return`${e} status >/dev/null 2>&1 || (nohup ${e} start >/dev/null 2>&1 &)`}function Ot(t,r){return`# >>> grix-connector autostart [${t}] >>>
|
|
102
|
+
${r}
|
|
103
103
|
# <<< grix-connector autostart [${t}] <<<
|
|
104
|
-
`}function tt(t){const
|
|
105
|
-
${i}`).replace(/^\n+/,"");else{const a=
|
|
104
|
+
`}function tt(t){const r=Wt(t);return new RegExp(`(?:^|\\n)# >>> grix-connector autostart \\[${r}\\] >>>\\n[\\s\\S]*?\\n# <<< grix-connector autostart \\[${r}\\] <<<\\n?`,"m")}async function jt(t,r,e){let n="";try{n=await S(t,"utf8")}catch{}const i=Ot(r,e),o=tt(r);let l;if(o.test(n))l=n.replace(o,`
|
|
105
|
+
${i}`).replace(/^\n+/,"");else{const a=n.length===0||n.endsWith(`
|
|
106
106
|
`)?"":`
|
|
107
|
-
`;l=`${
|
|
108
|
-
`,{encoding:"utf8",mode:384});const h=_t({nodePath:
|
|
107
|
+
`;l=`${n}${a}${i}`}l!==n&&(await P(c.dirname(t),{recursive:!0}),await $(t,l,{encoding:"utf8"}))}async function Mt(t,r){let e="";try{e=await S(t,"utf8")}catch{return}const n=tt(r);if(!n.test(e))return;const i=e.replace(n,"").replace(/^\n+/,"");await $(t,i,{encoding:"utf8"})}function Ut(){return{platform:"linux",kind:"bare-daemon",async install({serviceID:t,nodePath:r,cliPath:e,configDir:n,rootDir:i,stdoutPath:o,stderrPath:l,homeDir:a=p.homedir()}){const s=x(t,a);await P(c.dirname(s),{recursive:!0});const u=N(i,o,l,a),d={schema_version:1,service_id:t,node_path:r,cli_path:e,config_dir:n??"",root_dir:u,stdout_path:o,stderr_path:l,installed_at:Date.now()};await $(s,`${JSON.stringify(d,null,2)}
|
|
108
|
+
`,{encoding:"utf8",mode:384});const h=_t({nodePath:r,cliPath:e,configDir:n??"",rootDir:u});return await jt(Z(a),t,h),{definitionPath:s}},async start({serviceID:t,homeDir:r=p.homedir()}){const e=x(t,r),n=C(e);if(!n)throw new Error(`bare-daemon marker missing: ${e}`);const i=[n.cli_path];n.config_dir&&i.push("--config-dir",n.config_dir),_(n.node_path,i,{env:{...process.env,[T]:n.root_dir}})},async stop({serviceID:t,homeDir:r=p.homedir()}){const e=x(t,r),n=C(e);if(!n)return;const i=W(n.root_dir);if(i>0&&R(i))try{process.kill(i,"SIGTERM")}catch{}},async restart(t){await this.stop(t);const r=Date.now()+5e3,e=x(t.serviceID,t.homeDir??p.homedir()),n=C(e);for(;n&&Date.now()<r;){const i=W(n.root_dir);if(i<=0||!R(i))break;await new Promise(o=>setTimeout(o,100))}await this.start(t)},async uninstall({serviceID:t,homeDir:r=p.homedir()}){const e=x(t,r),n=C(e);if(n){const i=W(n.root_dir);if(i>0&&R(i))try{process.kill(i,"SIGTERM")}catch{}}await y(e,{force:!0}),await Mt(Z(r),t)},async discoverServices({homeDir:t=p.homedir()}={}){const r=c.join(t,".grix","service"),e=await I(r).catch(()=>[]),n=L("linux"),i=[];for(const o of e){if(!o.startsWith(n)||!o.endsWith(".bare.json"))continue;const l=o.slice(0,-10),a=c.join(r,o),s=C(a);i.push({serviceID:l,definitionPath:a,configDir:s?.config_dir||null})}return i},async isServiceLoaded({serviceID:t,homeDir:r=p.homedir()}){const e=x(t,r),n=C(e);if(!n)return!1;const i=W(n.root_dir);return i>0&&R(i)}}}function Bt(t=process.getuid?.()??0){if(process.env.GRIX_FORCE_BARE_DAEMON==="1")return!1;const r=process.env.XDG_RUNTIME_DIR;return!!(r&&v(c.join(r,"bus"))||v(`/run/user/${t}/bus`))}function Yt(t=process.platform,r={}){return t==="darwin"?Ft():t==="win32"?Dt():r.systemdUserAvailable??Bt()?At():Ut()}export{kt as WINDOWS_WRAPPER_TEMPLATE_MARKER,K as WINDOWS_WRAPPER_TEMPLATE_VERSION,Ct as buildWindowsWrapperScript,j as collectServicePortEnv,Yt as getPlatformServiceAdapter,bt as isInsideManagedDaemonTree,Bt as isSystemdUserBusAvailable,Mt as removeProfileAutostart,M as resolveWindowsStartupShortcutPath,k as resolveWindowsWrapperPath,Tt as resolveWindowsWrapperTemplateSource,jt as upsertProfileAutostart};
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import{createHash as R}from"node:crypto";import l from"node:fs";import B from"node:os";import n from"node:path";import f from"node:process";import{mkdir as y}from"node:fs/promises";import{setTimeout as N}from"node:timers/promises";import{resolveRuntimePaths as w}from"../core/config/paths.js";import{inspectDaemonState as S}from"../runtime/service-state.js";import{isProcessRunning as M,runCommand as m,terminateProcessTree as W,waitForProcessExit as q}from"./process-control.js";import{isLockHolderIdentityVerified as C,readDaemonLock as _}from"../runtime/daemon-lock.js";import{ServiceInstallStore as O}from"./service-install-store.js";import{getPlatformServiceAdapter as U}from"./platform-adapter.js";import{captureLogCursor as v,formatLogDelta as L}from"./log-diagnostics.js";import{LAUNCHER_FILE as j,bootstrapVersionFromPackage as V,compareVersions as z,ensureLauncher as G,launcherPath as A,packagedLauncherPath as J,readLauncherTemplateVersion as X,readPackageVersionAt as E,readPointer as I,switchCurrent as K,versionEntry as $}from"../core/upgrade/version-store.js";import{buildServiceID as P,parseExecStartWords as Q,parseProgramArgumentsFromPlistXML as Y,resolveLinuxUserUnitPath as Z,resolveMacOSLaunchAgentPath as tt,resolveWindowsServiceRuntimeDir as et,resolveServiceInstallRecordPath as it,resolveServiceStderrPath as rt,resolveServiceStdoutPath as at}from"./service-paths.js";import{resolveWindowsStartupShortcutPath as st,resolveWindowsWrapperPath as b}from"./platform-adapter.js";import{readInstalledWrapperTemplate as ot}from"./windows-wrapper-migration.js";import{appendLegacyHealLog as nt,isLauncherStoreUsable as H,parseWrapperCliPath as ct,planLegacyStateHeal as dt}from"./legacy-state-heal.js";function lt(F){try{return l.realpathSync(F)}catch{return F}}class jt{adapter;platform;homeDir;uid;nodePath;cliPath;waitForExit;terminateTree;constructor(e){this.platform=e.platform??f.platform,this.homeDir=e.homeDir??B.homedir(),this.uid=e.uid??f.getuid?.()??0,this.nodePath=e.nodePath??f.execPath,this.cliPath=lt(e.cliPath),this.adapter=U(this.platform),this.waitForExit=e.processControl?.waitForExit??q,this.terminateTree=e.processControl?.terminateTree??W}normalizeRootDir(e){return n.resolve(String(e??"").trim())}resolveLocalServiceBaseDir(){if(this.platform==="darwin")return n.join(this.homeDir,"Library","Application Support","grix-connector");if(this.platform==="win32"){const e=String(f.env.LOCALAPPDATA??"").trim();return n.join(e&&n.isAbsolute(e)?e:n.join(this.homeDir,"AppData","Local"),"grix-connector")}return null}resolveServiceRuntimeDir(e){const t=this.resolveLocalServiceBaseDir();return t?this.platform==="win32"?et(this.normalizeRootDir(e),f.env,this.homeDir):n.join(t,"runtime"):null}resolveServiceCliPath(e){const t=this.resolveServiceRuntimeDir(e);return t?A(t):this.cliPath}resolveSourcePackageDir(){return n.dirname(n.dirname(this.cliPath))}async adoptLauncher(e){const t=this.normalizeRootDir(e),r=this.resolveServiceRuntimeDir(t);if(!r)return!1;const i=await this.createStore(t).load();return!i||i.cli_path===this.resolveServiceCliPath(t)&&H(r)?!1:(await this.refreshDescriptor(t,i),!0)}prepareServiceRuntime(e){const t=this.resolveServiceRuntimeDir(e);if(!t)return;const r=this.resolveSourcePackageDir();l.mkdirSync(t,{recursive:!0});const i=E(r);if(!i)throw new Error(`Cannot read package version at ${r} (entry ${this.cliPath}); refusing to install the service`);if(!G(t,r))throw new Error(`Package at ${r} does not ship ${j}; refusing to point the service at it`);const a=I(t,"current");if((!a||!l.existsSync($(t,a))||z(i,a)>0)&&(V(t,r,i),K(t,i)),this.platform==="win32"||this.platform==="darwin"){const s=n.join(r,"scripts","upgrade-guardian.mjs");if(l.existsSync(s)){const o=n.join(this.normalizeRootDir(e),"bin"),d=n.join(o,"upgrade-guardian.mjs"),c=n.join(o,"upgrade-guardian.fallback.mjs"),h=`${d}.tmp-${f.pid}`;l.mkdirSync(o,{recursive:!0});try{l.copyFileSync(s,h),l.renameSync(h,d),l.copyFileSync(s,c)}finally{l.rmSync(h,{force:!0})}}if(this.platform==="darwin"){const o=n.join(r,"scripts","upgrade-guardian.sh");if(l.existsSync(o)){const d=n.join(this.normalizeRootDir(e),"bin","upgrade-guardian.sh"),c=`${d}.tmp-${f.pid}`;try{l.copyFileSync(o,c),l.chmodSync(c,493),l.renameSync(c,d)}finally{l.rmSync(c,{force:!0})}}}}}buildDescriptor(e,t,r=""){const i=this.normalizeRootDir(e),a=this.resolveServiceCliPath(i);return{schema_version:1,platform:this.platform,service_id:P(i,this.platform),node_path:this.nodePath,cli_path:a,cli_hash:this.computeServiceHash(a)??"",definition_path:r,config_dir:t??"",installed_at:0,updated_at:0}}createStore(e){return new O(it(this.normalizeRootDir(e)))}toAdapterPayload(e,t){return{serviceID:e.service_id,nodePath:e.node_path,cliPath:e.cli_path,configDir:e.config_dir||void 0,rootDir:this.normalizeRootDir(t),environmentPath:this.buildServiceEnvironmentPath(e.node_path)}}async resolveServiceLogPaths(e){const t=this.resolveLocalServiceBaseDir();if(t){const r=n.join(this.platform==="darwin"?n.join(this.homeDir,"Library","Logs","grix-connector"):n.join(t,"logs"),P(this.normalizeRootDir(e),this.platform));return await y(r,{recursive:!0}),{stdoutPath:n.join(r,"daemon.out.log"),stderrPath:n.join(r,"daemon.err.log")}}return await y(n.join(e,"service"),{recursive:!0}),{stdoutPath:at(e),stderrPath:rt(e)}}buildServiceEnvironmentPath(e){const t=[n.dirname(e),...this.platform==="darwin"?["/opt/homebrew/bin"]:[],"/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],r=String(f.env.PATH??"").split(n.delimiter).filter(s=>s&&n.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),i=[],a=new Set;for(const s of[...t,...r]){if(a.has(s))continue;if(a.add(s),[...i,s].join(n.delimiter).length>2048)break;i.push(s)}return i.join(n.delimiter)}toStartParams(e){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:m}}toStopParams(e){return{serviceID:e.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:m}}toRestartParams(e,t={}){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:m,reloadDefinition:t.reloadDefinition===!0}}toUninstallParams(e){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:m}}async loadDescriptor(e){const t=this.createStore(e),r=await t.load();if(!r)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C grix-connector start\uFF08\u4F1A\u81EA\u52A8\u5B89\u88C5\u5E76\u542F\u52A8\u670D\u52A1\uFF09\u3002");return{store:t,descriptor:r}}resolveServiceNodePath(e){return e.node_path&&l.existsSync(e.node_path)?e.node_path:this.nodePath}isDescriptorCurrent(e,t){if(t.node_path!==this.resolveServiceNodePath(t)||t.cli_path!==this.resolveServiceCliPath(e)||!t.cli_hash)return!1;const r=this.computeServiceHash(t.cli_path),i=this.computeServiceHash(this.cliPath);return r!==null&&i!==null&&r===t.cli_hash&&i===t.cli_hash}computeFileHash(e){try{const t=l.readFileSync(e);return R("sha256").update(t).digest("hex")}catch{return null}}computeServiceHash(e){return this.resolveLocalServiceBaseDir()?this.computeLauncherHash(e):this.computeFileHash(e)}computeLauncherHash(e){try{const t=this.resolveSourcePackageDir(),r=E(t)??"",i=n.basename(e)===j,a=i?n.dirname(e):null,s=i?e:J(t);if(!s)return null;const o=X(s);if(o<0)return null;let d=!0;if(a){const c=I(a,"current");d=!!c&&l.existsSync($(a,c))&&z(c,r)>=0}return d?R("sha256").update(`launcher:${o}`).update(`source:${r}`).digest("hex"):null}catch{return null}}computeRuntimeDirHash(e){try{const t=n.dirname(e),r=R("sha256"),i=s=>{const o=l.readdirSync(s,{withFileTypes:!0}).sort((d,c)=>d.name.localeCompare(c.name));for(const d of o){const c=n.join(s,d.name);r.update(n.relative(t,c)),d.isDirectory()?i(c):d.isFile()&&r.update(l.readFileSync(c))}};i(t);const a=n.join(n.dirname(t),"package.json");return l.existsSync(a)&&(r.update("package.json"),r.update(l.readFileSync(a))),r.digest("hex")}catch{return null}}async refreshDescriptor(e,t){const r=this.normalizeRootDir(e);await y(n.join(r,"service"),{recursive:!0}),this.prepareServiceRuntime(r);const i=this.resolveServiceCliPath(r),a={...t,platform:this.platform,service_id:t.service_id||P(r,this.platform),node_path:this.resolveServiceNodePath(t),cli_path:i,cli_hash:this.computeServiceHash(i)??"",config_dir:t.config_dir},s=await this.resolveServiceLogPaths(r),o=await this.adapter.install({...this.toAdapterPayload(a,r),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:m}),d={...a,installed_at:t.installed_at,definition_path:o?.definitionPath||a.definition_path,updated_at:Date.now()};return await this.createStore(r).save(d),d}async resolveActiveDescriptor(e){return(await this.resolveActiveDescriptorState(e)).descriptor}async resolveActiveDescriptorState(e){const{descriptor:t}=await this.loadDescriptor(e);return this.isDescriptorCurrent(e,t)?{descriptor:t,definitionRewritten:!1}:{descriptor:await this.refreshDescriptor(e,t),definitionRewritten:!0}}async discoverConflictingServices(e){const t=this.normalizeRootDir(e),r=P(t,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:m})).filter(a=>a.serviceID!==r)}async cleanupConflictingServices(e){const t=await this.discoverConflictingServices(e);if(t.length>0){const i=t.map(a=>a.serviceID).join(", ");console.log(`\u53D1\u73B0 ${t.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${i}`)}let r=0;for(const i of t){if(await this.adapter.isServiceLoaded({serviceID:i.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:m})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${i.serviceID}`);continue}try{await this.adapter.stop({serviceID:i.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:m})}catch{}try{await this.adapter.uninstall({serviceID:i.serviceID,definitionPath:i.definitionPath,uid:this.uid,homeDir:this.homeDir,runCommand:m}),r++}catch{}}return r}captureDaemonBaseline(e){return{status:S(e.daemonStatusFile),lock:_(e.daemonLockFile)}}isDaemonReady(e,t,r){const i=S(e.daemonStatusFile),a=_(e.daemonLockFile);if(!i.running||i.state!=="running"||i.pid<=0||!a||a.pid!==i.pid||!M(a.pid)||typeof a.root_dir!="string"||n.resolve(a.root_dir)!==n.resolve(e.rootDir)||typeof a.instance_id!="string"||a.instance_id.length===0||i.instance_id!==a.instance_id)return!1;const s=i.pid!==t.status.pid||i.state!==t.status.state||i.updated_at!==t.status.updated_at||i.instance_id!==t.status.instance_id,o=!t.lock||a.pid!==t.lock.pid||a.acquired_at!==t.lock.acquired_at||a.instance_id!==t.lock.instance_id,d=s&&o,c=t.status.state==="starting"&&t.status.instance_id===a.instance_id&&t.lock?.pid===a.pid&&t.lock.instance_id===a.instance_id&&i.instance_id===a.instance_id&&i.updated_at!==t.status.updated_at,h=t.lock?.pid===a.pid&&t.lock.instance_id===a.instance_id&&t.lock.acquired_at===a.acquired_at&&(t.status.pid!==a.pid||t.status.instance_id!==a.instance_id||t.status.state!=="running")&&i.pid===a.pid&&i.instance_id===a.instance_id&&i.state==="running"&&i.updated_at!==t.status.updated_at;return!C(a)&&!d&&!c&&!h?!1:r?d||c||h:!0}canForceTerminateDaemon(e,t){const r=_(e.daemonLockFile);return t>0&&r!==null&&r.pid===t&&typeof r.root_dir=="string"&&n.resolve(r.root_dir)===n.resolve(e.rootDir)&&C(r)}defaultDaemonStartTimeoutMs(){return this.platform==="darwin"?5e4:2e4}async waitForDaemonStarted(e,t={}){const r=t.timeoutMs??this.defaultDaemonStartTimeoutMs(),i=t.baseline??this.captureDaemonBaseline(e),a=t.requireNewDaemon??!1,s=t.stdoutLogFile??e.stdoutLogFile,o=t.stderrLogFile??e.stderrLogFile,d=t.stdoutLogCursor??v(s),c=t.stderrLogCursor??v(o),h=Date.now();for(;Date.now()-h<r;){if(this.isDaemonReady(e,i,a))return;const g=S(e.daemonStatusFile);if(g.state==="failed"&&(i.status.state!=="failed"||g.pid!==i.status.pid||g.updated_at!==i.status.updated_at||g.instance_id!==i.status.instance_id)){const x=_(e.daemonLockFile),T=["daemon start failed",`status=${JSON.stringify(g)}`,`lock=${x?JSON.stringify(x):"missing"}`,L("stderr",o,c),L("stdout",s,d)].filter(Boolean);throw new Error(T.join(`
|
|
2
|
-
`))}await
|
|
3
|
-
`))}readDefinitionCliPath(e,t){const r=t.service_id||P(e,this.platform),i=a=>{try{return l.readFileSync(a,"utf8")}catch{return null}};if(this.adapter.kind==="launchd"){const a=i(t.definition_path||tt(r,this.homeDir));return a?Y(a)[1]??null:null}if(this.adapter.kind==="systemd-user"){const a=i(t.definition_path||Z(r,this.homeDir));return a?Q(a)[1]??null:null}if(this.adapter.kind==="task-scheduler"){const a=i(b(r,this.homeDir));return a?ct(a):null}return null}observeLegacyState(e,t){const r=this.resolveServiceRuntimeDir(e),i=t?.service_id||P(e,this.platform);return{platform:this.platform,record:t,expectedLauncherPath:r?A(r):null,launcherStoreUsable:r?H(r):!1,nodePathExists:!!t?.node_path&&l.existsSync(t.node_path),cliPathExists:!!t?.cli_path&&l.existsSync(t.cli_path),definitionCliPath:t?this.readDefinitionCliPath(e,t):null,upgradePending:l.existsSync(n.join(e,"data","upgrade-pending.json")),windowsWrapperTemplate:this.platform==="win32"?ot(b(i,this.homeDir)):-1,windowsStartupShortcutExists:this.platform==="win32"&&l.existsSync(st(i,this.homeDir))}}async healLegacyState(e){const t=this.normalizeRootDir(e.rootDir),r=this.createStore(t),i=await r.load(),a=dt(this.observeLegacyState(t,i));if(!a.heal||!i)return{healed:!1,reasons:[]};const s=o=>{nt(t,o),console.log(`\u9057\u7559\u72B6\u6001\u81EA\u6108: ${o}`)};for(const o of a.reasons)s(`detected ${o}`);try{await this.stopServiceAndDaemon(t,i),s("stopped the legacy service and daemon")}catch(o){s(`stop failed (continuing): ${o instanceof Error?o.message:o}`)}try{await this.adapter.uninstall(this.toUninstallParams(i)),s("removed the legacy service definition")}catch(o){s(`uninstall failed (continuing): ${o instanceof Error?o.message:o}`)}return await r.clear(),s("cleared the install record; rebuilding via install"),{healed:!0,reasons:a.reasons,configDir:i.config_dir||void 0}}async install(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t),await y(n.join(t,"service"),{recursive:!0}),this.prepareServiceRuntime(t);const r=w(t),i=this.captureDaemonBaseline(r),a=this.buildDescriptor(t,e.configDir),s=await this.resolveServiceLogPaths(t),o=v(s.stdoutPath),d=v(s.stderrPath),c=await this.adapter.isServiceLoaded({serviceID:a.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:m}),h=await this.adapter.install({...this.toAdapterPayload(a,t),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:m}),p={...a,definition_path:h?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};return await this.createStore(t).save(p),c?await this.adapter.restart(this.toRestartParams(p,{reloadDefinition:!0})):await this.adapter.start(this.toStartParams(p)),await this.waitForDaemonStarted(r,{baseline:i,requireNewDaemon:!0,stdoutLogFile:s.stdoutPath,stderrLogFile:s.stderrPath,stdoutLogCursor:o,stderrLogCursor:d}),this.status({rootDir:t})}async start(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t);const r=w(t),i=this.captureDaemonBaseline(r),a=i.status,s=v(r.stdoutLogFile),o=v(r.stderrLogFile),c=await this.createStore(t).load();if(a.running&&c&&this.isDescriptorCurrent(t,c)){if(a.state==="starting"){const u=i.lock;if(u!==null&&u.pid===a.pid&&typeof u.root_dir=="string"&&n.resolve(u.root_dir)===n.resolve(r.rootDir)&&typeof u.instance_id=="string"&&u.instance_id.length>0&&a.instance_id===u.instance_id&&C(u)){const k=await this.resolveServiceLogPaths(t);return await this.waitForDaemonStarted(r,{baseline:i,stdoutLogFile:k.stdoutPath,stderrLogFile:k.stderrPath,stdoutLogCursor:s,stderrLogCursor:o}),this.status({rootDir:t})}}if(this.isDaemonReady(r,i,!1))return this.status({rootDir:t})}let h,p=!1;if(c)this.isDescriptorCurrent(t,c)&&a.running?h=c:(h=await this.refreshDescriptor(t,c),p=!0);else{const u=await this.resolveActiveDescriptorState(t);h=u.descriptor,p=u.definitionRewritten}const D=await this.resolveServiceLogPaths(t);try{a.running?await this.adapter.restart(this.toRestartParams(h,{reloadDefinition:p})):await this.adapter.start(this.toStartParams(h))}catch(u){if(u instanceof Error&&u.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(h,t),stdoutPath:D.stdoutPath,stderrPath:D.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:m}),await this.adapter.start(this.toStartParams(h));else throw u}return await this.waitForDaemonStarted(r,{baseline:i,requireNewDaemon:!0,stdoutLogFile:D.stdoutPath,stderrLogFile:D.stderrPath,stdoutLogCursor:s,stderrLogCursor:o}),this.status({rootDir:t})}async stopServiceAndDaemon(e,t){const r=w(e),i=S(r.daemonStatusFile);await this.adapter.stop(this.toStopParams(t)),!(!i.running||!i.pid||await this.waitForExit(i.pid,{timeoutMs:5e3}))&&this.canForceTerminateDaemon(r,i.pid)&&(await this.terminateTree(i.pid,{platform:this.platform,runCommandImpl:m}),await this.waitForExit(i.pid,{timeoutMs:5e3}))}async stop(e){const t=this.normalizeRootDir(e.rootDir),{descriptor:r}=await this.loadDescriptor(t);return await this.stopServiceAndDaemon(t,r),this.status({rootDir:t})}async restart(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t);const{descriptor:r,definitionRewritten:i}=await this.resolveActiveDescriptorState(t),a=w(t),s=this.captureDaemonBaseline(a),o=await this.resolveServiceLogPaths(t),d=v(o.stdoutPath),c=v(o.stderrPath);try{await this.adapter.restart(this.toRestartParams(r,{reloadDefinition:i}))}catch(h){if(h instanceof Error&&h.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(r,t),stdoutPath:o.stdoutPath,stderrPath:o.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:m}),await this.adapter.start(this.toStartParams(r));else throw h}return await this.waitForDaemonStarted(a,{baseline:s,requireNewDaemon:!0,stdoutLogFile:o.stdoutPath,stderrLogFile:o.stderrPath,stdoutLogCursor:d,stderrLogCursor:c}),this.status({rootDir:t})}async uninstall(e){const t=this.normalizeRootDir(e.rootDir),r=this.createStore(t),i=await r.load();return i&&(await this.adapter.uninstall(this.toUninstallParams(i)),await r.clear()),this.status({rootDir:t})}async status(e){const t=this.normalizeRootDir(e.rootDir),i=await this.createStore(t).load(),a=w(t),s=S(a.daemonStatusFile);return i?{installed:!0,install_state:this.isDescriptorCurrent(t,i)?"current":"stale",service_kind:this.adapter.kind,service_id:i.service_id,definition_path:i.definition_path,root_dir:t,daemon_state:s.state,pid:s.pid,connection_state:s.connection_state,updated_at:s.updated_at,reason:s.reason}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:t,daemon_state:s.state,pid:s.pid,reason:s.reason}}}export{jt as ServiceManager};
|
|
1
|
+
import{createHash as A}from"node:crypto";import l from"node:fs";import O from"node:os";import n from"node:path";import S from"node:process";import{mkdir as F}from"node:fs/promises";import{setTimeout as j}from"node:timers/promises";import{resolveRuntimePaths as y}from"../core/config/paths.js";import{inspectDaemonState as w}from"../runtime/service-state.js";import{isProcessRunning as E,runCommand as f,terminateProcessTree as U,waitForProcessExit as V}from"./process-control.js";import{isLockHolderIdentityVerified as R,readDaemonLock as L}from"../runtime/daemon-lock.js";import{ServiceInstallStore as J}from"./service-install-store.js";import{getPlatformServiceAdapter as X}from"./platform-adapter.js";import{captureLogCursor as _,formatLogDelta as $}from"./log-diagnostics.js";import{LAUNCHER_FILE as z,bootstrapVersionFromPackage as Y,compareVersions as I,ensureLauncher as K,launcherPath as T,packagedLauncherPath as Q,readLauncherTemplateVersion as Z,readPackageVersionAt as M,readPointer as H,switchCurrent as tt,versionEntry as N}from"../core/upgrade/version-store.js";import{buildServiceID as P,parseExecStartWords as et,parseProgramArgumentsFromPlistXML as it,resolveLinuxUserUnitPath as rt,resolveMacOSLaunchAgentPath as at,resolveWindowsServiceRuntimeDir as st,resolveServiceInstallRecordPath as ot,resolveServiceStderrPath as nt,resolveServiceStdoutPath as ct}from"./service-paths.js";import{resolveWindowsStartupShortcutPath as dt,resolveWindowsWrapperPath as b}from"./platform-adapter.js";import{readInstalledWrapperTemplate as lt}from"./windows-wrapper-migration.js";import{appendLegacyHealLog as ht,isLauncherStoreUsable as B,parseWrapperCliPath as ut,planLegacyStateHeal as mt}from"./legacy-state-heal.js";const pt=6e4;function ft(u=S.env){const e=String(u.GRIX_DAEMON_READY_TIMEOUT_MS??"").trim();return/^\d+$/.test(e)?Number(e):pt}function G(u,e,t){const i=e.pid!==u.status.pid||e.state!==u.status.state||e.updated_at!==u.status.updated_at||e.instance_id!==u.status.instance_id,r=!u.lock||t.pid!==u.lock.pid||t.acquired_at!==u.lock.acquired_at||t.instance_id!==u.lock.instance_id;return i&&r}function Dt(u){return u?`lock holder pid ${u.pid} ${E(u.pid)?"alive":"dead"}`:"lock holder: none"}function vt(u){try{return l.realpathSync(u)}catch{return u}}class Mt{adapter;platform;homeDir;uid;nodePath;cliPath;waitForExit;terminateTree;constructor(e){this.platform=e.platform??S.platform,this.homeDir=e.homeDir??O.homedir(),this.uid=e.uid??S.getuid?.()??0,this.nodePath=e.nodePath??S.execPath,this.cliPath=vt(e.cliPath),this.adapter=X(this.platform),this.waitForExit=e.processControl?.waitForExit??V,this.terminateTree=e.processControl?.terminateTree??U}normalizeRootDir(e){return n.resolve(String(e??"").trim())}resolveLocalServiceBaseDir(){if(this.platform==="darwin")return n.join(this.homeDir,"Library","Application Support","grix-connector");if(this.platform==="win32"){const e=String(S.env.LOCALAPPDATA??"").trim();return n.join(e&&n.isAbsolute(e)?e:n.join(this.homeDir,"AppData","Local"),"grix-connector")}return null}resolveServiceRuntimeDir(e){const t=this.resolveLocalServiceBaseDir();return t?this.platform==="win32"?st(this.normalizeRootDir(e),S.env,this.homeDir):n.join(t,"runtime"):null}resolveServiceCliPath(e){const t=this.resolveServiceRuntimeDir(e);return t?T(t):this.cliPath}resolveSourcePackageDir(){return n.dirname(n.dirname(this.cliPath))}async adoptLauncher(e){const t=this.normalizeRootDir(e),i=this.resolveServiceRuntimeDir(t);if(!i)return!1;const r=await this.createStore(t).load();return!r||r.cli_path===this.resolveServiceCliPath(t)&&B(i)?!1:(await this.refreshDescriptor(t,r),!0)}prepareServiceRuntime(e){const t=this.resolveServiceRuntimeDir(e);if(!t)return;const i=this.resolveSourcePackageDir();l.mkdirSync(t,{recursive:!0});const r=M(i);if(!r)throw new Error(`Cannot read package version at ${i} (entry ${this.cliPath}); refusing to install the service`);if(!K(t,i))throw new Error(`Package at ${i} does not ship ${z}; refusing to point the service at it`);const a=H(t,"current");if((!a||!l.existsSync(N(t,a))||I(r,a)>0)&&(Y(t,i,r),tt(t,r)),this.platform==="win32"||this.platform==="darwin"){const s=n.join(i,"scripts","upgrade-guardian.mjs");if(l.existsSync(s)){const o=n.join(this.normalizeRootDir(e),"bin"),d=n.join(o,"upgrade-guardian.mjs"),c=n.join(o,"upgrade-guardian.fallback.mjs"),h=`${d}.tmp-${S.pid}`;l.mkdirSync(o,{recursive:!0});try{l.copyFileSync(s,h),l.renameSync(h,d),l.copyFileSync(s,c)}finally{l.rmSync(h,{force:!0})}}if(this.platform==="darwin"){const o=n.join(i,"scripts","upgrade-guardian.sh");if(l.existsSync(o)){const d=n.join(this.normalizeRootDir(e),"bin","upgrade-guardian.sh"),c=`${d}.tmp-${S.pid}`;try{l.copyFileSync(o,c),l.chmodSync(c,493),l.renameSync(c,d)}finally{l.rmSync(c,{force:!0})}}}}}buildDescriptor(e,t,i=""){const r=this.normalizeRootDir(e),a=this.resolveServiceCliPath(r);return{schema_version:1,platform:this.platform,service_id:P(r,this.platform),node_path:this.nodePath,cli_path:a,cli_hash:this.computeServiceHash(a)??"",definition_path:i,config_dir:t??"",installed_at:0,updated_at:0}}createStore(e){return new J(ot(this.normalizeRootDir(e)))}toAdapterPayload(e,t){return{serviceID:e.service_id,nodePath:e.node_path,cliPath:e.cli_path,configDir:e.config_dir||void 0,rootDir:this.normalizeRootDir(t),environmentPath:this.buildServiceEnvironmentPath(e.node_path)}}async resolveServiceLogPaths(e){const t=this.resolveLocalServiceBaseDir();if(t){const i=n.join(this.platform==="darwin"?n.join(this.homeDir,"Library","Logs","grix-connector"):n.join(t,"logs"),P(this.normalizeRootDir(e),this.platform));return await F(i,{recursive:!0}),{stdoutPath:n.join(i,"daemon.out.log"),stderrPath:n.join(i,"daemon.err.log")}}return await F(n.join(e,"service"),{recursive:!0}),{stdoutPath:ct(e),stderrPath:nt(e)}}buildServiceEnvironmentPath(e){const t=[n.dirname(e),...this.platform==="darwin"?["/opt/homebrew/bin"]:[],"/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],i=String(S.env.PATH??"").split(n.delimiter).filter(s=>s&&n.isAbsolute(s)).filter(s=>!s.includes("/node_modules/.bin")).filter(s=>!s.includes("/node-gyp-bin")),r=[],a=new Set;for(const s of[...t,...i]){if(a.has(s))continue;if(a.add(s),[...r,s].join(n.delimiter).length>2048)break;r.push(s)}return r.join(n.delimiter)}toStartParams(e){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:f}}toStopParams(e){return{serviceID:e.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:f}}toRestartParams(e,t={}){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:f,reloadDefinition:t.reloadDefinition===!0}}toUninstallParams(e){return{serviceID:e.service_id,definitionPath:e.definition_path,uid:this.uid,homeDir:this.homeDir,runCommand:f}}async loadDescriptor(e){const t=this.createStore(e),i=await t.load();if(!i)throw new Error("\u540E\u53F0\u670D\u52A1\u8FD8\u6CA1\u6709\u5B89\u88C5\uFF0C\u8BF7\u5148\u6267\u884C grix-connector start\uFF08\u4F1A\u81EA\u52A8\u5B89\u88C5\u5E76\u542F\u52A8\u670D\u52A1\uFF09\u3002");return{store:t,descriptor:i}}resolveServiceNodePath(e){return e.node_path&&l.existsSync(e.node_path)?e.node_path:this.nodePath}isDescriptorCurrent(e,t){if(t.node_path!==this.resolveServiceNodePath(t)||t.cli_path!==this.resolveServiceCliPath(e)||!t.cli_hash)return!1;const i=this.computeServiceHash(t.cli_path),r=this.computeServiceHash(this.cliPath);return i!==null&&r!==null&&i===t.cli_hash&&r===t.cli_hash}computeFileHash(e){try{const t=l.readFileSync(e);return A("sha256").update(t).digest("hex")}catch{return null}}computeServiceHash(e){return this.resolveLocalServiceBaseDir()?this.computeLauncherHash(e):this.computeFileHash(e)}computeLauncherHash(e){try{const t=this.resolveSourcePackageDir(),i=M(t)??"",r=n.basename(e)===z,a=r?n.dirname(e):null,s=r?e:Q(t);if(!s)return null;const o=Z(s);if(o<0)return null;let d=!0;if(a){const c=H(a,"current");d=!!c&&l.existsSync(N(a,c))&&I(c,i)>=0}return d?A("sha256").update(`launcher:${o}`).update(`source:${i}`).digest("hex"):null}catch{return null}}computeRuntimeDirHash(e){try{const t=n.dirname(e),i=A("sha256"),r=s=>{const o=l.readdirSync(s,{withFileTypes:!0}).sort((d,c)=>d.name.localeCompare(c.name));for(const d of o){const c=n.join(s,d.name);i.update(n.relative(t,c)),d.isDirectory()?r(c):d.isFile()&&i.update(l.readFileSync(c))}};r(t);const a=n.join(n.dirname(t),"package.json");return l.existsSync(a)&&(i.update("package.json"),i.update(l.readFileSync(a))),i.digest("hex")}catch{return null}}async refreshDescriptor(e,t){const i=this.normalizeRootDir(e);await F(n.join(i,"service"),{recursive:!0}),this.prepareServiceRuntime(i);const r=this.resolveServiceCliPath(i),a={...t,platform:this.platform,service_id:t.service_id||P(i,this.platform),node_path:this.resolveServiceNodePath(t),cli_path:r,cli_hash:this.computeServiceHash(r)??"",config_dir:t.config_dir},s=await this.resolveServiceLogPaths(i),o=await this.adapter.install({...this.toAdapterPayload(a,i),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:f}),d={...a,installed_at:t.installed_at,definition_path:o?.definitionPath||a.definition_path,updated_at:Date.now()};return await this.createStore(i).save(d),d}async resolveActiveDescriptor(e){return(await this.resolveActiveDescriptorState(e)).descriptor}async resolveActiveDescriptorState(e){const{descriptor:t}=await this.loadDescriptor(e);return this.isDescriptorCurrent(e,t)?{descriptor:t,definitionRewritten:!1}:{descriptor:await this.refreshDescriptor(e,t),definitionRewritten:!0}}async discoverConflictingServices(e){const t=this.normalizeRootDir(e),i=P(t,this.platform);return(await this.adapter.discoverServices({homeDir:this.homeDir,runCommand:f})).filter(a=>a.serviceID!==i)}async cleanupConflictingServices(e){const t=await this.discoverConflictingServices(e);if(t.length>0){const r=t.map(a=>a.serviceID).join(", ");console.log(`\u53D1\u73B0 ${t.length} \u4E2A\u6B8B\u7559\u670D\u52A1\uFF0C\u6B63\u5728\u68C0\u67E5: ${r}`)}let i=0;for(const r of t){if(await this.adapter.isServiceLoaded({serviceID:r.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:f})){console.log(`\u8DF3\u8FC7\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1: ${r.serviceID}`);continue}try{await this.adapter.stop({serviceID:r.serviceID,uid:this.uid,homeDir:this.homeDir,runCommand:f})}catch{}try{await this.adapter.uninstall({serviceID:r.serviceID,definitionPath:r.definitionPath,uid:this.uid,homeDir:this.homeDir,runCommand:f}),i++}catch{}}return i}captureDaemonBaseline(e){return{status:w(e.daemonStatusFile),lock:L(e.daemonLockFile)}}readMatchedGeneration(e,t){const i=w(e.daemonStatusFile),r=L(e.daemonLockFile);return!i.running||!t.includes(i.state)||i.pid<=0||!r||r.pid!==i.pid||!E(r.pid)||typeof r.root_dir!="string"||n.resolve(r.root_dir)!==n.resolve(e.rootDir)||typeof r.instance_id!="string"||r.instance_id.length===0||i.instance_id!==r.instance_id?null:{inspection:i,lock:r}}isDaemonReady(e,t,i){const r=this.readMatchedGeneration(e,["running"]);if(!r)return!1;const{inspection:a,lock:s}=r,o=G(t,a,s),d=t.status.state==="starting"&&t.status.instance_id===s.instance_id&&t.lock?.pid===s.pid&&t.lock.instance_id===s.instance_id&&a.instance_id===s.instance_id&&a.updated_at!==t.status.updated_at,c=t.lock?.pid===s.pid&&t.lock.instance_id===s.instance_id&&t.lock.acquired_at===s.acquired_at&&(t.status.pid!==s.pid||t.status.instance_id!==s.instance_id||t.status.state!=="running")&&a.pid===s.pid&&a.instance_id===s.instance_id&&a.state==="running"&&a.updated_at!==t.status.updated_at;return!o&&!d&&!c&&!R(s)?!1:i?o||d||c:!0}findStartedGeneration(e,t,i,r){const a=this.readMatchedGeneration(e,["starting","running"]);if(!a)return null;const{inspection:s,lock:o}=a,d=t.lock?.pid===o.pid&&t.lock.instance_id===o.instance_id&&t.lock.acquired_at===o.acquired_at&&(t.status.pid!==o.pid||t.status.instance_id!==o.instance_id)&&s.updated_at!==t.status.updated_at;let c=G(t,s,o)||d;if(!c&&!i){const h=`${o.pid}:${o.instance_id}`;let p=r.get(h);p===void 0&&(p=R(o),r.set(h,p)),c=p}return c?{pid:o.pid,instance_id:o.instance_id,state:s.state}:null}diedHoldingLock(e,t){if(E(t.pid))return!1;const i=L(e.daemonLockFile);return i?.pid===t.pid&&i.instance_id===t.instance_id}canForceTerminateDaemon(e,t){const i=L(e.daemonLockFile);return t>0&&i!==null&&i.pid===t&&typeof i.root_dir=="string"&&n.resolve(i.root_dir)===n.resolve(e.rootDir)&&R(i)}defaultDaemonStartTimeoutMs(){return this.platform==="darwin"?5e4:this.platform==="win32"?45e3:2e4}async waitForDaemonStarted(e,t={}){const i=t.timeoutMs??this.defaultDaemonStartTimeoutMs(),r=t.readyTimeoutMs??ft(),a=t.baseline??this.captureDaemonBaseline(e),s=t.requireNewDaemon??!1,o=t.stdoutLogFile??e.stdoutLogFile,d=t.stderrLogFile??e.stderrLogFile,c=t.stdoutLogCursor??_(o),h=t.stderrLogCursor??_(d),p=new Map,v=(D,k=w(e.daemonStatusFile))=>{const x=L(e.daemonLockFile),q=[D,`status=${JSON.stringify(k)}`,`lock=${x?JSON.stringify(x):"missing"}`,Dt(x),$("stderr",d,h),$("stdout",o,c)].filter(Boolean);return new Error(q.join(`
|
|
2
|
+
`))},m=()=>{const D=w(e.daemonStatusFile);if(D.state==="failed"&&(a.status.state!=="failed"||D.pid!==a.status.pid||D.updated_at!==a.status.updated_at||D.instance_id!==a.status.instance_id))throw v("daemon start failed",D)},C=Date.now();let g=null;for(;Date.now()-C<i;){if(this.isDaemonReady(e,a,s))return;if(m(),g=this.findStartedGeneration(e,a,s,p),g)break;await j(100)}if(!g)throw v(`daemon start timeout (${i}ms)`);const W=Date.now()+r;for(;;){if(this.isDaemonReady(e,a,s))return;m();const D=this.findStartedGeneration(e,a,s,p);if(D)g=D;else if(this.diedHoldingLock(e,g))throw v(`daemon exited during startup (pid ${g.pid})`);if(Date.now()>=W){if(D){const k=Math.round((Date.now()-C)/1e3);console.warn(`Warning: daemon \u4ECD\u5728\u542F\u52A8\u4E2D(pid ${D.pid}\uFF0C\u5DF2 ${k}s)\uFF0C\u7A0D\u540E\u7528 grix-connector status \u67E5\u770B`);return}throw v(`daemon start timeout (${i}ms + ${r}ms)`)}await j(100)}}readDefinitionCliPath(e,t){const i=t.service_id||P(e,this.platform),r=a=>{try{return l.readFileSync(a,"utf8")}catch{return null}};if(this.adapter.kind==="launchd"){const a=r(t.definition_path||at(i,this.homeDir));return a?it(a)[1]??null:null}if(this.adapter.kind==="systemd-user"){const a=r(t.definition_path||rt(i,this.homeDir));return a?et(a)[1]??null:null}if(this.adapter.kind==="task-scheduler"){const a=r(b(i,this.homeDir));return a?ut(a):null}return null}observeLegacyState(e,t){const i=this.resolveServiceRuntimeDir(e),r=t?.service_id||P(e,this.platform);return{platform:this.platform,record:t,expectedLauncherPath:i?T(i):null,launcherStoreUsable:i?B(i):!1,nodePathExists:!!t?.node_path&&l.existsSync(t.node_path),cliPathExists:!!t?.cli_path&&l.existsSync(t.cli_path),definitionCliPath:t?this.readDefinitionCliPath(e,t):null,upgradePending:l.existsSync(n.join(e,"data","upgrade-pending.json")),windowsWrapperTemplate:this.platform==="win32"?lt(b(r,this.homeDir)):-1,windowsStartupShortcutExists:this.platform==="win32"&&l.existsSync(dt(r,this.homeDir))}}async healLegacyState(e){const t=this.normalizeRootDir(e.rootDir),i=this.createStore(t),r=await i.load(),a=mt(this.observeLegacyState(t,r));if(!a.heal||!r)return{healed:!1,reasons:[]};const s=o=>{ht(t,o),console.log(`\u9057\u7559\u72B6\u6001\u81EA\u6108: ${o}`)};for(const o of a.reasons)s(`detected ${o}`);try{await this.stopServiceAndDaemon(t,r),s("stopped the legacy service and daemon")}catch(o){s(`stop failed (continuing): ${o instanceof Error?o.message:o}`)}try{await this.adapter.uninstall(this.toUninstallParams(r)),s("removed the legacy service definition")}catch(o){s(`uninstall failed (continuing): ${o instanceof Error?o.message:o}`)}return await i.clear(),s("cleared the install record; rebuilding via install"),{healed:!0,reasons:a.reasons,configDir:r.config_dir||void 0}}async install(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t),await F(n.join(t,"service"),{recursive:!0}),this.prepareServiceRuntime(t);const i=y(t),r=this.captureDaemonBaseline(i),a=this.buildDescriptor(t,e.configDir),s=await this.resolveServiceLogPaths(t),o=_(s.stdoutPath),d=_(s.stderrPath),c=await this.adapter.isServiceLoaded({serviceID:a.service_id,uid:this.uid,homeDir:this.homeDir,runCommand:f}),h=await this.adapter.install({...this.toAdapterPayload(a,t),stdoutPath:s.stdoutPath,stderrPath:s.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:f}),p={...a,definition_path:h?.definitionPath??"",installed_at:Date.now(),updated_at:Date.now()};return await this.createStore(t).save(p),c?await this.adapter.restart(this.toRestartParams(p,{reloadDefinition:!0})):await this.adapter.start(this.toStartParams(p)),await this.waitForDaemonStarted(i,{baseline:r,requireNewDaemon:!0,stdoutLogFile:s.stdoutPath,stderrLogFile:s.stderrPath,stdoutLogCursor:o,stderrLogCursor:d}),this.status({rootDir:t})}async start(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t);const i=y(t),r=this.captureDaemonBaseline(i),a=r.status,s=_(i.stdoutLogFile),o=_(i.stderrLogFile),c=await this.createStore(t).load();if(a.running&&c&&this.isDescriptorCurrent(t,c)){if(a.state==="starting"){const m=r.lock;if(m!==null&&m.pid===a.pid&&typeof m.root_dir=="string"&&n.resolve(m.root_dir)===n.resolve(i.rootDir)&&typeof m.instance_id=="string"&&m.instance_id.length>0&&a.instance_id===m.instance_id&&R(m)){const g=await this.resolveServiceLogPaths(t);return await this.waitForDaemonStarted(i,{baseline:r,stdoutLogFile:g.stdoutPath,stderrLogFile:g.stderrPath,stdoutLogCursor:s,stderrLogCursor:o}),this.status({rootDir:t})}}if(this.isDaemonReady(i,r,!1))return this.status({rootDir:t})}let h,p=!1;if(c)this.isDescriptorCurrent(t,c)&&a.running?h=c:(h=await this.refreshDescriptor(t,c),p=!0);else{const m=await this.resolveActiveDescriptorState(t);h=m.descriptor,p=m.definitionRewritten}const v=await this.resolveServiceLogPaths(t);try{a.running?await this.adapter.restart(this.toRestartParams(h,{reloadDefinition:p})):await this.adapter.start(this.toStartParams(h))}catch(m){if(m instanceof Error&&m.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(h,t),stdoutPath:v.stdoutPath,stderrPath:v.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:f}),await this.adapter.start(this.toStartParams(h));else throw m}return await this.waitForDaemonStarted(i,{baseline:r,requireNewDaemon:!0,stdoutLogFile:v.stdoutPath,stderrLogFile:v.stderrPath,stdoutLogCursor:s,stderrLogCursor:o}),this.status({rootDir:t})}async stopServiceAndDaemon(e,t){const i=y(e),r=w(i.daemonStatusFile);await this.adapter.stop(this.toStopParams(t)),!(!r.running||!r.pid||await this.waitForExit(r.pid,{timeoutMs:5e3}))&&this.canForceTerminateDaemon(i,r.pid)&&(await this.terminateTree(r.pid,{platform:this.platform,runCommandImpl:f}),await this.waitForExit(r.pid,{timeoutMs:5e3}))}async stop(e){const t=this.normalizeRootDir(e.rootDir),{descriptor:i}=await this.loadDescriptor(t);return await this.stopServiceAndDaemon(t,i),this.status({rootDir:t})}async restart(e){const t=this.normalizeRootDir(e.rootDir);await this.cleanupConflictingServices(t);const{descriptor:i,definitionRewritten:r}=await this.resolveActiveDescriptorState(t),a=y(t),s=this.captureDaemonBaseline(a),o=await this.resolveServiceLogPaths(t),d=_(o.stdoutPath),c=_(o.stderrPath);try{await this.adapter.restart(this.toRestartParams(i,{reloadDefinition:r}))}catch(h){if(h instanceof Error&&h.message.includes("plist missing"))await this.adapter.install({...this.toAdapterPayload(i,t),stdoutPath:o.stdoutPath,stderrPath:o.stderrPath,homeDir:this.homeDir,uid:this.uid,runCommand:f}),await this.adapter.start(this.toStartParams(i));else throw h}return await this.waitForDaemonStarted(a,{baseline:s,requireNewDaemon:!0,stdoutLogFile:o.stdoutPath,stderrLogFile:o.stderrPath,stdoutLogCursor:d,stderrLogCursor:c}),this.status({rootDir:t})}async uninstall(e){const t=this.normalizeRootDir(e.rootDir),i=this.createStore(t),r=await i.load();return r&&(await this.adapter.uninstall(this.toUninstallParams(r)),await i.clear()),this.status({rootDir:t})}async status(e){const t=this.normalizeRootDir(e.rootDir),r=await this.createStore(t).load(),a=y(t),s=w(a.daemonStatusFile);return r?{installed:!0,install_state:this.isDescriptorCurrent(t,r)?"current":"stale",service_kind:this.adapter.kind,service_id:r.service_id,definition_path:r.definition_path,root_dir:t,daemon_state:s.state,pid:s.pid,connection_state:s.connection_state,updated_at:s.updated_at,reason:s.reason}:{installed:!1,install_state:"missing",service_kind:this.adapter.kind,root_dir:t,daemon_state:s.state,pid:s.pid,reason:s.reason}}}export{Mt as ServiceManager};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "4.11.
|
|
3
|
+
"version": "4.11.14",
|
|
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",
|