grix-connector 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -4
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/bridge/bridge.js +4 -4
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/admin/admin-server.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/installer/installer.js +8 -8
- package/dist/core/installer/manual-guide.js +1 -1
- package/dist/core/installer/registry.js +1 -1
- package/dist/core/proxy/index.js +1 -1
- package/dist/core/proxy/proxy-manager.js +7 -7
- package/dist/core/proxy/relay-hosts.js +1 -0
- package/dist/core/proxy/relay-orchestration.js +1 -0
- package/dist/core/proxy/routing/registry.js +1 -1
- package/dist/core/proxy/server/mitm-proxy.js +8 -8
- package/dist/core/util/cli-probe.js +2 -2
- package/dist/grix.js +6 -6
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readJSONFile as o,writeJSONFileAtomic as
|
|
1
|
+
import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class y{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}async start(e){return new Promise((s,t)=>{this.server=u((r,n)=>this.handleRequest(r,n)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),s()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,s){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(s);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(n=>this.handleAdd(s,n)).catch(n=>this.error(s,n));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const n=decodeURIComponent(r.slice(12));this.handleRemove(s,n)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const n=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(s,n)}else if(r==="/api/reload"&&t==="POST")this.handleReload(s);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(s);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(s);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,s,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(s);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(n=>this.handleInstall(s,n)).catch(n=>this.error(s,n));else if(t==="GET"&&r.startsWith("/api/install/")){const n=decodeURIComponent(r.slice(13));this.handleInstallProgress(s,n)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(s);else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(n=>this.handleProxySetEnabled(s,n)).catch(n=>this.error(s,n));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(n=>this.handleProxySetDefaultRoute(s,n)).catch(n=>this.error(s,n));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(n=>this.handleProxySetInterceptHosts(s,n)).catch(n=>this.error(s,n));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const n=decodeURIComponent(r.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(s,n,o)).catch(o=>this.error(s,o))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const n=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(s,n)}else this.json(s,404,{error:"not_found"})}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(s){this.error(e,s)}}async handleProxySetRoute(e,s,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const n=await this.proxyHandler.setRoute(s,t);this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}handleProxyDeleteRoute(e,s){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(s).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetEnabled(e,s){if(!this.ensureProxy(e))return;const t=s;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const r=t.enabled?await this.proxyHandler.enable():await this.proxyHandler.disable();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,s){if(!this.ensureProxy(e))return;const r=s?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const n=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}async handleProxySetInterceptHosts(e,s){if(!this.ensureProxy(e))return;const t=s;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(s=>typeof s=="string")}handleList(e){try{const s=this.handler?.list()??[];this.json(e,200,s)}catch(s){this.error(e,s)}}async handleAdd(e,s){try{const t=await this.handler.add(s);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,s){this.handler.remove(s).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,s){this.handler.restart(s).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(s=>{this.json(e,200,{ok:!0,result:s})}).catch(s=>this.error(e,s))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(s=>{this.json(e,200,s)}).catch(s=>this.error(e,s))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,s){const t=s;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="RELOAD_UNSAFE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??s}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,s,t){const r=JSON.stringify(t);e.writeHead(s,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((s,t)=>{let r="";e.setEncoding("utf8"),e.on("data",n=>{r+=n}),e.on("end",()=>{try{s(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,s,t){if(!this.probeHandler){this.json(s,501,{error:"probe not configured"});return}const r=t.indexOf("?"),n=r>=0?t.slice(0,r):t,o=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=n.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(s,200,h)}).catch(h=>this.error(s,h));return}this.probeHandler.probeAll(a).then(i=>{this.json(s,200,i)}).catch(i=>this.error(s,i))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const s=this.installHandler.listInstallable();this.json(e,200,s)}catch(s){this.error(e,s)}}handleInstallProgress(e,s){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(s);if(!t){this.json(e,200,{agentType:s,status:"unknown",inProgress:!1,progress:null});return}let r,n,o;switch(t.phase){case"completed":r="done",n="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",n="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",n=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",n=`\u6B63\u5728\u5B89\u88C5 ${s}...`;break;case"verifying":r="installing",n="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:s,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:n,error:o})}async handleInstall(e,s){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=s;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const n=r.error?.code;n==="UNKNOWN_AGENT"||n==="UNSUPPORTED_OS"?this.json(e,400,r):n==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{y as AdminServer};
|
|
1
|
+
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class f{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}async start(e){return new Promise((n,t)=>{this.server=u((r,s)=>this.handleRequest(r,s)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(s=>this.handleAdd(n,s)).catch(s=>this.error(n,s));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const s=decodeURIComponent(r.slice(12));this.handleRemove(n,s)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,s)}else if(r==="/api/reload"&&t==="POST")this.handleReload(n);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(s=>this.handleInstall(n,s)).catch(s=>this.error(n,s));else if(t==="GET"&&r.startsWith("/api/install/")){const s=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,s)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const s=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(o=>this.handleProxySetAgentRelay(n,s,o)).catch(o=>this.error(n,o))}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(s=>this.handleProxySetEnabled(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(s=>this.handleProxySetDefaultRoute(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(s=>this.handleProxySetInterceptHosts(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const s=decodeURIComponent(r.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(n,s,o)).catch(o=>this.error(n,o))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const s=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,s)}else this.json(n,404,{error:"not_found"})}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(n){this.error(e,n)}}async handleProxySetRoute(e,n,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const s=await this.proxyHandler.setRoute(n,t);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleProxyDeleteRoute(e,n){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required"});return}const s=t;if(typeof s?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const o=await this.proxyHandler.setAgentRelay(r,s.enabled);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetEnabled(e,n){if(!this.ensureProxy(e))return;const t=n;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const r=await this.proxyHandler.disableAll();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,n){if(!this.ensureProxy(e))return;const r=n?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const s=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetInterceptHosts(e,n){if(!this.ensureProxy(e))return;const t=n;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(n=>typeof n=="string")}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(n=>{this.json(e,200,{ok:!0,result:n})}).catch(n=>this.error(e,n))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",s=>{r+=s}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),s=r>=0?t.slice(0,r):t,o=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(n,200,h)}).catch(h=>this.error(n,h));return}this.probeHandler.probeAll(a).then(i=>{this.json(n,200,i)}).catch(i=>this.error(n,i))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);if(!t){this.json(e,200,{agentType:n,status:"unknown",inProgress:!1,progress:null});return}let r,s,o;switch(t.phase){case"completed":r="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",s=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",s=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",s="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:n,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:s,error:o})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const s=r.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,r):s==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{f as AdminServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{execFile as
|
|
2
|
-
${
|
|
3
|
-
${g}`}catch(
|
|
1
|
+
import{execFile as R,spawn as _}from"node:child_process";import{log as m}from"../log/logger.js";import{resolveCliPath as N,getCliVersion as T,invalidateCliPathCache as x}from"../util/cli-probe.js";import{getInstallCommand as k,getCliBinary as A,isKnownAgent as D,detectPlatformOS as O,formatInstallCommand as U}from"./registry.js";import{checkPrerequisites as y,getMissingPrerequisites as L}from"./preflight.js";import{installMissingPrerequisites as C}from"./prereq-installer.js";import{detectEnvironment as V,formatEnvironmentInfo as F,isEnvironmentSupported as q}from"./env-detect.js";import{generateManualGuide as P}from"./manual-guide.js";import{npmInstallWithMirror as G,isTransientInstallError as B}from"./npm-registry.js";import{getAllAgentInstallInfo as H}from"./registry.js";class c extends Error{code;constructor(n,t){super(t),this.name="InstallerError",this.code=n}}const w=64*1024,j=20,v=2,W=3e3;class ie{os;activeInstalls=new Map;constructor(){this.os=O()}listInstallable(){return{platform:this.os,agents:H(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:t}=n,e=Date.now();if(this.activeInstalls.has(t))return this.fail(t,"preflight",e,new c("INSTALL_IN_PROGRESS",`${t} is already being installed`));try{return await this._doInstall(n,e)}catch(i){this.activeInstalls.delete(t);const r=i instanceof Error?i.message:String(i);return m.error("installer",`${t} install unexpected error: ${r}`),{agentType:t,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${r}`},durationMs:Date.now()-e,output:""}}}async _doInstall(n,t){const{agentType:e}=n;let i;try{i=await V()}catch(s){const a=s instanceof Error?s.message:String(s);return this.fail(e,"preflight",t,new c("INTERNAL",`Environment detection failed: ${a}`))}m.info("installer",`Install request: ${e}
|
|
2
|
+
${F(i)}`);const r=q(i);if(!r.supported)return this.fail(e,"preflight",t,new c("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${r.reason}. Please install ${e} manually.`),i);if(this.setProgress(e,"preflight",t),!D(e))return this.fail(e,"preflight",t,new c("UNKNOWN_AGENT",`Unknown agent type: ${e}`),i);const l=k(e,this.os);if(!l){const s=this.getManualHint(e,this.os),a=s?`Installation of ${e} is not supported on ${this.os}. ${s}`:`Installation of ${e} is not supported on ${this.os}`;return this.fail(e,"preflight",t,new c("UNSUPPORTED_OS",a),i)}const u=A(e),h=await N(u),p=h?(await T(h)).version:null;if(h&&!n.force&&!n.dryRun){this.activeInstalls.delete(e);const s=Date.now()-t;return m.info("installer",`${e} already installed at ${h}${p?` (v${p})`:""}`),{agentType:e,ok:!0,phase:"completed",installedPath:h,installedVersion:p,durationMs:s,output:"",environment:i}}const f=l.prerequisites??[];let o=[];if(f.length>0){m.info("installer",`Checking prerequisites for ${e}: ${f.join(", ")}`),o=await y(f,this.os),m.info("installer",`Prerequisites: ${o.map(a=>`${a.label}=${a.met?a.version:"missing"}`).join(", ")}`);const s=L(o);if(s.length>0){const a=s.map(d=>`${d.label}${d.minVersion?` >= ${d.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(e,"preflight",t,new c("PREREQ_MISSING",`Missing prerequisites: ${a}. Install them first or retry without skipPrereqInstall.`),i,o);const d=s.map($=>`${$.label}${$.minVersion?` >= ${$.minVersion}`:""}`);m.info("installer",`Will auto-install prerequisites: ${d.join(", ")}`),this.setProgress(e,"installing_prereq",t,s[0].label,d);const I=await C(s,this.os);if(!I.allOk){const $=I.results.find(M=>!M.ok),b=$?`Failed to install prerequisite ${$.prereq.label}: ${$.output}`:"Prerequisite installation failed";return this.fail(e,"installing_prereq",t,new c("PREREQ_INSTALL_FAILED",b),i,o)}m.info("installer",`All prerequisites installed for ${e}`),o=await y(f,this.os)}}}if(n.dryRun){this.activeInstalls.delete(e);const s=L(o),a=this.getManualHint(e,this.os),d={agentType:e,environment:i,canInstall:!0,alreadyInstalled:!!h,installedPath:h,installedVersion:p,installCommand:U(l),installMode:l.mode,prerequisites:o,missingPrerequisites:s,fallbackCommand:l.fallback?.command??null,manualHint:a},I=P({agentType:e,os:this.os,env:i,missingPrereqs:s});return{agentType:e,ok:!0,phase:"completed",durationMs:Date.now()-t,output:"dry-run: no commands executed",environment:i,dryRun:d,manualGuide:I}}this.setProgress(e,"installing",t),m.info("installer",`Installing ${e}: ${l.command}`);let g;try{g=await this.executeWithRetry(l,e,t,n.timeoutMs)}catch(s){if(l.fallback&&s instanceof c&&(s.code==="INSTALL_FAILED"||s.code==="INSTALL_TIMEOUT")){m.info("installer",`Primary install (${l.command}) failed: ${s.message}`),m.info("installer",`Trying fallback: ${l.fallback.command}`),this.setProgress(e,"installing",t);try{g=await this.executeWithRetry(l.fallback,e,t,n.timeoutMs),g=`[primary failed, fallback succeeded]
|
|
3
|
+
${g}`}catch(a){const d=s.message,I=a instanceof c?a.message:String(a);return this.fail(e,"installing",t,new c("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
|
|
4
4
|
Primary: ${d}
|
|
5
|
-
Fallback: ${I}`),
|
|
5
|
+
Fallback: ${I}`),i,o)}}else return s instanceof c?this.fail(e,"installing",t,new c(s.code,s.message),i,o):this.fail(e,"installing",t,new c("INTERNAL",s instanceof Error?s.message:String(s)),i,o)}{const s=(g??"").trim().split(`
|
|
6
6
|
`).slice(-12).join(`
|
|
7
7
|
`);m.info("installer",`${e} \u5B89\u88C5\u547D\u4EE4\u8F93\u51FA(\u5C3E\u90E8):
|
|
8
|
-
${
|
|
9
|
-
${
|
|
10
|
-
`).slice(-
|
|
11
|
-
`),l=this.activeInstalls.get(n);l&&(l.outputTail=
|
|
8
|
+
${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(e,"verifying",t),m.info("installer",`Verifying ${e} installation...`);const s=await N(u);if(!s)return this.fail(e,"verifying",t,new c("VERIFICATION_FAILED",`${u} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,o);let{version:a,error:d}=await T(s);a===null&&d&&(await this.sleep(1e3),{version:a,error:d}=await T(s));const I=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} installed successfully at ${s} (v${a??"unknown"}, ${I}ms)`),{agentType:e,ok:!0,phase:"completed",installedPath:s,installedVersion:a,durationMs:I,output:g,prerequisites:o.length>0?o:void 0,environment:i}}const E=Date.now()-t;return this.activeInstalls.delete(e),m.info("installer",`${e} install command completed (${E}ms, verification skipped)`),{agentType:e,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:g,prerequisites:o.length>0?o:void 0,environment:i}}getManualHint(n,t){const i=k(n,t)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix",openhuman:"https://github.com/tinyhumansai/openhuman/issues/128"}[n],u=[];return l&&u.push(`Docs: ${l}`),i&&u.push(`Alternative: ${i.command}`),u.length>0?u.join(" | "):null}setProgress(n,t,e,i,r){this.activeInstalls.set(n,{agentType:n,phase:t,startedAt:e,elapsedMs:Date.now()-e,...i?{currentPrereq:i}:{},...r?{pendingPrereqs:r}:{}})}fail(n,t,e,i,r,l,u){const h=u??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),m.error("installer",`${n} install failed at ${t}: ${i.message}`);const p=l?L(l):[],f=P({agentType:n,os:this.os,env:r??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:p,primaryFailed:t==="installing",fallbackFailed:i.code==="FALLBACK_EXHAUSTED",error:i.message});return{agentType:n,ok:!1,phase:"failed",error:{code:i.code,message:i.message},durationMs:Date.now()-e,output:h,environment:r,prerequisites:l,manualGuide:f}}async executeWithRetry(n,t,e,i){let r=null;for(let l=0;l<=v;l++)try{return l>0&&(m.info("installer",`Retry ${l}/${v} for ${t}...`),await this.sleep(W)),await this.executeCommand(n,t,e,i)}catch(u){if(r=u instanceof c?u:new c("INTERNAL",String(u)),!(r.code==="INSTALL_TIMEOUT"||r.code==="INSTALL_FAILED"&&B(r.message))||l>=v)throw r;m.info("installer",`Attempt ${l+1} failed (retryable): ${r.message}`)}throw r??new c("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(t=>setTimeout(t,n))}executeCommand(n,t,e,i){const r=i??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,r,t,e);case"shell":return this.executeShell(n.command,r,t,e);case"exec":return this.executeExec(n.command,n.execArgs??[],r,t,e);default:return Promise.reject(new c("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,t,e,i){try{const{output:r,registry:l}=await G(n,t,w);return m.info("installer",`npm install ${n} succeeded via ${l}`),r}catch(r){const l=r instanceof Error?r.message:String(r);throw l.includes("timed out")||l.includes("ETIMEDOUT")?new c("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${l}`):new c("INSTALL_FAILED",`npm install failed (tried all mirrors): ${l}`)}}executeShell(n,t,e,i){return new Promise((r,l)=>{const u=process.platform==="win32",h=u?"cmd.exe":"bash",p=u?["/d","/c",n]:["-c",`set -o pipefail; ${n}`];m.info("installer",`exec: ${h} ${p.join(" ")}`);const f=_(h,p,{timeout:t,stdio:["ignore","pipe","pipe"],...u?{windowsVerbatimArguments:!0}:{},env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let o="",g=!1;const E=setTimeout(()=>{g=!0;try{f.kill("SIGTERM")}catch{}setTimeout(()=>{try{f.kill("SIGKILL")}catch{}},5e3).unref()},t);f.stdout?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),f.stderr?.on("data",s=>{const a=s.toString("utf-8");o+=a,o.length>w&&(o=o.slice(-w)),this.updateOutputTail(e,i,o)}),f.on("error",s=>{clearTimeout(E),l(new c("INSTALL_FAILED",`Spawn error: ${s.message}`))}),f.on("close",s=>{if(clearTimeout(E),g){l(new c("INSTALL_TIMEOUT",`Install timed out after ${t/1e3}s`));return}if(s!==0){const a=o.slice(-1024);l(new c("INSTALL_FAILED",`Process exited with code ${s}: ${a}`));return}r(o.trim())})})}executeExec(n,t,e,i,r){return new Promise((l,u)=>{m.info("installer",`exec: ${n} ${t.join(" ")}`);const h=R(n,t,{timeout:e,maxBuffer:w},(p,f,o)=>{if(p){if(p.killed)u(new c("INSTALL_TIMEOUT",`${n} timed out after ${e/1e3}s`));else{const g=o?.trim()||p.message;u(new c("INSTALL_FAILED",`${n} failed: ${g}`))}return}l(`${f??""}
|
|
9
|
+
${o??""}`.trim())});this.trackOutput(h,i,r)})}trackOutput(n,t,e){n.on("close",()=>{const i=this.activeInstalls.get(t);i&&(i.elapsedMs=Date.now()-e)})}updateOutputTail(n,t,e){const r=e.split(`
|
|
10
|
+
`).slice(-j).join(`
|
|
11
|
+
`),l=this.activeInstalls.get(n);l&&(l.outputTail=r,l.elapsedMs=Date.now()-t)}}export{ie as AgentInstaller,c as InstallerError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getInstallCommand as
|
|
1
|
+
import{getInstallCommand as f,getCliBinary as g}from"./registry.js";import{getPrereqManualInstructions as $}from"./prereq-installer.js";import{getMirrorInstallCommands as t}from"./npm-registry.js";function v(u){const{agentType:e,os:i,env:h,missingPrereqs:c}=u,s=[],a=g(e)??e,o=f(e,i),m=b(i);if(s.push(`=== ${e} \u4EBA\u5DE5\u5B89\u88C5\u6307\u5357 (${m}) ===`),s.push(""),s.push("\u5F53\u524D\u73AF\u5883:"),s.push(` \u7CFB\u7EDF: ${h.osVersion} (${h.arch})`),s.push(` Node.js: ${h.nodeVersion??"\u672A\u5B89\u88C5"}`),s.push(` npm: ${h.npmVersion??"\u672A\u5B89\u88C5"}`),s.push(""),u.error&&(s.push("\u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u539F\u56E0:"),s.push(` ${u.error}`),s.push("")),c.length>0){s.push("--- \u6B65\u9AA4 1: \u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56 ---"),s.push("");for(const n of c){const p=$(n.id,i);s.push(`\u25B8 ${n.label}${n.minVersion?` (\u9700\u8981 >= ${n.minVersion})`:""}`),p&&s.push(` \u5B89\u88C5\u65B9\u6CD5: ${p}`),s.push(` \u9A8C\u8BC1: ${n.id==="node"?"node --version":n.id==="npm"?"npm --version":`${n.id} --version`}`),s.push("")}}const l=c.length>0?"2":"1";if(s.push(`--- \u6B65\u9AA4 ${l}: \u5B89\u88C5 ${e} ---`),s.push(""),o){if(s.push(" \u63A8\u8350\u65B9\u5F0F:"),s.push(` ${o.command}`),s.push(""),o.mode==="npm"&&o.npmPackage){const n=t(o.npmPackage);s.push(" \u955C\u50CF\u5B89\u88C5\uFF08\u56FD\u5185\u7F51\u7EDC\u6162\u6216\u88AB\u5899\u65F6\u4F7F\u7528\uFF09:");for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`);s.push("")}if(o.fallback){if(s.push(" \u5907\u9009\u65B9\u5F0F\uFF08\u63A8\u8350\u5B89\u88C5\u5931\u8D25\u65F6\u4F7F\u7528\uFF09:"),s.push(` ${o.fallback.command}`),o.fallback.mode==="npm"&&o.fallback.npmPackage){const n=t(o.fallback.npmPackage);for(const p of n)s.push(` # ${p.label}`),s.push(` ${p.command}`)}s.push("")}}const d=c.length>0?"3":"2";s.push(`--- \u6B65\u9AA4 ${d}: \u9A8C\u8BC1\u5B89\u88C5 ---`),s.push(""),s.push(` ${a} --version`),s.push(""),s.push(" \u5982\u679C\u663E\u793A\u7248\u672C\u53F7\uFF0C\u8BF4\u660E\u5B89\u88C5\u6210\u529F\u3002"),s.push(""),s.push("--- \u5E38\u89C1\u95EE\u9898 ---"),s.push(""),s.push(" Q: \u5B89\u88C5\u540E\u547D\u4EE4\u627E\u4E0D\u5230\uFF1F"),s.push(" A: \u53EF\u80FD\u9700\u8981\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\uFF0C\u6216\u8FD0\u884C:"),i==="macos"||i==="linux"?(s.push(" source ~/.zshrc # zsh"),s.push(" source ~/.bashrc # bash")):s.push(" \u91CD\u65B0\u6253\u5F00 PowerShell \u6216 CMD \u7A97\u53E3"),s.push(""),o?.mode==="npm"&&(s.push(" Q: npm install -g \u62A5\u6743\u9650\u9519\u8BEF\uFF1F"),i==="macos"||i==="linux"?(s.push(" A: \u4E0D\u8981\u7528 sudo npm install -g\u3002\u5EFA\u8BAE\u7528\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00:"),s.push(" 1. brew install node (macOS\uFF0C\u63A8\u8350)"),s.push(" 2. \u4F7F\u7528 fnm/nvm \u7BA1\u7406 Node.js (Linux)"),s.push(" 3. \u914D\u7F6E npm \u5168\u5C40\u76EE\u5F55: npm config set prefix ~/.npm-global")):s.push(" A: \u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6253\u5F00 PowerShell \u540E\u91CD\u8BD5"),s.push("")),s.push("--- \u5B98\u65B9\u6587\u6863 ---"),s.push("");const r={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",gemini:"https://github.com/google-gemini/gemini-cli",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"};return r[e]&&s.push(` ${e}: ${r[e]}`),s.join(`
|
|
2
2
|
`)}function b(u){switch(u){case"macos":return"macOS";case"linux":return"Linux";case"windows":return"Windows"}}export{v as generateManualGuide};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function
|
|
1
|
+
function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function c(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},linux:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},windows:{...n('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:e("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...e("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:n("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:e("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:e("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n(`powershell -ExecutionPolicy ByPass -c "irm 'https://cursor.com/install?win32=true' | iex"`)},copilot:{cliBinary:"copilot",macos:e("@github/copilot",{minNodeVersion:"22.0"}),linux:e("@github/copilot",{minNodeVersion:"22.0"}),windows:e("@github/copilot",{minNodeVersion:"22.0"})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function r(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function m(i,s){const o=l[i];return o?o[s]:null}function p(i){return l[i]?.cliBinary??null}function a(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?r(t):null,prerequisites:t?.prerequisites}}function d(i){return Object.keys(l).sort().map(s=>a(s,i)).filter(s=>s!==null)}function h(i){return i in l}export{u as detectPlatformOS,r as formatInstallCommand,a as getAgentInstallInfo,d as getAllAgentInstallInfo,p as getCliBinary,m as getInstallCommand,h as isKnownAgent};
|
package/dist/core/proxy/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ProxyManager as
|
|
1
|
+
import{ProxyManager as l}from"./proxy-manager.js";import{ProxyManager as g,ProxyNoRelayRouteError as O}from"./proxy-manager.js";import{relayHostForClientType as E}from"./relay-hosts.js";let o=null;function y(r){return o||(o=new l(r)),o}function c(){return o}function u(){o=null}class s extends Error{agentName;code="RELAY_PROXY_UNAVAILABLE";constructor(e){super(`agent "${e}" has Grix relay enabled but the local MITM proxy is not running; refusing to start it (it would silently fall back to your own account). Fix the proxy or turn relay off.`),this.agentName=e,this.name="ProxyRelayUnavailableError"}}const i="localhost,127.0.0.1,::1";function p(r,e){if(o?.isRelayExpectedButUnavailable(e))throw new s(e);const n=o?.getInjectionEnv(e);if(!n)return r;const a={...r,...n},t=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return a.NO_PROXY=t&&t.trim()?`${t},${i}`:i,a}export{g as ProxyManager,O as ProxyNoRelayRouteError,s as ProxyRelayUnavailableError,p as applyProxyEnv,c as getProxyManager,y as initProxyManager,E as relayHostForClientType,u as resetProxyManagerForTest};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`,"utf8")}getInjectionEnv(){return!this.
|
|
3
|
-
`),
|
|
4
|
-
`)?
|
|
5
|
-
`;return await
|
|
6
|
-
${
|
|
7
|
-
`,"utf8")}}export{
|
|
1
|
+
import{existsSync as w,readFileSync as x}from"node:fs";import{mkdir as p,rm as m,writeFile as c}from"node:fs/promises";import{join as o}from"node:path";import{rootCertificates as A}from"node:tls";import{DynamicCertificateAuthority as I}from"./crypto/index.js";import{TrafficCaptureWriter as E,TrafficEventEmitter as H}from"./capture/index.js";import{HostInterceptList as D,RuntimeRouteRegistry as P}from"./routing/index.js";import{AcpTrafficProxyServer as C}from"./server/index.js";import{logProxyRuntimeStderr as i}from"./server/logger.js";class l{filePath="";append(){}close(){return Promise.resolve()}}class S extends Error{agentName;host;code="NO_RELAY_ROUTE";constructor(t,e){super(`no usable relay route for agent "${t}" (host ${e}); provision a gateway key first`),this.agentName=t,this.host=e,this.name="ProxyNoRelayRouteError"}}const h="proxy-state.json",d="routes.json",a="grix-gateway";class k{proxyDataDir;certificateAuthority=null;captureWriter=new l;runtimeRoutes=new P;interceptHosts=new D;server=null;runtimeInfo=null;relayAgents=new Map;configExtras={};persistQueue=Promise.resolve();configLoaded=!1;lifecycleQueue=Promise.resolve();enqueueLifecycle(t){const e=this.lifecycleQueue.then(t,t);return this.lifecycleQueue=e.catch(()=>{}),e}constructor(t){this.proxyDataDir=o(t,"proxy"),this.loadConfigFromDisk()}loadConfigFromDisk(){if(this.configLoaded)return;this.configLoaded=!0;const t=this.loadRoutesFile();t&&(this.applyRoutesFile(t),t.capture!==void 0&&(this.configExtras.capture=t.capture),t.mitmPort!==void 0&&(this.configExtras.mitmPort=t.mitmPort),t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.relayAgents.size>0&&this.syncInterceptHosts())}async start(t={}){return this.enqueueLifecycle(()=>this.startInternal(t))}async startInternal(t={}){if(this.runtimeInfo)return this.runtimeInfo;await p(this.proxyDataDir,{recursive:!0});const e=t.captureEnabled??this.configExtras.capture??process.env.GRIX_PROXY_CAPTURE==="1";this.captureWriter=e?await E.create(this.proxyDataDir,{onCleanupError:(g,u)=>{const R=u instanceof Error?u.message:String(u);i("warn",`stale capture cleanup skipped ${g}: ${R}`)}}):new l,this.certificateAuthority=new I(this.proxyDataDir);const r=await this.certificateAuthority.ensureRootCertificate(),s=await this.writeCaBundle(r.certPem);t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.server=new C(this.captureWriter,this.certificateAuthority,new H,this.runtimeRoutes,this.interceptHosts);const n=t.port??this.configExtras.mitmPort??0,{port:y}=await this.server.start(n,"127.0.0.1");return this.runtimeInfo={mitmPort:y,proxyUrl:`http://127.0.0.1:${y}`,caCertPath:r.certPath,caBundlePath:s,captureFilePath:this.captureWriter.filePath,interceptHosts:this.interceptHosts.getHosts()},await this.writeStateFile(this.runtimeInfo),this.runtimeInfo}async setAgentRelayEnabled(t,e,r={}){return this.enqueueLifecycle(()=>this.setAgentRelayEnabledInternal(t,e,r))}async setAgentRelayEnabledInternal(t,e,r={}){const s=t.trim();if(s){if(e){const n=r.relayHost?.trim();if(!n)throw new Error(`relayHost is required to enable relay for agent "${s}"`);if(!this.runtimeRoutes.hasUsableHostRoute(n))throw new S(s,n);await this.startInternal(),this.relayAgents.set(s,n)}else this.relayAgents.delete(s);this.syncInterceptHosts(),await this.persistConfig()}}syncInterceptHosts(){this.interceptHosts.setHosts([...new Set(this.relayAgents.values())]),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(t=>{i("warn",`failed to write proxy-state.json: ${t}`)}))}isAgentRelayEnabled(t){return this.relayAgents.has(t.trim())}getRelayAgents(){return[...this.relayAgents.keys()]}isRelayExpectedButUnavailable(t){return this.isAgentRelayEnabled(t)&&!this.runtimeInfo}getDegradedRelayAgents(){return this.runtimeInfo?[]:[...this.relayAgents.keys()]}async startIfAnyRelayAgent(){this.relayAgents.size!==0&&await this.start()}async stopIfUnused(t){return this.enqueueLifecycle(async()=>!this.runtimeInfo||this.relayAgents.size>0||t?.()?!1:(await this.stopInternal(),!0))}async disableAllAgentRelay(){return this.enqueueLifecycle(async()=>{const t=[...this.relayAgents.keys()];return this.relayAgents.clear(),this.syncInterceptHosts(),await this.persistConfig(),t})}async stop(){return this.enqueueLifecycle(()=>this.stopInternal())}async stopInternal(){this.server&&(await this.server.stop().catch(()=>{}),this.server=null),await this.captureWriter.close().catch(()=>{}),this.captureWriter=new l,this.runtimeInfo=null,await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}async clearRuntimeState(){this.runtimeInfo||await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}getRuntimeInfo(){return this.runtimeInfo}getMitmPort(){return this.runtimeInfo?.mitmPort??null}getCaCertPath(){return this.runtimeInfo?.caCertPath??null}getCaBundlePath(){return this.runtimeInfo?.caBundlePath??null}setRoute(t){this.runtimeRoutes.setRoute(t)}deleteRoute(t){this.runtimeRoutes.deleteRoute(t)}setDefaultRouteKey(t){this.runtimeRoutes.setDefaultRouteKey(t)}setHostDefaultRoute(t,e){this.runtimeRoutes.setHostDefaultRoute(t,e)}getInterceptHosts(){return this.interceptHosts.getHosts()}setInterceptHosts(t){this.interceptHosts.setHosts(t),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(e=>{i("warn",`failed to write proxy-state.json: ${e}`)}))}getConfigSnapshot(){const t={routes:this.runtimeRoutes.listRoutes(),interceptHosts:this.interceptHosts.getHosts(),relayAgents:Object.fromEntries(this.relayAgents)},e=this.runtimeRoutes.getDefaultRouteKey();e&&(t.defaultRouteKey=e);const r=this.runtimeRoutes.getHostDefaultRoutes();return Object.keys(r).length>0&&(t.hostDefaultRoutes=r),this.configExtras.capture!==void 0&&(t.capture=this.configExtras.capture),this.configExtras.mitmPort!==void 0&&(t.mitmPort=this.configExtras.mitmPort),t}async persistConfig(){const t=this.getConfigSnapshot(),e=this.persistQueue.then(()=>this.writeConfigFile(t));return this.persistQueue=e.catch(()=>{}),e}async writeConfigFile(t){await p(this.proxyDataDir,{recursive:!0});const e=o(this.proxyDataDir,d);await c(e,`${JSON.stringify(t,null,2)}
|
|
2
|
+
`,"utf8")}getInjectionEnv(t){return!this.isAgentRelayEnabled(t)||!this.runtimeInfo?null:{HTTPS_PROXY:this.runtimeInfo.proxyUrl,NODE_EXTRA_CA_CERTS:this.runtimeInfo.caCertPath,SSL_CERT_FILE:this.runtimeInfo.caBundlePath}}async writeCaBundle(t){const e=o(this.proxyDataDir,"ca-bundle.pem"),r=A.join(`
|
|
3
|
+
`),s=t.endsWith(`
|
|
4
|
+
`)?t:`${t}
|
|
5
|
+
`;return await c(e,`${r}
|
|
6
|
+
${s}`,"utf8"),e}loadRoutesFile(){const t=o(this.proxyDataDir,d);if(!w(t))return null;try{return JSON.parse(x(t,"utf8"))}catch(e){const r=e instanceof Error?e.message:String(e);return i("warn",`proxy routes file ignored (${t}): ${r}`),null}}applyRoutesFile(t){if(Array.isArray(t.relayAgents))i("warn","relayAgents in routes.json is an outdated array format (no host info): discarding it. Re-enable Grix relay per agent in the desktop app.");else for(const[e,r]of Object.entries(t.relayAgents??{})){const s=e.trim(),n=String(r??"").trim();s&&n&&this.relayAgents.set(s,n)}this.relayAgents.size>0&&i("info",`proxy relay agents restored: ${[...this.relayAgents.keys()].join(", ")}`),t.relayAgents===void 0&&(t.routes??[]).some(e=>e.routeKey.startsWith("grix-gateway"))&&i("warn","found legacy Grix relay config without per-agent list: relay is now per-agent and starts OFF. Re-enable it per agent in the desktop app (Settings \u2192 Grix relay).");for(const e of t.routes??[]){if(e.routeKey===a){i("info",`proxy legacy route dropped: ${a}`);continue}try{this.runtimeRoutes.handleControlLine(JSON.stringify(e.type==="delete_route"?e:{type:"set_route",routeKey:e.routeKey,targetBaseUrl:e.targetBaseUrl,...e.headers?{headers:e.headers}:{},...e.model?{model:e.model}:{},...e.modelMap?{modelMap:e.modelMap}:{},...e.passthrough?{passthrough:!0}:{},...e.relayOnly?{relayOnly:!0}:{},...e.codexResponsesToChat?{codexResponsesToChat:!0}:{}})),i("info",`proxy route loaded: ${e.routeKey} \u2192 ${e.targetBaseUrl}`)}catch(r){const s=r instanceof Error?r.message:String(r);i("warn",`proxy route "${e.routeKey}" skipped: ${s}`)}}t.defaultRouteKey&&t.defaultRouteKey!==a?(this.runtimeRoutes.setDefaultRouteKey(t.defaultRouteKey),i("info",`proxy default route key: ${t.defaultRouteKey}`)):t.defaultRouteKey===a&&i("info",`proxy legacy default route key dropped: ${a}`);for(const[e,r]of Object.entries(t.hostDefaultRoutes??{}))this.runtimeRoutes.setHostDefaultRoute(e,r),i("info",`proxy host default route: ${e} \u2192 ${r}`)}async writeStateFile(t){const e=o(this.proxyDataDir,h),r={...t,pid:process.pid,updated_at:Date.now()};await c(e,`${JSON.stringify(r,null,2)}
|
|
7
|
+
`,"utf8")}}export{k as ProxyManager,S as ProxyNoRelayRouteError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={claude:"api.anthropic.com",codex:"api.openai.com"};function t(o){return e[String(o??"").trim().toLowerCase()]}export{t as relayHostForClientType};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async function f(a,t){return a.getRuntimeInfo()?a.stopIfUnused(()=>t.getAgentsWithStaleRelayEnv().length>0):!1}async function u(a,t,n){for(const e of a.getRelayAgents())t.markAgentRelayEnvStale(e);const s=await a.disableAllAgentRelay();for(const e of s)t.markAgentRelayEnvStale(e);for(const e of s){if(t.isAgentBusy(e)){n.info(`relay disabled for "${e}" but agent is busy; env stays stale until its next message`);continue}await t.restartAgent(e).catch(r=>{if(r?.code==="NOT_FOUND"){n.info(`relay disabled for "${e}" (agent no longer exists; nothing to restart)`);return}n.warn(`relay disabled for "${e}" but restart failed (env stays stale): ${r}`)})}return await f(a,t),s}async function g(a,t,n,s){const{agentName:e,enabled:r,relayHost:y}=s;r||t.markAgentRelayEnvStale(e),await a.setAgentRelayEnabled(e,r,r?{relayHost:y}:{});let l=!1,o=!1;if(t.isAgentBusy(e))o=!0,t.markAgentRelayEnvStale(e),n.info(`relay toggled for "${e}" but agent is busy; env stays stale until its next message`);else try{await t.restartAgent(e),l=!0}catch(i){if(i?.code==="NOT_FOUND")n.info(`relay toggled for "${e}" (agent no longer exists; nothing to restart)`);else{const d=t.markAgentRelayEnvStale(e);n.warn(`relay toggled for "${e}" but restart failed${d?" (old instance still holds the proxy env; marked stale)":" (no running instance; nothing holds the proxy env)"}: ${i}`)}}return await f(a,t),{restarted:l,busy:o}}export{u as disableAllRelayAndRestart,g as setOneAgentRelayAndRestart,f as stopProxyIfNobodyNeedsIt};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeHost as
|
|
1
|
+
import{normalizeHost as i}from"./intercept-list.js";const h="x-grix-proxy-route-key";function a(s){return Array.isArray(s)?s[0]?.trim()||null:s?.trim()||null}function d(s){return typeof s!="object"||s===null||Array.isArray(s)?!1:Object.values(s).every(e=>typeof e=="string")}function y(s){if(typeof s!="object"||s===null||Array.isArray(s))return!1;const e=s;return e.type!=="set_route"&&e.type!=="delete_route"&&e.type!=="pin_route"&&e.type!=="unpin_route"||typeof e.routeKey!="string"||e.routeKey.trim().length===0?!1:e.type==="unpin_route"?typeof e.sessionId=="string"&&e.sessionId.trim().length>0:e.type==="delete_route"?!0:typeof e.targetBaseUrl=="string"&&e.targetBaseUrl.trim().length>0&&(e.headers===void 0||d(e.headers))&&(e.model===void 0||typeof e.model=="string")&&(e.modelMap===void 0||d(e.modelMap))&&(e.passthrough===void 0||typeof e.passthrough=="boolean")&&(e.type==="set_route"||typeof e.sessionId=="string"&&e.sessionId.trim().length>0)}function f(s){const e=s.model?.trim();return{routeKey:s.routeKey.trim(),targetBaseUrl:s.targetBaseUrl.trim(),...s.headers?{headers:{...s.headers}}:{},...e?{model:e}:{},...s.modelMap?{modelMap:{...s.modelMap}}:{},...s.passthrough?{passthrough:!0}:{},...s.relayOnly?{relayOnly:!0}:{},...s.codexResponsesToChat?{codexResponsesToChat:!0}:{}}}class R{routes=new Map;pinnedRoutes=new Map;defaultRouteKey=null;hostDefaultRoutes=new Map;setDefaultRouteKey(e){this.defaultRouteKey=e?.trim()||null}getDefaultRouteKey(){return this.defaultRouteKey}setHostDefaultRoute(e,r){const t=i(e);if(!t)return;const o=r?.trim();o?this.hostDefaultRoutes.set(t,o):this.hostDefaultRoutes.delete(t)}getHostDefaultRoutes(){return Object.fromEntries(this.hostDefaultRoutes)}hasUsableHostRoute(e){const r=this.hostDefaultRoutes.get(i(e));return!!r&&this.routes.has(r)}listRoutes(){return[...this.routes.values()].map(e=>({...e}))}setRoute(e){const r=f(e);this.routes.set(r.routeKey,r)}deleteRoute(e){this.routes.delete(e.trim())}pinRoute(e,r){const t=f(e);this.pinnedRoutes.set(this.buildPinnedRouteKey(t.routeKey,r),t)}unpinRoute(e,r){this.pinnedRoutes.delete(this.buildPinnedRouteKey(e,r))}resolveRequestRouteInfo(e,r){const t=a(e.headers[h]);if(!t){if(r){const n=this.hostDefaultRoutes.get(i(r));if(n){const l=this.routes.get(n);if(l)return{route:l,routeKey:n,pinned:!1}}}if(this.defaultRouteKey){const n=this.routes.get(this.defaultRouteKey);if(n)return{route:n,routeKey:this.defaultRouteKey,pinned:!1}}return null}const o=a(e.headers["x-claude-code-session-id"]);if(o){const n=this.pinnedRoutes.get(this.buildPinnedRouteKey(t,o));if(n)return{route:n,routeKey:t,sessionId:o,pinned:!0}}const u=this.routes.get(t);return u?{route:u,routeKey:t,...o?{sessionId:o}:{},pinned:!1}:null}resolveRequestRoute(e){return this.resolveRequestRouteInfo(e)?.route??null}buildPinnedRouteKey(e,r){return`${e.trim()}:${r.trim()}`}handleControlLine(e){const r=e.trim();if(!r)return!1;const t=JSON.parse(r);if(!y(t))return!1;if(t.type==="delete_route")return this.deleteRoute(t.routeKey),!0;if(t.type==="unpin_route")return this.unpinRoute(t.routeKey,t.sessionId??""),!0;const o=t.targetBaseUrl;if(!o)return!1;const u={routeKey:t.routeKey,targetBaseUrl:o,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{},...t.relayOnly?{relayOnly:!0}:{},...t.codexResponsesToChat?{codexResponsesToChat:!0}:{}};return t.type==="pin_route"?(this.pinRoute(u,t.sessionId??""),!0):(this.setRoute(u),!0)}}export{h as GRIX_PROXY_ROUTE_KEY_HEADER,R as RuntimeRouteRegistry};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import{randomUUID as u}from"node:crypto";import
|
|
1
|
+
import{randomUUID as u}from"node:crypto";import g from"node:http";import H from"node:net";import _ from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as E,parseConnectAuthority as S,resolveAbsoluteRequestTarget as P,resolveMitmRequestTarget as x,resolveRuntimeRoutedRequestTarget as R}from"../routing/index.js";import{forwardHttpRequest as f}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as w,CODEX_OPENAI_WIRE_COMPAT_MODE as I}from"../compat/index.js";import{handleWebSocketUpgrade as C}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const y=1e4;function M(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function W(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function O(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function v(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class K{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;constructor(t,r,a,n,i=null){this.captureWriter=t,this.certificateAuthority=r,this.events=a,this.runtimeRoutes=n,this.interceptHosts=i,this.proxyServer=g.createServer((o,e)=>{this.handlePlainHttpRequest(o,e)}),this.proxyServer.on("connect",(o,e,s)=>{this.handleConnectRequest(o,e,s)}),this.proxyServer.on("clientError",(o,e)=>{m("warn","proxy client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
|
|
2
2
|
\r
|
|
3
|
-
`)}),this.mitmHttpServer=
|
|
3
|
+
`)}),this.mitmHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmHttpServer.on("clientError",(o,e)=>{m("warn","mitm client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
|
|
4
4
|
\r
|
|
5
|
-
`)}),this.mitmPlainHttpServer=
|
|
5
|
+
`)}),this.mitmPlainHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmPlainHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmPlainHttpServer.on("clientError",(o,e)=>{m("warn","mitm plain client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
|
|
6
6
|
\r
|
|
7
|
-
`)})}async start(t,
|
|
7
|
+
`)})}async start(t,r="0.0.0.0"){await new Promise((n,i)=>{this.proxyServer.once("error",i),this.proxyServer.listen(t??0,r,()=>{this.proxyServer.off("error",i),n()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t())})}async handlePlainHttpRequest(t,r){const a=u();let n=this.runtimeRoutes.resolveRequestRouteInfo(t);if(n&&this.interceptHosts&&!t.headers[E]){const l=M(t.url);l&&!this.interceptHosts.matches(l)&&(n=null)}if(!n&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){r.writeHead(502,{"content-type":"text/plain"}),r.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h}=n?R(t,n.route):{...P(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:t.method??"GET",path:o,headers:t.headers}),await f({requestId:a,connectionId:a,target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(n)})}async handleConnectRequest(t,r,a){const n=u();let i=t.url??"",o=443;try{const e=S(t.url??"");if(i=e.host,o=e.port,this.captureWriter.append({type:"connect_start",connectionId:n,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(r,e,a,n);return}r.write(`HTTP/1.1 200 Connection Established\r
|
|
8
8
|
\r
|
|
9
|
-
`),a.length>0&&
|
|
9
|
+
`),a.length>0&&r.unshift(a);const s=h=>{this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:e.host,error:h.message}),this.events.emitConnectError({requestId:n,host:e.host,port:e.port,error:h.message})};r.on("error",s);const p=()=>{const h=r.read(1);if(!h||h.length===0){r.once("readable",p);return}if(r.unshift(h),h[0]===22)this.startTlsMitm({socket:r,target:e,connectionId:n,reportConnectError:s});else{const d=r;d.__grixConnectionId=n,d.__grixProxyTarget={...e,protocol:"http:"},d.on("error",s),this.mitmPlainHttpServer.emit("connection",d)}};p()}catch(e){const s=e instanceof Error?e.message:String(e);this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:i,error:s}),this.events.emitConnectError({requestId:n,host:i,port:o,error:s}),r.end(`HTTP/1.1 502 Bad Gateway\r
|
|
10
10
|
\r
|
|
11
|
-
`)}}blindTunnel(t,
|
|
11
|
+
`)}}blindTunnel(t,r,a,n){const i=H.connect(r.port,r.host);let o=!1,e=!1,s;const p=h=>{o||(o=!0,clearTimeout(s),this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:r.host,error:h.message}),this.events.emitConnectError({requestId:n,host:r.host,port:r.port,error:h.message}),e?t.destroy():t.end(`HTTP/1.1 502 Bad Gateway\r
|
|
12
12
|
\r
|
|
13
|
-
`),i.destroy())};s=setTimeout(()=>{p(new Error(`blind tunnel connect timeout after ${
|
|
13
|
+
`),i.destroy())};s=setTimeout(()=>{p(new Error(`blind tunnel connect timeout after ${y}ms`))},y),i.once("connect",()=>{e=!0,clearTimeout(s),t.write(`HTTP/1.1 200 Connection Established\r
|
|
14
14
|
\r
|
|
15
|
-
`),a.length>0&&i.write(a),t.pipe(i),i.pipe(t)}),i.on("error",p),t.on("error",p)}async startTlsMitm(t){const{socket:
|
|
15
|
+
`),a.length>0&&i.write(a),t.pipe(i),i.pipe(t)}),i.on("error",p),t.on("error",p)}async startTlsMitm(t){const{socket:r,target:a,connectionId:n,reportConnectError:i}=t;try{const o=await this.certificateAuthority.getLeafCertificate(a.host),e=new _.TLSSocket(r,{isServer:!0,secureContext:o.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=n,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",i)}catch(o){i(o instanceof Error?o:new Error(String(o))),r.destroy()}}async handleMitmHttpRequest(t,r){const a=t.socket,n=a.__grixProxyTarget,i=a.__grixConnectionId??u();if(!n){r.writeHead(502),r.end("Missing MITM target");return}const o=u(),e=this.runtimeRoutes.resolveRequestRouteInfo(t,n.host);if(!e){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${n.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),r.writeHead(502,{"content-type":"text/plain"}),r.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T}=x({request:t,fallbackTarget:n,route:e.route});if(e?.route.relayOnly&&!W(p)){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),t.resume(),r.writeHead(404,{"content-type":"text/plain"}),r.end("relay: non-inference endpoint blocked");return}e?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&O(p)&&(t.headers[w]=I),this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),await f({requestId:o,connectionId:i,target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(e)})}handleMitmUpgrade(t,r,a){C(this.captureWriter,this.events,t,r,a)}}export{K as AcpTrafficProxyServer};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execFile as
|
|
2
|
-
`)[0]?.trim()||null||null}}catch(
|
|
1
|
+
import{execFile as c}from"node:child_process";import{accessSync as b,constants as A,existsSync as H,statSync as T}from"node:fs";import{join as h,dirname as m,basename as $,delimiter as p}from"node:path";import{promisify as l}from"node:util";import{log as a}from"../log/logger.js";const I=3e3,C=5e3,d="__GRIX_SHELL_PATH__";function x(t,e){const n=[".cmd",".bat",".exe",""];for(const r of t)if(r)for(const i of n){const s=h(r,`${e}${i}`);if(H(s))return s}return null}const M=1e4,P={cache:null,inflight:null},_={cache:null,inflight:null},S={cache:null,inflight:null};let w=0;function z(){w++;for(const t of[P,_,S])t.cache=null,t.inflight=null}function g(t,e){if(t.cache&&Date.now()-t.cache.at<M)return Promise.resolve(t.cache.value);if(t.inflight)return t.inflight;const n=w,r=e().then(i=>(w===n&&(t.cache={value:i,at:Date.now()}),t.inflight===r&&(t.inflight=null),i),i=>{throw t.inflight===r&&(t.inflight=null),i});return t.inflight=r,r}function D(){return g(P,async()=>{const t=l(c),e=async n=>{try{const{stdout:r}=await t("powershell",["-NoProfile","-NonInteractive","-Command",`[Environment]::GetEnvironmentVariable('Path','${n}')`],{timeout:5e3,encoding:"utf-8",windowsHide:!0});return r.split(";").map(i=>i.trim()).filter(Boolean)}catch(r){return a.warn("cli-probe",`\u8BFB\u53D6\u6CE8\u518C\u8868 ${n} PATH \u5931\u8D25: ${r instanceof Error?r.message:String(r)}`),[]}};return[...await e("User"),...await e("Machine")]})}async function F(t){if(process.platform!=="win32")return null;const e=await D(),n=x(e,t)??await E(t);return n&&L(m(n)),n}function O(t){try{return T(t).isFile()?(b(t,A.X_OK),!0):!1}catch{return!1}}function y(t,e){for(const n of t){if(!n)continue;const r=h(n,e);if(O(r))return r}return null}const W=new Set(["sh","bash","zsh","ksh","dash","ash"]);function G(){return g(_,async()=>{const t=process.env.SHELL||"/bin/sh";if(!W.has($(t)))return a.info("cli-probe",`\u767B\u5F55 shell ${t} \u4E0D\u652F\u6301 -lic\uFF0C\u8DF3\u8FC7 shell PATH \u8BFB\u53D6`),[];const e=l(c);try{const n=e(t,["-lic",`printf '\\n${d}%s\\n' "$PATH"`],{timeout:C,killSignal:"SIGKILL",encoding:"utf-8"});n.child?.stdin?.end();const{stdout:r}=await n,i=r.split(/\r?\n/).reverse().find(s=>s.startsWith(d));return i?i.slice(d.length).split(p).map(s=>s.trim()).filter(Boolean):[]}catch(n){return a.warn("cli-probe",`\u8BFB\u53D6\u767B\u5F55 shell PATH \u5931\u8D25: ${n instanceof Error?n.message:String(n)}`),[]}})}function k(){return g(S,async()=>{const t=l(c),e=process.platform==="win32";try{const{stdout:n}=await t(e?"cmd.exe":"npm",e?["/c","npm","prefix","-g"]:["prefix","-g"],{timeout:5e3,killSignal:"SIGKILL",encoding:"utf-8",...e?{windowsHide:!0}:{}});return n.trim()||null}catch{return null}})}async function E(t){const e=await k();return e?process.platform==="win32"?x([e],t):y([h(e,"bin"),e],t):null}async function N(t){if(process.platform==="win32")return null;const e=await G(),n=y(e,t)??await E(t);return a.info("cli-probe",`\u767B\u5F55 shell PATH / npm \u5168\u5C40 bin \u515C\u5E95\u67E5\u627E ${t}: \u7ED3\u679C=${n??"\u672A\u627E\u5230"}`),n&&L(m(n)),n}function L(t){const e=process.env.PATH??"",n=process.platform==="win32"?(r,i)=>r.toLowerCase()===i.toLowerCase():(r,i)=>r===i;e.split(p).some(r=>n(r,t))||(process.env.PATH=e?`${e}${p}${t}`:t,a.info("cli-probe",`\u5DF2\u5C06 ${t} \u8FFD\u52A0\u5230\u5F53\u524D\u8FDB\u7A0B PATH\uFF08\u5B89\u88C5\u540E\u81EA\u6108\uFF09`))}async function q(t){const e=l(c),n=process.platform==="win32",r=n?"where":"which";try{const{stdout:i}=await e(r,[t],{timeout:3e3,encoding:"utf-8"}),s=i.trim().split(/\r?\n/).map(o=>o.trim()).filter(Boolean);if(s.length>0){if(n){const o=s.find(f=>/\.(cmd|bat)$/i.test(f));if(o)return o;const u=s.find(f=>/\.exe$/i.test(f));return u||s[0]}return s[0]}return await v(t)}catch{return await v(t)}}function v(t){return process.platform==="win32"?F(t):N(t)}async function B(t,e=["--version"],n=I){const r=l(c),i=process.platform==="win32";try{const s=i&&/\s/.test(t)&&!t.startsWith('"')?`"${t}"`:t,{stdout:o,stderr:u}=await r(s,e,{timeout:n,encoding:"utf-8",...i?{shell:!0}:{}});return{version:(o||u||"").trim().split(`
|
|
2
|
+
`)[0]?.trim()||null||null}}catch(s){const o=s;return o.killed||o.code==="ETIMEDOUT"?{version:null,error:{code:"version_timeout",message:"version check timed out"}}:{version:null,error:{code:"cli_error",message:o.message??String(s)}}}}export{y as findPosixExecutableInDirs,x as findWindowsExecutableInDirs,B as getCliVersion,z as invalidateCliPathCache,q as resolveCliPath,N as resolvePosixInstalledCli,E as resolveViaNpmGlobalBin,F as resolveWindowsInstalledCli};
|
package/dist/grix.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),
|
|
2
|
+
import x from"node:path";import{writeFileSync as C}from"node:fs";import{Manager as V}from"./manager.js";import{ensureGrixDirs as X,initLogger as J,log as o,installProcessLogRotation as K,setConsoleOutput as q}from"./core/log/index.js";import{HealthServer as Y,bindPortOrFail as U}from"./core/runtime/index.js";import{writePidFile as z,removePidFile as S,readDaemonPid as Q}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as Z}from"./service/service-manager.js";import{killProcessesByCommandLine as ee,isWindowsElevated as te}from"./service/process-control.js";import{acquireDaemonLock as oe,isLockHolderSameProcess as re,readDaemonLock as ne,releaseDaemonLock as R}from"./runtime/daemon-lock.js";import{writeDaemonStatus as I,removeDaemonStatus as ae}from"./runtime/service-state.js";import{AdminServer as se,generateToken as ie,writeTokenFile as ce}from"./core/admin/index.js";import{initSentry as le,closeSentry as b,reportFatal as T}from"./core/observability/sentry.js";import{initProxyManager as de,getProxyManager as m,relayHostForClientType as pe}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as me,disableAllRelayAndRestart as ge,setOneAgentRelayAndRestart as ue}from"./core/proxy/relay-orchestration.js";import{resolveClientVersion as fe}from"./core/util/client-version.js";import{parseCliArgs as he}from"./core/util/cli-args.js";const{command:u,flags:g,unknownFlags:F}=he(process.argv.slice(2));if(F.length>0&&(console.error(`Unknown option${F.length>1?"s":""}: ${F.map(t=>`--${t}`).join(", ")}
|
|
3
|
+
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),g.version&&(console.log(fe()),process.exit(0)),g.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
4
4
|
|
|
5
5
|
Usage: grix-connector <command> [options]
|
|
6
6
|
|
|
@@ -34,8 +34,8 @@ 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)),u==="reload"){const t=
|
|
37
|
+
`),process.exit(0)),u==="reload"){const t=Q();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 d=ne(v().daemonLockFile);d&&d.pid===t&&!re(d)&&(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(d){console.error(`reload failed: ${d instanceof Error?d.message:d}`),process.exit(1)}}const N=["start","stop","restart","status"];if(u&&N.includes(u)){process.platform==="win32"&&["start","stop","restart"].includes(u)&&!te()&&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
|
-
`),
|
|
39
|
+
right-click the terminal and select "Run as administrator".`);const t=v(),d=g["config-dir"]??(g.profile?x.join(t.configDir,g.profile):void 0),f=x.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),s=new Z({cliPath:f,nodePath:process.execPath});try{let l;switch(u){case"start":(await s.status({rootDir:t.rootDir})).installed?l=await s.start({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"stop":l=await s.stop({rootDir:t.rootDir});break;case"restart":(await s.status({rootDir:t.rootDir})).installed?l=await s.restart({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"status":l=await s.status({rootDir:t.rootDir});break}console.log(JSON.stringify(l,null,2)),process.exit(0)}catch(l){console.error(`${u} failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}else u&&(console.error(`Unknown command: ${u}
|
|
40
|
+
Valid commands: ${N.join(", ")}`),process.exit(1));const i=v(),ye=g["config-dir"]??(g.profile?`${i.configDir}/${g.profile}`:void 0),n=new V,D=new Y,H=ie(),h=new se(H);let $=!1;async function L(t){process.stderr.write(t.message+`
|
|
41
|
+
`),o.error("main",t.message.replace(/\n/g," \u2014 ")),await I(i.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)}async function A(t){if($)return;$=!0,o.info("main",`Received ${t}, shutting down...`),D.markShuttingDown();const d=setTimeout(()=>{o.error("main","Shutdown timed out, forcing exit"),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)},1e4);try{await n.stop(),await m()?.stop().catch(()=>{}),await h.stop(),await D.stop(),await b(),await R(i.daemonLockFile),await ae(i.daemonStatusFile).catch(()=>{}),clearTimeout(d),S(),o.info("main","Shutdown complete"),process.exit(0)}catch(f){o.error("main",`Shutdown error: ${f}`),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)}}async function we(){X(),J(),await le(),K(i.stdoutLogFile,i.stderrLogFile),q(!1),process.platform==="win32"&&await ee("GrixConnectorDaemon",{platform:"win32"});try{await oe(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}z(),o.info("main",`grix-connector starting (PID ${process.pid})`),await I(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(g["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await U({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:c=>D.start(c)});e&&await L(e)}const d=x.join(i.dataDir,"health-port");C(d,String(t),"utf-8"),process.on("SIGINT",()=>A("SIGINT")),process.on("SIGTERM",()=>A("SIGTERM")),process.on("SIGHUP",()=>{$||(o.info("main","Received SIGHUP, reloading config..."),n.reload().then(e=>o.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>o.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let f="",s=0,l;process.on("uncaughtException",e=>{const c=e instanceof Error?e.stack??e.message:String(e);c===f?(s++,(s<=3||s%100===0)&&o.error("main",`Uncaught exception (x${s}): ${c}`)):(s>3&&o.error("main",`Previous exception repeated ${s} times total`),f=c,s=1,o.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),l||(l=setTimeout(()=>{s>3&&o.error("main",`Previous exception repeated ${s} times total`),f="",s=0,l=void 0},1e4).unref())),!_(e)&&(T(e,"uncaughtException"),A("uncaughtException"))}),process.on("unhandledRejection",e=>{o.error("main",`Unhandled rejection: ${e}`),!_(e)&&(T(e,"unhandledRejection"),A("unhandledRejection"))});const w=de(i.dataDir);await w.clearRuntimeState();const P=w.getRelayAgents();if(P.length>0)try{await w.startIfAnyRelayAgent(),o.info("main",`mitm proxy started for relay agents: ${P.join(", ")} (per-agent; others stay direct)`)}catch(e){o.error("main",`mitm proxy FAILED to start (relay agents: ${P.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 o.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');D.setStatusProvider(()=>n.getAgentsStatus()),await n.start(ye);const O=parseInt(g["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);if(h.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e),reload:()=>n.reload()}),h.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),h.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,c)=>n.probeOne(e,c)}),h.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)}),m()){const e=async()=>{const r=m();r&&await me(r,n)&&o.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};n.setRelayEnvSettledHandler(()=>{e().catch(r=>{o.warn("main",`failed to stop idle mitm proxy: ${r}`)})});const c=()=>{const r=m(),a=r.getRelayAgents(),p=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:n.getAgentsWithStaleRelayEnv(),degradedRelayAgents:p,runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}};h.setProxyHandler({status:()=>c(),setAgentRelay:async(r,a)=>{const p=m();let y;if(a){const k=n.getAgentsStatus().find(W=>W.name===r);if(!k)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});if(y=pe(k.clientType),!y)throw Object.assign(new Error(`client type "${k.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"})}const{restarted:M,busy:B}=await ue(p,n,{info:E=>o.info("main",E),warn:E=>o.warn("main",E)},{agentName:r,enabled:a,relayHost:y});return{...c(),restarted:M,busy:B}},disableAll:async()=>{const r=m();return await ge(r,n,{info:a=>o.info("main",a),warn:a=>o.warn("main",a)}),c()},setRoute:async(r,a)=>{const p=a,y=m();return y.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 y.persistConfig(),c()},deleteRoute:async r=>{const a=m();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=m();return a.setDefaultRouteKey(r),await a.persistConfig(),c()},setInterceptHosts:async r=>{const a=m();return a.setInterceptHosts(r),await a.persistConfig(),c()}})}{const e=await U({label:"admin",port:O,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:c=>h.start(c)});e&&await L(e)}const G=x.join(i.dataDir,"admin-token"),j=x.join(i.dataDir,"admin-port");ce(G,H),C(j,String(O),"utf-8"),await I(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),o.info("main","grix-connector ready")}we().catch(async t=>{o.error("main",`Fatal: ${t}`),T(t,"startup"),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)});const xe=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function _(t){return t instanceof Error&&"code"in t?xe.has(t.code):!1}
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|