grix-connector 1.3.0 → 2.0.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.
Files changed (48) hide show
  1. package/dist/adapter/acp/acp-adapter.js +7 -7
  2. package/dist/adapter/agy/agy-adapter.js +3 -2
  3. package/dist/adapter/claude/claude-adapter.js +17 -17
  4. package/dist/adapter/claude/claude-bridge-server.js +1 -0
  5. package/dist/adapter/claude/claude-tools.js +1 -0
  6. package/dist/adapter/claude/claude-worker-client.js +1 -0
  7. package/dist/adapter/claude/mcp-http-launcher.js +2 -0
  8. package/dist/adapter/claude/result-timeout.js +1 -0
  9. package/dist/adapter/codewhale/codewhale-adapter.js +3 -3
  10. package/dist/adapter/codex/codex-bridge.js +5 -5
  11. package/dist/adapter/cursor/cursor-adapter.js +5 -5
  12. package/dist/adapter/deepseek/deepseek-adapter.js +3 -3
  13. package/dist/adapter/opencode/opencode-adapter.js +4 -4
  14. package/dist/adapter/openhuman/openhuman-adapter.js +3 -3
  15. package/dist/adapter/pi/pi-adapter.js +5 -5
  16. package/dist/adapter/qwen/index.js +1 -0
  17. package/dist/adapter/qwen/qwen-adapter.js +4 -0
  18. package/dist/aibot/client.js +1 -0
  19. package/dist/aibot/index.js +1 -0
  20. package/dist/aibot/types.js +0 -0
  21. package/dist/core/file-ops/handler.js +1 -0
  22. package/dist/core/file-ops/list-files.js +1 -0
  23. package/dist/core/file-ops/types.js +0 -0
  24. package/dist/core/installer/npm-registry.js +2 -2
  25. package/dist/core/observability/sentry.js +1 -0
  26. package/dist/core/upgrade/npm-upgrader.js +2 -2
  27. package/dist/default-skills/index.js +1 -1
  28. package/dist/grix.js +4 -4
  29. package/dist/log.js +3 -0
  30. package/dist/main.js +31 -0
  31. package/dist/manager.js +2 -2
  32. package/dist/mcp/stream-http/config.js +1 -0
  33. package/dist/mcp/stream-http/connection-binding.js +1 -0
  34. package/dist/mcp/stream-http/event-tool-executor.js +1 -0
  35. package/dist/mcp/stream-http/gateway.js +1 -0
  36. package/dist/mcp/stream-http/index.js +1 -0
  37. package/dist/mcp/stream-http/security.js +1 -0
  38. package/dist/mcp/stream-http/session-manager.js +1 -0
  39. package/dist/mcp/stream-http/tool-executor.js +1 -0
  40. package/dist/mcp/stream-http/tool-registry.js +1 -0
  41. package/dist/mcp/stream-http/tool-schemas.js +1 -0
  42. package/dist/session/index.js +1 -0
  43. package/dist/session/manager.js +1 -0
  44. package/dist/transport/index.js +1 -0
  45. package/dist/transport/json-rpc.js +3 -0
  46. package/package.json +2 -1
  47. package/scripts/install-guardian.sh +0 -0
  48. package/scripts/upgrade-guardian.sh +0 -0
@@ -1,9 +1,9 @@
1
- import{EventEmitter as g}from"node:events";import{stat as k}from"node:fs/promises";import{existsSync as y,mkdirSync as b,readFileSync as E,writeFileSync as $}from"node:fs";import{join as I,resolve as P,dirname as A}from"node:path";import{homedir as _}from"node:os";import{fileURLToPath as C}from"node:url";import{resolveCommandPath as O,spawnCommand as M,killProcessGroup as T,hasChildProcesses as F}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{buildSimpleProbeReport as j}from"../shared/probe-util.js";import{OpenCodeTransport as N}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as S}from"../../core/protocol/index.js";class D extends g{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const H=200,L=2e3,U=12e4,J=6e5,x=3e4,G="127.0.0.1",q=0,X=1e3;class oe extends g{type="opencode";config;callbacks;options;process=null;transport=new N;alive=!1;stopped=!1;internalApi=null;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{}}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??G,s=this.options.port??q,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{T(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{T(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd(),t=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.getSession(t.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e)}sendPrompt(e){const s=new D(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){n.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t),this.startComposing(t),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,o).catch(a=>{n.error("opencode-adapter",`prompt_async failed: ${a}`),this.finishRun("failed",String(a))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=P(C(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?F(e,[e]):!1}async probe(e){const s=this.getStatus();return j(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(a,c)=>this.callbacks.agentInvoke(a,c)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||I(_(),".config"),t=I(s,"opencode","opencode.json");b(A(t),{recursive:!0});let i={};try{y(t)&&(i=JSON.parse(E(t,"utf8")))}catch{}const o=i.mcp&&typeof i.mcp=="object"?i.mcp:{};o[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=o,$(t,`${JSON.stringify(i,null,2)}
2
- `,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await k(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,o)=>{const a=this.config.command||"opencode",f=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],h={...process.env,...this.config.env},p={};if(this.options.model){const[r,...l]=this.options.model.split("/");r&&l.length>0&&(p.agents={coder:{model:l.join("/"),maxTokens:16e3}})}this.options.permissionPolicy==="fullAuto"&&(p.permission={edit:"allow",bash:"allow",webfetch:"allow",doom_loop:"allow",external_directory:"allow"}),Object.keys(p).length>0&&(h.OPENCODE_CONFIG_CONTENT=JSON.stringify(p));const m=O(a,typeof h.PATH=="string"?h.PATH:void 0);n.info("opencode-adapter",`Spawning: ${m} ${f.join(" ")} (cwd=${t})`),this.process=M(m,f,{env:h,cwd:t}).process;let v="",d=!1;const u=setTimeout(()=>{d||(d=!0,o(new Error(`opencode serve did not start after ${x/1e3}s`)))},x);this.process.stdout?.on("data",r=>{if(v+=r.toString(),!d)for(const l of v.split(`
3
- `)){const R=l.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(u),d=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",r=>{const l=r.toString().trim();l&&n.info("opencode-adapter",`[stderr] ${l}`)}),this.process.on("error",r=>{n.error("opencode-adapter",`Spawn error: ${r.message}`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,o(r))}),this.process.on("exit",r=>{n.info("opencode-adapter",`Process exited (code=${r})`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),d?this.stopped||this.emit("exit",r??1):(d=!0,o(new Error(`opencode serve exited with code ${r}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e);if(s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId),s.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const t=s?.cwd??this.resolveCwd(),i=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:i.id,cwd:t}),n.info("opencode-adapter",`Created OC session ${i.id} for aibot=${e}`),i.id}async sendOcPrompt(e,s){const t=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=t),await this.transport.sendPromptAsync(t,{parts:[{type:"text",text:s}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;s.type==="text"?t?this.appendText(t):s.text&&this.appendText(s.text):s.type==="reasoning"?t&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(s.role==="assistant"){const t=s;t.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`)}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const s=e.error,t=s?typeof s=="object"&&"message"in s?s.message:JSON.stringify(s):"unknown session error";n.error("opencode-adapter",`Session error: ${t}`),this.flushTextBuffer(),this.finishRun("failed",t);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(t.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=t.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(a=>{n.warn("opencode-adapter",`Auto-approve failed: ${a}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(s,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=this.pendingPermissions.get(this.activeRun.eventId);if(!s)return{handled:!1,kind:""};const{permissionId:t,ocSessionId:i}=s,a=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,t,a).then(()=>{n.info("opencode-adapter",`Permission ${a}: ${t}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,s){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>X){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,o=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const a=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(()=>{this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const c of S(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,c,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}a(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun=null,e&&this.emit("eventDone",e)}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=L){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},H))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of S(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?J:U;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
1
+ import{EventEmitter as I}from"node:events";import{stat as w}from"node:fs/promises";import{existsSync as y,mkdirSync as b,readFileSync as E,writeFileSync as $}from"node:fs";import{join as m,resolve as P,dirname as A}from"node:path";import{homedir as _}from"node:os";import{fileURLToPath as C}from"node:url";import{resolveCommandPath as O,spawnCommand as M,killProcessGroup as T,hasChildProcesses as F}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as D}from"../shared/probe-util.js";import{OpenCodeTransport as N}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as S}from"../../core/protocol/index.js";class H extends I{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const L=200,U=2e3,J=12e4,G=6e5,x=3e4,q="127.0.0.1",X=0,W=1e3;class ae extends I{type="opencode";config;callbacks;options;process=null;transport=new N;alive=!1;stopped=!1;internalApi=null;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{}}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??q,s=this.options.port??X,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{T(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{T(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd(),t=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.getSession(t.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e)}sendPrompt(e){const s=new H(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){n.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t),this.startComposing(t),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,o).catch(r=>{n.error("opencode-adapter",`prompt_async failed: ${r}`),this.finishRun("failed",String(r))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=P(C(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?F(e,[e]):!1}async probe(e){const s=this.getStatus();return D(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(p,h)=>this.callbacks.agentInvoke(p,h)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||m(_(),".config"),t=m(s,"opencode","opencode.json");b(A(t),{recursive:!0});let i={};try{y(t)&&(i=JSON.parse(E(t,"utf8")))}catch{}const o=i.mcp&&typeof i.mcp=="object"?i.mcp:{};o[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=o,$(t,`${JSON.stringify(i,null,2)}
2
+ `,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`);const r=m(s,"opencode","skills"),c=j(r);c.length>0&&n.info("opencode-adapter",`Synced connector skills to ${r}: [${c.join(", ")}]`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await w(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,o)=>{const r=this.config.command||"opencode",p=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],h={...process.env,...this.config.env},u={};if(this.options.model){const[a,...l]=this.options.model.split("/");a&&l.length>0&&(u.agents={coder:{model:l.join("/"),maxTokens:16e3}})}this.options.permissionPolicy==="fullAuto"&&(u.permission={edit:"allow",bash:"allow",webfetch:"allow",doom_loop:"allow",external_directory:"allow"}),Object.keys(u).length>0&&(h.OPENCODE_CONFIG_CONTENT=JSON.stringify(u));const v=O(r,typeof h.PATH=="string"?h.PATH:void 0);n.info("opencode-adapter",`Spawning: ${v} ${p.join(" ")} (cwd=${t})`),this.process=M(v,p,{env:h,cwd:t}).process;let g="",d=!1;const f=setTimeout(()=>{d||(d=!0,o(new Error(`opencode serve did not start after ${x/1e3}s`)))},x);this.process.stdout?.on("data",a=>{if(g+=a.toString(),!d)for(const l of g.split(`
3
+ `)){const R=l.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(f),d=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",a=>{const l=a.toString().trim();l&&n.info("opencode-adapter",`[stderr] ${l}`)}),this.process.on("error",a=>{n.error("opencode-adapter",`Spawn error: ${a.message}`),clearTimeout(f),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,o(a))}),this.process.on("exit",a=>{n.info("opencode-adapter",`Process exited (code=${a})`),clearTimeout(f),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),d?this.stopped||this.emit("exit",a??1):(d=!0,o(new Error(`opencode serve exited with code ${a}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e);if(s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId),s.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const t=s?.cwd??this.resolveCwd(),i=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:i.id,cwd:t}),n.info("opencode-adapter",`Created OC session ${i.id} for aibot=${e}`),i.id}async sendOcPrompt(e,s){const t=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=t),await this.transport.sendPromptAsync(t,{parts:[{type:"text",text:s}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;s.type==="text"?t?this.appendText(t):s.text&&this.appendText(s.text):s.type==="reasoning"?t&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(s.role==="assistant"){const t=s;t.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`)}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const s=e.error,t=s?typeof s=="object"&&"message"in s?s.message:JSON.stringify(s):"unknown session error";n.error("opencode-adapter",`Session error: ${t}`),this.flushTextBuffer(),this.finishRun("failed",t);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(t.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=t.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(r=>{n.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(s,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=this.pendingPermissions.get(this.activeRun.eventId);if(!s)return{handled:!1,kind:""};const{permissionId:t,ocSessionId:i}=s,r=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,t,r).then(()=>{n.info("opencode-adapter",`Permission ${r}: ${t}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,s){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>W){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,o=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(()=>{this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const c of S(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,c,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}r(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun=null,e&&this.emit("eventDone",e)}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=U){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},L))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of S(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?G:J;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
4
4
  `)+`
5
5
 
6
6
  `+s),s}buildPromptTextFromEvent(e){let s=e.content||"";if(e.context_messages_json)try{const t=JSON.parse(e.context_messages_json);Array.isArray(t)&&t.length>0&&(s=t.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
7
7
  `)+`
8
8
 
9
- `+s)}catch{}return s}}export{oe as OpenCodeAdapter};
9
+ `+s)}catch{}return s}}export{ae as OpenCodeAdapter};
@@ -1,8 +1,8 @@
1
- import{EventEmitter as m}from"node:events";import{stat as R}from"node:fs/promises";import{mkdirSync as I}from"node:fs";import{homedir as d}from"node:os";import{join as u,resolve as _}from"node:path";import{fileURLToPath as g}from"node:url";import{randomUUID as E}from"node:crypto";import{resolveCommandPath as k,spawnCommand as x,killProcessGroup as f,hasChildProcesses as w}from"../../core/runtime/spawn.js";import{InternalApiServer as S}from"../../core/mcp/internal-api-server.js";import{buildSimpleProbeReport as b}from"../shared/probe-util.js";import{OpenHumanTransport as y,readBearerToken as $}from"./openhuman-transport.js";import{log as i}from"../../core/log/index.js";import{splitTextForAibotProtocol as A}from"../../core/protocol/index.js";class C extends m{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){i.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const L=200,P=2e3,F=12e4,M=500,v=3e4,N=7788,D="127.0.0.1";class z extends m{type="openhuman";config;callbacks;options;process=null;transport=new y;alive=!1;stopped=!1;internalApi=null;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){await this.startInternalApiAndInjectMcp();const t=this.options.host??D,e=this.options.port??N;await this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),n=await $(s),r=`http://${t}:${e}`;if(await this.transport.connect(r,n),this.transport.on("event",a=>this.handleSocketEvent(a)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),i.info("openhuman-adapter","Session token stored")}catch(a){i.warn("openhuman-adapter",`Failed to store session token: ${a}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),i.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const t=this.process;try{f(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{f(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new C(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(n=>{this.activeRun&&(this.activeRun.requestId=n.request_id)}).catch(n=>{e.emitError(n instanceof Error?n:new Error(String(n)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:n}=t,r=this.buildPromptTextFromEvent(t);if(this.completedEvents.has(e)){i.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){i.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(i.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),i.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,r).then(a=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=a.request_id,a.accepted||(i.warn("openhuman-adapter",`web_chat not accepted: ${a.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(a=>{i.error("openhuman-adapter",`web_chat failed: ${a}`),this.finishRun("failed",String(a))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(i.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const t=_(g(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?w(t,[t]):!1}async probe(t){const e=this.getStatus();return b(this.config.command||"openhuman-core",{alive:e.alive,busy:e.busy,started:!!this.process},t)}async startInternalApiAndInjectMcp(){try{this.internalApi=new S,this.internalApi.setInvokeHandler(async(h,l)=>this.callbacks.agentInvoke(h,l)),await this.internalApi.start(0),i.info("openhuman-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=u(d(),".openhuman","users","local","workspace","mcp_clients"),s=u(e,"mcp_clients.db");I(e,{recursive:!0});const{execFileSync:n}=await import("node:child_process"),r=E(),a=JSON.stringify(t.args),c=["CREATE TABLE IF NOT EXISTS mcp_servers (server_id TEXT PRIMARY KEY, qualified_name TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, icon_url TEXT, command_kind TEXT NOT NULL DEFAULT 'node', command TEXT NOT NULL, args_json TEXT NOT NULL DEFAULT '[]', env_keys_json TEXT NOT NULL DEFAULT '[]', config_json TEXT, installed_at INTEGER NOT NULL, last_connected_at INTEGER);","DELETE FROM mcp_servers WHERE qualified_name = 'grix-connector-tools';",`INSERT INTO mcp_servers (server_id, qualified_name, display_name, description, command_kind, command, args_json, installed_at) VALUES ('${r}', 'grix-connector-tools', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${a.replace(/'/g,"''")}', ${Date.now()});`].join(`
2
- `);n("sqlite3",[s,c],{timeout:1e4,stdio:"ignore"}),i.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`)}catch(t){i.warn("openhuman-adapter",`Failed to inject MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnProcess(t,e){const s=this.config.command||"openhuman-core",n=k(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),r=this.config.args??[],c=r.includes("run")||r.includes("serve")?r:["run","--host",t,"--port",String(e),...r],h={...process.env,...this.config.env},l=this.resolveCwd();i.info("openhuman-adapter",`Spawning: ${n} ${c.join(" ")}`);try{if(!(await R(l)).isDirectory())throw new Error(`Bound path is not a directory: ${l}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${l}. Please rebind with /grix open <valid-directory>.`):o}try{this.process=x(n,c,{env:h,cwd:l}).process}catch(o){throw i.error("openhuman-adapter",`Spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{i.error("openhuman-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${o.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{i.info("openhuman-adapter",`Process exited (code=${o})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${o})`),this.clearRun()),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const p=o.toString().trim();p&&i.info("openhuman-adapter",`[stderr] ${p}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,n=Date.now()+v;for(;Date.now()<n;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(r=>setTimeout(r,M))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${v/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{i.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(i.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{i.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const a=this.completedEvents.values();for(let h=0;h<500;h++)a.next();const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.emit("eventDone",s.eventId),this.stopComposing(),this.stopIdleTimer();const n=++s.chunkSeq,r=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n,r).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,r),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,r),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const t=this.activeRun?.eventId;this.activeRun=null,t&&this.emit("eventDone",t)}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=P){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},L))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of A(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){}stopComposing(){}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{i.error("openhuman-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},F))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,d()):u(d(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(n=>`[context] ${n.senderId}: ${n.content}`).join(`
1
+ import{EventEmitter as m}from"node:events";import{stat as R}from"node:fs/promises";import{mkdirSync as I}from"node:fs";import{homedir as u}from"node:os";import{join as p,resolve as _}from"node:path";import{fileURLToPath as g}from"node:url";import{randomUUID as k}from"node:crypto";import{resolveCommandPath as E,spawnCommand as x,killProcessGroup as f,hasChildProcesses as w}from"../../core/runtime/spawn.js";import{InternalApiServer as S}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as b}from"../../default-skills/index.js";import{buildSimpleProbeReport as y}from"../shared/probe-util.js";import{OpenHumanTransport as $,readBearerToken as A}from"./openhuman-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as C}from"../../core/protocol/index.js";class L extends m{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){n.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const P=200,F=2e3,D=12e4,M=500,v=3e4,N=7788,B="127.0.0.1";class Z extends m{type="openhuman";config;callbacks;options;process=null;transport=new $;alive=!1;stopped=!1;internalApi=null;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){await this.startInternalApiAndInjectMcp();const t=this.options.host??B,e=this.options.port??N;await this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await A(s),r=`http://${t}:${e}`;if(await this.transport.connect(r,i),this.transport.on("event",o=>this.handleSocketEvent(o)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(o){n.warn("openhuman-adapter",`Failed to store session token: ${o}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),n.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const t=this.process;try{f(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{f(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new L(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(i=>{this.activeRun&&(this.activeRun.requestId=i.request_id)}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,r=this.buildPromptTextFromEvent(t);if(this.completedEvents.has(e)){n.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){n.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(n.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,r).then(o=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=o.request_id,o.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${o.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(o=>{n.error("openhuman-adapter",`web_chat failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(n.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const t=_(g(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?w(t,[t]):!1}async probe(t){const e=this.getStatus();return y(this.config.command||"openhuman-core",{alive:e.alive,busy:e.busy,started:!!this.process},t)}async startInternalApiAndInjectMcp(){try{this.internalApi=new S,this.internalApi.setInvokeHandler(async(a,d)=>this.callbacks.agentInvoke(a,d)),await this.internalApi.start(0),n.info("openhuman-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(u(),".openhuman","users","local","workspace","mcp_clients"),s=p(e,"mcp_clients.db");I(e,{recursive:!0});const{execFileSync:i}=await import("node:child_process"),r=k(),o=JSON.stringify(t.args),l=["CREATE TABLE IF NOT EXISTS mcp_servers (server_id TEXT PRIMARY KEY, qualified_name TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, icon_url TEXT, command_kind TEXT NOT NULL DEFAULT 'node', command TEXT NOT NULL, args_json TEXT NOT NULL DEFAULT '[]', env_keys_json TEXT NOT NULL DEFAULT '[]', config_json TEXT, installed_at INTEGER NOT NULL, last_connected_at INTEGER);","DELETE FROM mcp_servers WHERE qualified_name = 'grix-connector-tools';",`INSERT INTO mcp_servers (server_id, qualified_name, display_name, description, command_kind, command, args_json, installed_at) VALUES ('${r}', 'grix-connector-tools', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${o.replace(/'/g,"''")}', ${Date.now()});`].join(`
2
+ `);i("sqlite3",[s,l],{timeout:1e4,stdio:"ignore"}),n.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`);const c=p(u(),".openhuman","skills"),h=b(c);h.length>0&&n.info("openhuman-adapter",`Synced connector skills to ${c}: [${h.join(", ")}]`)}catch(t){n.warn("openhuman-adapter",`Failed to inject MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=E(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),r=this.config.args??[],l=r.includes("run")||r.includes("serve")?r:["run","--host",t,"--port",String(e),...r],c={...process.env,...this.config.env},h=this.resolveCwd();n.info("openhuman-adapter",`Spawning: ${i} ${l.join(" ")}`);try{if(!(await R(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(a){throw String(a?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):a}try{this.process=x(i,l,{env:c,cwd:h}).process}catch(a){throw n.error("openhuman-adapter",`Spawn threw: ${a}`),this.alive=!1,a}this.process.on("error",a=>{n.error("openhuman-adapter",`Spawn error: ${a.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",a=>{n.info("openhuman-adapter",`Process exited (code=${a})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),this.stopped||this.emit("exit",a??1)}),this.process.stderr?.on("data",a=>{const d=a.toString().trim();d&&n.info("openhuman-adapter",`[stderr] ${d}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+v;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(r=>setTimeout(r,M))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${v/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{n.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(n.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{n.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const o=this.completedEvents.values();for(let c=0;c<500;c++)o.next();const l=[...this.completedEvents].slice(-500);this.completedEvents=new Set(l)}this.activeRun=null,this.emit("eventDone",s.eventId),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,r=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,r).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,r),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,r),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const t=this.activeRun?.eventId;this.activeRun=null,t&&this.emit("eventDone",t)}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=F){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},P))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of C(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){}stopComposing(){}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{n.error("openhuman-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},D))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,u()):p(u(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
3
3
  `)+`
4
4
 
5
5
  `+e),e}buildPromptTextFromEvent(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(r=>`[context] ${r.sender_id??"unknown"}: ${r.content}`).join(`
6
6
  `)+`
7
7
 
8
- `+e)}catch{}return e}}export{z as OpenHumanAdapter};
8
+ `+e)}catch{}return e}}export{Z as OpenHumanAdapter};
@@ -1,10 +1,10 @@
1
- import{EventEmitter as p}from"node:events";import{stat as v}from"node:fs/promises";import{mkdirSync as g,writeFileSync as S,unlinkSync as I,statSync as x}from"node:fs";import{join as u,resolve as b}from"node:path";import{fileURLToPath as T}from"node:url";import{tmpdir as y}from"node:os";import{resolveCommandPath as k,spawnCommand as E,killProcessGroup as m,hasChildProcesses as w}from"../../core/runtime/spawn.js";import{InternalApiServer as _}from"../../core/mcp/internal-api-server.js";import{PiTransport as P}from"./pi-transport.js";import{log as a}from"../../core/log/index.js";import{scanSkills as $}from"../claude/skill-scanner.js";import{SessionBindingStore as A}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as C}from"../../core/protocol/index.js";import{buildSimpleProbeReport as R}from"../shared/probe-util.js";class M extends p{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){a.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const B=12e4,F=500,D=2e3;class f extends p{type="pi";config;callbacks;process=null;transport=new P;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;constructor(t,e){super(),this.config=t,this.callbacks=e;const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof A?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),a.info("pi-adapter",`Ready (pid=${this.process?.pid})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{I(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{m(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{m(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return a.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}async destroySession(t){this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new M(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(a.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(o=>{a.error("pi-adapter",`steer failed: ${o}`),this.callbacks.sendEventResult(e,"failed",String(o))})):(a.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(o=>{o.success||(a.error("pi-adapter",`prompt rejected: ${o.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",o.error),this.clearActive())}).catch(o=>{a.error("pi-adapter",`prompt error: ${o}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(o)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){a.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const{provider:s,modelId:i}=t.params??{};if(s&&i)try{return await this.transport.send("set_model",{provider:s,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:s,modelId:i}),{handled:!0,kind:"set_model"}}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}return{handled:!1,kind:""}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=b(T(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?w(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await R(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=x(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const r=i.slice(0,c),d=i.slice(c+1);if(!d)return{status:"failed",message:"Format: provider:model_id"};const l=await this.transport.send("set_model",{provider:r,modelId:d});return l?.success?{status:"ok",message:`Model set to ${r}:${d}`}:{status:"failed",message:`Failed to set model: ${l?.error??"unknown error"}`}}const o=(await this.transport.send("get_available_models"))?.data?.models;return o&&o.length>0?{status:"ok",message:`Available models:
2
- ${o.map(r=>` ${r.provider??"unknown"}:${r.id} (${r.name??r.id})`).join(`
3
- `)}`,data:o}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=$({mode:"pi"}),n=i.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""} [${o.source}]: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
4
- `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new _,this.internalApi.setInvokeHandler(async(s,i)=>this.callbacks.agentInvoke(s,i)),await this.internalApi.start(0),a.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=u(y(),"grix-pi-mcp");g(e,{recursive:!0}),this.mcpConfigPath=u(e,`mcp-${process.pid}-${Date.now()}.json`),S(this.mcpConfigPath,JSON.stringify({mcpServers:{[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),a.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`)}catch(t){a.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=k(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(r=>r.startsWith("--mode"))?[...s]:["--mode","rpc",...s];this.mcpConfigPath&&!n.some(r=>r==="--mcp-config")&&n.push("--mcp-config",this.mcpConfigPath);const o={...process.env,...this.config.env},c=this.resolveCwd();a.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await v(c)).isDirectory())throw new Error(`Bound path is not a directory: ${c}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${c}. Please rebind with /grix open <valid-directory>.`):r}try{this.process=E(e,n,{env:o,cwd:c}).process}catch(r){throw a.error("pi-adapter",`PI spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{a.error("pi-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${r.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{a.info("pi-adapter",`PI process exited (code=${r})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${r})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&a.info("pi-adapter",`[pi stderr] ${d}`)}),this.transport.on("event",r=>this.handlePiEvent(r)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const o=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(o,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,o=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(o,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=G(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(a.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.activeEventId){const s=this.activeEventId,i=this.activeSessionId??"",n=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(s,i,n)}else this.clearActive();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(a.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=D){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},F))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of C(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
1
+ import{EventEmitter as u}from"node:events";import{stat as v}from"node:fs/promises";import{mkdirSync as g,writeFileSync as S,unlinkSync as I,statSync as x}from"node:fs";import{join as h,resolve as b}from"node:path";import{fileURLToPath as y}from"node:url";import{tmpdir as T,homedir as k}from"node:os";import{resolveCommandPath as E,spawnCommand as w,killProcessGroup as m,hasChildProcesses as _}from"../../core/runtime/spawn.js";import{InternalApiServer as P}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as $}from"../../default-skills/index.js";import{PiTransport as A}from"./pi-transport.js";import{log as o}from"../../core/log/index.js";import{scanSkills as C}from"../claude/skill-scanner.js";import{SessionBindingStore as R}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as M}from"../../core/protocol/index.js";import{buildSimpleProbeReport as B}from"../shared/probe-util.js";class F extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){o.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const D=12e4,G=500,j=2e3;class f extends u{type="pi";config;callbacks;process=null;transport=new A;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;constructor(t,e){super(),this.config=t,this.callbacks=e;const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof R?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),o.info("pi-adapter",`Ready (pid=${this.process?.pid})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{I(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{m(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{m(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return o.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}async destroySession(t){this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new F(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(o.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(a=>{o.error("pi-adapter",`steer failed: ${a}`),this.callbacks.sendEventResult(e,"failed",String(a))})):(o.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(a=>{a.success||(o.error("pi-adapter",`prompt rejected: ${a.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",a.error),this.clearActive())}).catch(a=>{o.error("pi-adapter",`prompt error: ${a}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(a)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){o.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const{provider:s,modelId:i}=t.params??{};if(s&&i)try{return await this.transport.send("set_model",{provider:s,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:s,modelId:i}),{handled:!0,kind:"set_model"}}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}return{handled:!1,kind:""}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=b(y(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?_(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await B(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=x(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const r=i.slice(0,c),d=i.slice(c+1);if(!d)return{status:"failed",message:"Format: provider:model_id"};const p=await this.transport.send("set_model",{provider:r,modelId:d});return p?.success?{status:"ok",message:`Model set to ${r}:${d}`}:{status:"failed",message:`Failed to set model: ${p?.error??"unknown error"}`}}const a=(await this.transport.send("get_available_models"))?.data?.models;return a&&a.length>0?{status:"ok",message:`Available models:
2
+ ${a.map(r=>` ${r.provider??"unknown"}:${r.id} (${r.name??r.id})`).join(`
3
+ `)}`,data:a}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=C({mode:"pi"}),n=i.map(a=>`- ${a.name}${a.trigger?` (${a.trigger})`:""} [${a.source}]: ${a.description}`);return{status:"ok",message:n.length>0?n.join(`
4
+ `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new P,this.internalApi.setInvokeHandler(async(n,a)=>this.callbacks.agentInvoke(n,a)),await this.internalApi.start(0),o.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=h(T(),"grix-pi-mcp");g(e,{recursive:!0}),this.mcpConfigPath=h(e,`mcp-${process.pid}-${Date.now()}.json`),S(this.mcpConfigPath,JSON.stringify({mcpServers:{[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),o.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const s=h(k(),".pi","agent","skills"),i=$(s);i.length>0&&o.info("pi-adapter",`Synced connector skills to ${s}: [${i.join(", ")}]`)}catch(t){o.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=E(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(r=>r.startsWith("--mode"))?[...s]:["--mode","rpc",...s];this.mcpConfigPath&&!n.some(r=>r==="--mcp-config")&&n.push("--mcp-config",this.mcpConfigPath);const a={...process.env,...this.config.env},c=this.resolveCwd();o.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await v(c)).isDirectory())throw new Error(`Bound path is not a directory: ${c}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${c}. Please rebind with /grix open <valid-directory>.`):r}try{this.process=w(e,n,{env:a,cwd:c}).process}catch(r){throw o.error("pi-adapter",`PI spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{o.error("pi-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${r.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{o.info("pi-adapter",`PI process exited (code=${r})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${r})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&o.info("pi-adapter",`[pi stderr] ${d}`)}),this.transport.on("event",r=>this.handlePiEvent(r)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const a=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(a,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,a=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(a,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=N(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(o.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.activeEventId){const s=this.activeEventId,i=this.activeSessionId??"",n=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(s,i,n)}else this.clearActive();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(o.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=j){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},G))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of M(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
5
5
  `)+`
6
6
 
7
7
  `+e)}catch{}return e}buildPromptTextFromRequest(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
8
8
  `)+`
9
9
 
10
- `+e),e}clearActive(){const t=this.activeEventId;this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear(),t&&this.emit("eventDone",t)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{a.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}a.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{if(this.doneGuardTimer=null,!this.activeEventId||!this.isStreaming)return;a.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer();const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)},f.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{a.error("pi-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},B))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&a.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function G(h){if(!h||typeof h!="object")return"";const t=h.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{f as PiAdapter};
10
+ `+e),e}clearActive(){const t=this.activeEventId;this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear(),t&&this.emit("eventDone",t)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{o.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}o.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{if(this.doneGuardTimer=null,!this.activeEventId||!this.isStreaming)return;o.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer();const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)},f.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{o.error("pi-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},D))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&o.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function N(l){if(!l||typeof l!="object")return"";const t=l.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{f as PiAdapter};
@@ -0,0 +1 @@
1
+ import{QwenAdapter as e}from"./qwen-adapter.js";export{e as QwenAdapter};
@@ -0,0 +1,4 @@
1
+ import l from"node:path";import{fileURLToPath as m}from"node:url";import{EventEmitter as h}from"node:events";import{AgentProcess as f}from"../../agent/process.js";import{AcpClient as v,AcpAuthRequiredError as g}from"../../protocol/acp-client.js";import{AgentEventType as a}from"../../types/events.js";import{QuotedMessageStream as R}from"../../core/util/quoted-message-stream.js";import{InternalApiServer as A}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as b}from"../../core/persistence/event-results-store.js";import{log as r}from"../../core/log/index.js";const d=l.dirname(m(import.meta.url)),w=300*1e3,u=60*1e3,I=200;class T extends h{type="qwen";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;pendingApprovals=new Map;bindingStore;eventResults=null;currentAibotSessionId;stopped=!1;cwd;model;promptTimeoutMs;clientMsgSeq=0;deferredEvents=new Map;sessionBindings=new Map;constructor(e,t,s,i){super(),this.config=e,this.callbacks=t,this.bindingStore=s,this.cwd=e.cwd??process.cwd(),this.model=e.options?.model,this.promptTimeoutMs=e.options?.promptTimeoutMs??w,i&&(this.eventResults=new b(i))}async start(){const e=[...this.config.args??[],"--acp"];this.model&&e.push("--model",this.model);const t={command:this.config.command||"qwen",args:e,cwd:this.cwd,env:this.config.env},s=await this.startInternalApiAndMcp();this.agentProcess=new f;const i=await this.agentProcess.start(t);r.info("qwen-adapter","Qwen process started"),this.agentProcess.on("exit",n=>{this.stopped||(r.error("qwen-adapter",`Process exited unexpectedly (code=${n})`),this.activeRun&&this.finishRun("failed",`qwen process exited (code=${n})`),this.emit("exit",n))}),this.acpClient=new v,this.acpClient.on("event",n=>this.handleAcpEvent(n));const o=this.currentAibotSessionId?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0;try{await this.acpClient.connect({transport:i,initialMode:"bypass",mcpServers:s,sessionId:o,cwd:this.cwd}),r.info("qwen-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.currentAibotSessionId&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[n,c]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",c)}catch(n){if(n instanceof g){await this.handleAuthRequired(n);return}throw n}}async stop(){this.stopped=!0,this.deferredEvents.clear(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.timeoutTimer&&(clearTimeout(this.activeRun.timeoutTimer),this.activeRun.timeoutTimer=null),this.activeRun.firstResponseTimer&&(clearTimeout(this.activeRun.firstResponseTimer),this.activeRun.firstResponseTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.acpClient?.isAlive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new S(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.ping(e)??!1}getStatus(){return{alive:this.acpClient?.isAlive??!1,busy:this.activeRun!==null,sessions:this.acpClient?1:0}}getMcpConfig(){if(!this.internalApi)return null;const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}get pendingApprovalEntries(){return this.pendingApprovals}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};if(t==="exec_approve"||t==="exec_reject"){const i=String(s.tool_call_id??""),o=t==="exec_approve";if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"};const n=this.pendingApprovals.get(i);return n?(this.pendingApprovals.delete(i),this.acpClient&&this.acpClient.respondPermission(n,{behavior:o?"allow":"deny"}).catch(c=>{r.error("qwen-adapter",`Failed to respond to permission: ${c}`)}),this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${i}`),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async startInternalApiAndMcp(){try{this.internalApi=new A,this.internalApi.setInvokeHandler(async(t,s)=>this.callbacks.agentInvoke(t,s)),await this.internalApi.start(0),r.info("qwen-adapter",`Internal API started at ${this.internalApi.url}`);const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return[{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]}catch(e){r.error("qwen-adapter",`Failed to start MCP tools: ${e}`);return}}bindSession(e,t){return this.sessionBindings.get(e)?!1:(this.sessionBindings.set(e,t),this.bindingStore.set(e,t),this.acpClient?.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.acpClient?.isAlive&&this.callbacks.sendUpdateBindingCard(e,"connected",t),!0)}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}deliverInboundEvent(e){if(this.callbacks.sendEventAck(e.event_id,e.session_id),this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("qwen-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.activeRun){r.info("qwen-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.acpClient?.isAlive){this.callbacks.sendEventResult(e.event_id,"failed","qwen agent not alive");return}if(e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),!this.sessionBindings.has(e.session_id)){this.deferEvent(e),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"Qwen needs a workspace before it can reply. Use /grix open <directory> to bind.",1,!1),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"",2,!0),this.callbacks.sendEventResult(e.event_id,"responded");return}this.startRun(e,!1)}deliverStopEvent(e){this.activeRun?.eventId===e&&(this.flushStream(),this.finishRun("canceled","stopped by user"))}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(s=>s.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("qwen-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`))}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!(!t||t.length===0)){this.deferredEvents.delete(e),r.info("qwen-adapter",`Replaying ${t.length} deferred events for session ${e}`);for(const{event:s}of t){if(this.activeRun){r.info("qwen-adapter",`Cannot replay ${s.event_id}: agent busy, dropping`);continue}this.startRun(s,!0)}}}startRun(e,t){this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgId:`qwen_${++this.clientMsgSeq}_${Date.now()}`,chunkSeq:0,buffer:"",quotedStream:new R,responded:!1,silent:t,flushTimer:null,timeoutTimer:null,firstResponseTimer:null};const s=this.activeRun;s.firstResponseTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&!s.responded&&(r.error("qwen-adapter",`No response from agent within ${u}ms for ${e.event_id}`),this.finishRun("failed","agent not responding"))},u),s.timeoutTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&(r.error("qwen-adapter",`Prompt timed out for ${e.event_id}`),this.finishRun("failed","agent response timed out"))},this.promptTimeoutMs),this.callbacks.sendSessionComposing(e.session_id,!0),this.acpClient.send(e.content).catch(i=>{r.error("qwen-adapter",`Prompt failed: ${i}`),this.finishRun("failed",i instanceof Error?i.message:String(i))})}handleAcpEvent(e){if(e.type===a.PermissionRequest){this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(t.responded||(t.responded=!0,t.firstResponseTimer&&(clearTimeout(t.firstResponseTimer),t.firstResponseTimer=null)),e.type){case a.Text:{e.content&&this.appendToStream(t,e.content);break}case a.ToolUse:{e.toolName&&this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??"");break}case a.ToolResult:{e.content&&this.callbacks.sendToolResult(t.eventId,t.sessionId,e.content);break}case a.Thinking:{e.content&&this.callbacks.sendThinking(t.eventId,t.sessionId,e.content);break}case a.Error:{r.error("qwen-adapter",`ACP error: ${e.error}`);break}case a.Result:{this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}):(r.info("qwen-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}async handleAuthRequired(e){r.info("qwen-adapter",`Auth required, methods: ${e.authMethods.map(o=>o.id).join(", ")}`);const t=e.authMethods.find(o=>/oauth|browser/i.test(o.id))??e.authMethods[0];if(!t)throw e;const s=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(s,`Qwen authentication required (${t.id}). Initiating auth flow...`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"failed",n);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(s,`Please open this URL to authenticate:
2
+ ${i}
3
+
4
+ Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("qwen-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming..."),await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:"bypass",mcpServers:this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[l.resolve(d,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0}),r.info("qwen-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"ready",n)}catch(o){throw r.error("qwen-adapter",`Auth retry failed: ${o}`),o}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=o=>{if(s)return;const n=o.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);n&&(s=!0,this.agentProcess.removeListener("stderr",i),e(n[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}appendToStream(e,t){const s=e.quotedStream.consume(t);s.deltaContent&&(e.buffer+=s.deltaContent,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),I)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.callbacks.sendStreamChunk(e.eventId,e.sessionId,t,++e.chunkSeq,!1)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.timeoutTimer&&(clearTimeout(s.timeoutTimer),s.timeoutTimer=null),s.firstResponseTimer&&(clearTimeout(s.firstResponseTimer),s.firstResponseTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),s.buffer&&(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,s.buffer,++s.chunkSeq,!1),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",++s.chunkSeq,!0),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.eventResults&&!s.silent&&this.eventResults.set({sessionId:s.sessionId,eventId:s.eventId,status:e,msg:t,updatedAt:Date.now()})}}class S extends h{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){this.emit("error",e)}async cancel(){}}export{T as QwenAdapter};
@@ -0,0 +1 @@
1
+ import{EventEmitter as o}from"node:events";import c from"ws";const r="aibot-agent-api-v1",h=1;class l extends o{ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;config;constructor(e){super(),this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,capabilities:e.capabilities??["stream_chunk","local_action_v1"],localActions:e.localActions??["exec_approve","exec_reject"]}}get isConnected(){return this.connected}async connect(){return new Promise((e,a)=>{const s=new c(this.config.url);this.ws=s;const i=setTimeout(()=>{a(new Error("Auth timeout: no auth_ack received within 15s")),s.close()},15e3);s.on("open",()=>{this.sendPacket("auth",{agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:r,contract_version:h,capabilities:this.config.capabilities,local_actions:this.config.localActions})}),s.on("message",t=>{let n;try{n=JSON.parse(t.toString())}catch{return}this.handlePacket(n,i,e,a)}),s.on("close",(t,n)=>{this.connected=!1,this.stopHeartbeat(),clearTimeout(i),this.emit("close",t,n.toString())}),s.on("error",t=>{clearTimeout(i),this.emit("error",t),this.connected||a(t)})})}handlePacket(e,a,s,i){switch(e.cmd){case"auth_ack":{clearTimeout(a);const t=e.payload;t.code===0?(this.connected=!0,t.heartbeat_sec&&(this.heartbeatSec=t.heartbeat_sec),this.startHeartbeat(),this.emit("auth",t),s(t)):i(new Error(`Auth failed: code=${t.code} msg=${t.msg}`));break}case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"kicked":{this.emit("kicked",e.payload),this.disconnect();break}case"error":{const t=e.payload;this.emit("error",new Error(`Server error: code=${t.code} msg=${t.msg}`));break}case"send_ack":case"send_nack":case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)}sendStreamChunk(e){this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)}sendEventResult(e){this.sendPacket("event_result",e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendPing(){this.sendPacket("ping",{})}disconnect(){this.connected=!1,this.stopHeartbeat(),this.ws&&(this.ws.close(),this.ws=null)}sendPacket(e,a){if(!this.ws||this.ws.readyState!==c.OPEN)return;const s={cmd:e,seq:++this.seq,payload:a};this.ws.send(JSON.stringify(s))}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.sendPing()},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{l as AibotClient};
@@ -0,0 +1 @@
1
+ export*from"./types.js";import{AibotClient as o}from"./client.js";export{o as AibotClient};
File without changes
@@ -0,0 +1 @@
1
+ import{mkdir as m}from"node:fs/promises";import{join as c,basename as l}from"node:path";import{homedir as _}from"node:os";import{listFiles as u}from"./list-files.js";function d(){return _()}async function f(a,s){const o=a.params??{},t=o.parent_id?.trim(),i=o.show_hidden??!1;let e;t?e=t:e=s.resolveCwd()||d();try{return{status:"ok",result:{files:await u(e,i),current_path:e}}}catch(r){return r?.code==="ENOENT"?{status:"failed",error_code:"path_not_found",error_msg:`Directory not found: ${e}`}:r?.code==="ENOTDIR"?{status:"failed",error_code:"not_a_directory",error_msg:`Not a directory: ${e}`}:{status:"failed",error_code:"list_failed",error_msg:String(r.message||r)}}}async function p(a,s){const o=a.params??{},t=o.name?.trim(),i=o.parent_id?.trim();if(!t)return{status:"failed",error_code:"name_required",error_msg:"Folder name is required"};if(/[/\\]/.test(t))return{status:"failed",error_code:"invalid_name",error_msg:"Folder name must not contain path separators"};const e=i||s.resolveCwd()||d(),r=c(e,t);try{return await m(r),{status:"ok",result:{id:r,name:l(r),is_directory:!0}}}catch(n){return n?.code==="EEXIST"?{status:"failed",error_code:"already_exists",error_msg:`Folder already exists: ${r}`}:{status:"failed",error_code:"create_failed",error_msg:String(n.message||n)}}}export{p as handleCreateFolderAction,f as handleFileListAction};
@@ -0,0 +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 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};
File without changes
@@ -1,2 +1,2 @@
1
- import{execFile as f}from"node:child_process";import{promisify as y}from"node:util";import{log as p}from"../log/logger.js";import{probeUrls as h}from"./speed-test.js";const w=y(f),b=1e4,c=[{id:"official",label:"npm \u5B98\u65B9",url:"https://registry.npmjs.org"},{id:"npmmirror",label:"npmmirror (\u6DD8\u5B9D\u955C\u50CF)",url:"https://registry.npmmirror.com"}];async function x(){const s=await h(c.map(r=>({url:r.url,label:r.label})),5e3),t=[];for(const r of s)if(r.reachable){const e=c.find(o=>o.url===r.url);e&&t.push(e)}for(const r of s)if(!r.reachable){const e=c.find(o=>o.url===r.url);e&&!t.includes(e)&&t.push(e)}for(const r of c)t.includes(r)||t.push(r);return t}async function N(){try{const s=process.platform==="win32",t=s?"cmd.exe":"npm",r=s?["/c","npm","config","get","registry"]:["config","get","registry"],{stdout:e}=await w(t,r,{timeout:b,encoding:"utf-8"});return e.trim()}catch{return"https://registry.npmjs.org"}}async function O(s,t,r){const e=await x();if(e.length===0)throw new Error("All npm registries are unreachable. Check your network connection.");let o="";for(const l of e)try{return p.info("installer",`npm install ${s} using ${l.label} (${l.url})`),{output:await R(s,l.url,t,r),registry:l.label}}catch(i){const n=i instanceof Error?i.message:String(i);if(o=n,p.info("installer",`${l.label} failed: ${n.slice(0,200)}`),!(n.includes("ECONNRESET")||n.includes("ETIMEDOUT")||n.includes("ECONNREFUSED")||n.includes("ENOTFOUND")||n.includes("network")||n.includes("fetch failed")||n.includes("404")||n.includes("ERR_SOCKET_TIMEOUT")||n.includes("getaddrinfo")))throw i}throw new Error(`npm install failed on all registries. Last error: ${o}`)}function R(s,t,r,e){return new Promise((o,l)=>{const i=["install","-g",s,"--registry",t,"--prefer-online","--no-audit","--no-fund"],n=process.platform==="win32",a=n?"cmd.exe":"npm",g=n?["/c","npm",...i]:i;f(a,g,{timeout:r,maxBuffer:e},(u,d,m)=>{if(u){const E=m?.trim()||u.message;l(new Error(E));return}o(`${d??""}
2
- ${m??""}`.trim())})})}function M(s){return c.map(t=>({label:t.label,command:`npm install -g ${s} --registry ${t.url}`}))}export{c as NPM_REGISTRIES,N as getCurrentRegistry,M as getMirrorInstallCommands,O as npmInstallWithMirror,x as probeRegistries};
1
+ import{execFile as f}from"node:child_process";import{promisify as h}from"node:util";import{log as p}from"../log/logger.js";import{probeUrls as b}from"./speed-test.js";const w=h(f),x=1e4,c=[{id:"official",label:"npm \u5B98\u65B9",url:"https://registry.npmjs.org"},{id:"npmmirror",label:"npmmirror (\u6DD8\u5B9D\u955C\u50CF)",url:"https://registry.npmmirror.com"}];async function T(){const e=await b(c.map(r=>({url:r.url,label:r.label})),5e3),t=[];for(const r of e)if(r.reachable){const n=c.find(o=>o.url===r.url);n&&t.push(n)}for(const r of e)if(!r.reachable){const n=c.find(o=>o.url===r.url);n&&!t.includes(n)&&t.push(n)}for(const r of c)t.includes(r)||t.push(r);return t}async function U(){try{const e=process.platform==="win32",t=e?"cmd.exe":"npm",r=e?["/c","npm","config","get","registry"]:["config","get","registry"],{stdout:n}=await w(t,r,{timeout:x,encoding:"utf-8"});return n.trim()}catch{return"https://registry.npmjs.org"}}async function _(e,t,r){const n=await T();if(n.length===0)throw new Error("All npm registries are unreachable. Check your network connection.");let o="";for(const s of n)try{return p.info("installer",`npm install ${e} using ${s.label} (${s.url})`),{output:await I(e,s.url,t,r),registry:s.label}}catch(i){const l=i instanceof Error?i.message:String(i);if(o=l,p.info("installer",`${s.label} failed: ${l.slice(0,200)}`),!$(l))throw i}throw new Error(`npm install failed on all registries. Last error: ${o}`)}function $(e){return/ECONNRESET|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ERR_SOCKET_TIMEOUT|getaddrinfo|socket hang up|network|fetch failed|timed out|tunneling socket|self.signed|404/i.test(e)}function I(e,t,r,n){return new Promise((o,s)=>{const i=["install","-g",e,"--registry",t,"--prefer-online","--no-audit","--no-fund"],l=process.platform==="win32",g=l?"cmd.exe":"npm",d=l?["/c","npm",...i]:i;f(g,d,{timeout:r,maxBuffer:n},(a,E,m)=>{if(a){const u=m?.trim()||a.message,y=a.killed===!0?`ETIMEDOUT: npm install exceeded ${r}ms (registry likely unreachable): ${u}`:u;s(new Error(y));return}o(`${E??""}
2
+ ${m??""}`.trim())})})}function A(e){return c.map(t=>({label:t.label,command:`npm install -g ${e} --registry ${t.url}`}))}export{c as NPM_REGISTRIES,U as getCurrentRegistry,A as getMirrorInstallCommands,$ as isRetriableRegistryError,_ as npmInstallWithMirror,T as probeRegistries};
@@ -0,0 +1 @@
1
+ import*as i from"@sentry/node";import{log as s}from"../log/logger.js";import{resolveClientVersion as c}from"../util/client-version.js";let a=!1;const p="https://e8e202d7625372b1314b3ff4e85a7ff9@o119262.ingest.us.sentry.io/4511410543329280";function f(){if(!process.env.GRIX_SENTRY_DISABLE)return process.env.SENTRY_DSN||process.env.GRIX_SENTRY_DSN||p}function T(){const e=f();if(!e){s.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u7981\u7528\uFF08GRIX_SENTRY_DISABLE\uFF09");return}try{i.init({dsn:e,release:`grix-connector@${c()}`,environment:process.env.SENTRY_ENVIRONMENT||process.env.NODE_ENV||"production",sendDefaultPii:!1,initialScope:{tags:{component:"grix-connector"}},defaultIntegrations:!1,tracesSampleRate:0}),a=!0,s.info("sentry","Sentry \u9519\u8BEF\u4E0A\u62A5\u5DF2\u542F\u7528")}catch(n){s.error("sentry",`Sentry \u521D\u59CB\u5316\u5931\u8D25: ${n instanceof Error?n.message:String(n)}`)}}function g(){return a}const E=new Set(["INSTALL_FAILED","INSTALL_TIMEOUT","FALLBACK_EXHAUSTED","PREREQ_INSTALL_FAILED","VERIFICATION_FAILED","ENVIRONMENT_UNSUPPORTED","INTERNAL"]);function S(e){if(e.ok)return!1;const n=e.error?.code;return!!n&&E.has(n)}function I(e){if(!a||!S(e))return;const n=e.error?.code??"INTERNAL",t=e.error?.message??"unknown install failure",r=e.environment;try{i.withScope(o=>{o.setLevel("error"),o.setTags({agent_type:e.agentType,error_code:n,phase:e.phase}),o.setContext("install",{agentType:e.agentType,code:n,phase:e.phase,durationMs:e.durationMs,os:r?.platform,osVersion:r?.osVersion,arch:r?.arch,nodeVersion:r?.nodeVersion,npmVersion:r?.npmVersion,isDocker:r?.isDocker,isCI:r?.isCI,outputTail:(e.output??"").slice(-2e3)}),o.setFingerprint(["agent-install-failure",e.agentType,n]),i.captureException(new Error(`Agent install failed [${e.agentType}/${n}]: ${t}`))}),s.info("sentry",`\u5DF2\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25: ${e.agentType}/${n}`)}catch(o){s.error("sentry",`\u4E0A\u62A5\u5B89\u88C5\u5931\u8D25\u65F6\u51FA\u9519: ${o instanceof Error?o.message:String(o)}`)}}function N(e,n){if(a)try{i.withScope(t=>{t.setLevel("fatal"),t.setTag("crash_type",n);const r=e instanceof Error?e:new Error(String(e));i.captureException(r)}),s.info("sentry",`\u5DF2\u4E0A\u62A5\u5D29\u6E83: ${n}`)}catch(t){s.error("sentry",`\u4E0A\u62A5\u5D29\u6E83\u65F6\u51FA\u9519: ${t instanceof Error?t.message:String(t)}`)}}async function u(e=2e3){if(a)try{await i.close(e)}catch{}}export{u as closeSentry,T as initSentry,g as isSentryEnabled,N as reportFatal,I as reportInstallFailure,S as shouldReportInstallFailure};
@@ -1,2 +1,2 @@
1
- import{execFile as N,execFileSync as f}from"node:child_process";import{existsSync as a,readFileSync as m,renameSync as x,statfsSync as I,unlinkSync as y,writeFileSync as F}from"node:fs";import{join as d}from"node:path";import{GRIX_PATHS as g}from"../log/index.js";import{appendRotatingFileSync as M}from"../log/rotation.js";import{resolveClientVersion as A}from"../util/client-version.js";class o extends Error{code;constructor(n,r){super(r),this.name="UpgradeError",this.code=n}}function _(){return d(g.log,"upgrade.log")}function l(){return d(g.data,"upgrade-pending.json")}function b(){return a(l())}function O(){const e=l();if(!a(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function D(e,n){const r={from_version:e,target_version:n,upgraded_at:new Date().toISOString(),crash_count:0},i=l(),c=i+".tmp";F(c,JSON.stringify(r),"utf-8"),x(c,i)}function C(){const e=l();if(a(e))try{y(e)}catch{}}function p(e){const n=`[${new Date().toISOString()}] ${e}
2
- `;try{M(_(),n)}catch{}}function U(e=4096){const n=_();if(!a(n))return"";try{const r=m(n,"utf-8");return r.length<=e?r:r.slice(-e)}catch{return""}}function S(){try{const e=process.platform==="win32";return f(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new o("NPM_NOT_FOUND","npm is not available or timed out")}}function h(){let e;try{const n=process.platform==="win32";e=f(n?"cmd.exe":"npm",n?["/c","npm","prefix","-g"]:["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return Number.MAX_SAFE_INTEGER}try{if(process.platform==="win32")return Number.MAX_SAFE_INTEGER;const n=I(e);return Math.floor(n.bsize*n.bavail/(1024*1024))}catch{return Number.MAX_SAFE_INTEGER}}function R(){let e;try{e=S()}catch(r){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:r instanceof Error?r.message:"npm not available"}}const n=h();return n<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${n}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:n}:{ok:!0,npmVersion:e,diskFreeMb:n}}function G(){let e="";try{e=S()}catch{}let n=Number.MAX_SAFE_INTEGER;try{n=h()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:n,platform:process.platform,arch:process.arch}}async function X(e,n,r=12e4){const i=`${e}@${n}`;return p(`npm install -g ${i} starting`),new Promise((c,s)=>{N("npm",["install","-g",i,"--prefer-online","--no-audit","--no-fund"],{timeout:r,maxBuffer:10*1024*1024},(u,w,E)=>{if(u){const t=E?.trim()||u.message;p(`npm install failed: ${t}`),u.killed?s(new o("NPM_TIMEOUT",`npm install timed out after ${r/1e3}s`)):t.includes("EACCES")||t.includes("permission denied")?s(new o("NPM_INSTALL_FAILED",`Permission denied: ${t}`)):t.includes("ENOSPC")||t.includes("no space left")?s(new o("DISK_FULL",`Disk full: ${t}`)):t.includes("404")||t.includes("not found")?s(new o("NPM_INSTALL_FAILED",`Package not found: ${t}`)):s(new o("NPM_INSTALL_FAILED",t));return}p("npm install succeeded"),c()})})}function V(e){const n=A();if(n!==e)throw new o("VERSION_MISMATCH",`Installed version ${n} does not match expected ${e}`);return n}export{o as UpgradeError,h as checkDiskSpace,S as checkNpmAvailable,G as collectEnvInfo,U as getUpgradeLogTail,X as npmInstall,b as pendingExists,R as preflightCheck,O as readPending,C as removePending,p as upgradeLog,V as verifyInstalledVersion,D as writePending};
1
+ import{execFileSync as p}from"node:child_process";import{existsSync as c,readFileSync as m,renameSync as _,statfsSync as S,unlinkSync as E,writeFileSync as N}from"node:fs";import{join as u}from"node:path";import{GRIX_PATHS as f}from"../log/index.js";import{appendRotatingFileSync as I}from"../log/rotation.js";import{resolveClientVersion as w}from"../util/client-version.js";import{npmInstallWithMirror as y}from"../installer/npm-registry.js";class i extends Error{code;constructor(r,n){super(n),this.name="UpgradeError",this.code=r}}function d(){return u(f.log,"upgrade.log")}function a(){return u(f.data,"upgrade-pending.json")}function T(){return c(a())}function k(){const e=a();if(!c(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function $(e,r){const n={from_version:e,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0},s=a(),o=s+".tmp";N(o,JSON.stringify(n),"utf-8"),_(o,s)}function b(){const e=a();if(c(e))try{E(e)}catch{}}function l(e){const r=`[${new Date().toISOString()}] ${e}
2
+ `;try{I(d(),r)}catch{}}function O(e=4096){const r=d();if(!c(r))return"";try{const n=m(r,"utf-8");return n.length<=e?n:n.slice(-e)}catch{return""}}function g(){try{const e=process.platform==="win32";return p(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new i("NPM_NOT_FOUND","npm is not available or timed out")}}function h(){let e;try{const r=process.platform==="win32";e=p(r?"cmd.exe":"npm",r?["/c","npm","prefix","-g"]:["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return Number.MAX_SAFE_INTEGER}try{if(process.platform==="win32")return Number.MAX_SAFE_INTEGER;const r=S(e);return Math.floor(r.bsize*r.bavail/(1024*1024))}catch{return Number.MAX_SAFE_INTEGER}}function D(){let e;try{e=g()}catch(n){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:n instanceof Error?n.message:"npm not available"}}const r=h();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:r}:{ok:!0,npmVersion:e,diskFreeMb:r}}function U(){let e="";try{e=g()}catch{}let r=Number.MAX_SAFE_INTEGER;try{r=h()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch}}async function C(e,r,n=12e4){const s=`${e}@${r}`;l(`npm install -g ${s} starting (with mirror fallback)`);try{const{registry:o}=await y(s,n,10485760);l(`npm install succeeded via ${o}`)}catch(o){const t=o instanceof Error?o.message:String(o);throw l(`npm install failed: ${t}`),/timed out|ETIMEDOUT/i.test(t)?new i("NPM_TIMEOUT",`npm install timed out after ${n/1e3}s (tried all mirrors): ${t}`):t.includes("EACCES")||t.includes("permission denied")?new i("NPM_INSTALL_FAILED",`Permission denied: ${t}`):t.includes("ENOSPC")||t.includes("no space left")?new i("DISK_FULL",`Disk full: ${t}`):t.includes("404")||t.includes("not found")?new i("NPM_INSTALL_FAILED",`Package not found: ${t}`):new i("NPM_INSTALL_FAILED",t)}}function R(e){const r=w();if(r!==e)throw new i("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}export{i as UpgradeError,h as checkDiskSpace,g as checkNpmAvailable,U as collectEnvInfo,O as getUpgradeLogTail,C as npmInstall,T as pendingExists,D as preflightCheck,k as readPending,b as removePending,l as upgradeLog,R as verifyInstalledVersion,$ as writePending};
@@ -1 +1 @@
1
- import{readFileSync as c,readdirSync as a}from"node:fs";import{join as m,dirname as f}from"node:path";import{fileURLToPath as u}from"node:url";import{parseSkillFrontmatter as d}from"../adapter/claude/skill-scanner.js";import{log as r}from"../core/log/index.js";const o=f(u(import.meta.url));function p(){const t=[];let e;try{e=a(o,{withFileTypes:!0,encoding:"utf8"})}catch{return t}for(const n of e){if(!n.isDirectory())continue;const s=m(o,n.name,"SKILL.md");try{const l=c(s,"utf-8"),i=d(l);i?.name&&t.push({name:i.name,description:i.description,trigger:i.trigger,source:"connector"})}catch{}}return t}function L(){const t=p();if(t.length===0){r.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}r.info("default-skills",`Connector system skills available: [${t.map(e=>e.name).join(", ")}]`)}export{L as logDefaultSkillsCheck,p as scanDefaultSkills};
1
+ import{readFileSync as m,readdirSync as c,existsSync as u,mkdirSync as d,cpSync as p,writeFileSync as k,rmSync as y}from"node:fs";import{join as t,dirname as h}from"node:path";import{homedir as g}from"node:os";import{fileURLToPath as S}from"node:url";import{parseSkillFrontmatter as D}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as x}from"../core/config/index.js";import{log as a}from"../core/log/index.js";const l=h(S(import.meta.url)),f=".grix-managed";function v(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=m(n,"utf-8"),o=D(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function K(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(u(t(s,"SKILL.md")))try{d(e,{recursive:!0});const o=t(e,n.name);p(s,o,{recursive:!0}),k(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function F(){const e=g(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(x().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills")]}function M(){const e=[];for(const i of F()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(u(t(s,f)))try{y(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&a.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function R(){const e=v();if(e.length===0){a.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}a.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{M as cleanupProjectedSkills,R as logDefaultSkillsCheck,v as scanDefaultSkills,K as syncDefaultSkillsToDir};
package/dist/grix.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import g from"node:path";import{writeFileSync as E}from"node:fs";import{Manager as y}from"./manager.js";import{ensureGrixDirs as O,initLogger as $,log as a,installProcessLogRotation as L,setConsoleOutput as N}from"./core/log/index.js";import{HealthServer as b}from"./core/runtime/index.js";import{writePidFile as C,removePidFile as h}from"./core/runtime/index.js";import{resolveRuntimePaths as x}from"./core/config/index.js";import{ServiceManager as U}from"./service/service-manager.js";import{killProcessesByCommandLine as H,isWindowsElevated as _}from"./service/process-control.js";import{acquireDaemonLock as j,releaseDaemonLock as w}from"./runtime/daemon-lock.js";import{writeDaemonStatus as v,removeDaemonStatus as G}from"./runtime/service-state.js";import{AdminServer as W,generateToken as M,writeTokenFile as B}from"./core/admin/index.js";const c=process.argv.slice(2),k=[],s={};for(let t=0;t<c.length;t++)c[t].startsWith("--")&&c[t+1]&&!c[t+1].startsWith("--")?(s[c[t].slice(2)]=c[t+1],t++):c[t].startsWith("--")?s[c[t].slice(2)]="true":k.push(c[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import g from"node:path";import{writeFileSync as x}from"node:fs";import{Manager as $}from"./manager.js";import{ensureGrixDirs as L,initLogger as N,log as a,installProcessLogRotation as b,setConsoleOutput as C}from"./core/log/index.js";import{HealthServer as U}from"./core/runtime/index.js";import{writePidFile as H,removePidFile as h}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as j}from"./service/service-manager.js";import{killProcessesByCommandLine as _,isWindowsElevated as G}from"./service/process-control.js";import{acquireDaemonLock as W,releaseDaemonLock as w}from"./runtime/daemon-lock.js";import{writeDaemonStatus as k,removeDaemonStatus as M}from"./runtime/service-state.js";import{AdminServer as B,generateToken as V,writeTokenFile as X}from"./core/admin/index.js";import{initSentry as q,closeSentry as P,reportFatal as E}from"./core/observability/sentry.js";const c=process.argv.slice(2),A=[],s={};for(let t=0;t<c.length;t++)c[t].startsWith("--")&&c[t+1]&&!c[t+1].startsWith("--")?(s[c[t].slice(2)]=c[t+1],t++):c[t].startsWith("--")?s[c[t].slice(2)]="true":A.push(c[t]);s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
3
 
4
4
  Usage: grix-connector <command> [options]
5
5
 
@@ -25,7 +25,7 @@ Examples:
25
25
  grix-connector start # Start as system service
26
26
  grix-connector status # Check service status
27
27
  grix-connector restart # Restart the service
28
- `),process.exit(0));const d=k[0],P=["start","stop","restart","status"];if(d&&P.includes(d)){process.platform==="win32"&&["start","stop","restart"].includes(d)&&!_()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
28
+ `),process.exit(0));const d=A[0],I=["start","stop","restart","status"];if(d&&I.includes(d)){process.platform==="win32"&&["start","stop","restart"].includes(d)&&!G()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
29
29
  using Startup folder auto-start instead. For full Task Scheduler integration,
30
- right-click the terminal and select "Run as administrator".`);const t=x(),m=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),l=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new U({cliPath:l,nodePath:process.execPath});try{let o;switch(d){case"start":(await r.status({rootDir:t.rootDir})).installed?o=await r.start({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"stop":o=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?o=await r.restart({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"status":o=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(o,null,2)),process.exit(0)}catch(o){console.error(`${d} failed: ${o instanceof Error?o.message:o}`),process.exit(1)}}else d&&(console.error(`Unknown command: ${d}
31
- Valid commands: ${P.join(", ")}`),process.exit(1));const i=x(),V=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),n=new y,S=new b,A=M(),p=new W(A);let I=!1;async function D(t){if(I)return;I=!0,a.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const m=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)},1e4);try{await n.stop(),await p.stop(),await S.stop(),await w(i.daemonLockFile),await G(i.daemonStatusFile).catch(()=>{}),clearTimeout(m),h(),a.info("main","Shutdown complete"),process.exit(0)}catch(l){a.error("main",`Shutdown error: ${l}`),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)}}async function X(){O(),$(),L(i.stdoutLogFile,i.stderrLogFile),N(!1),process.platform==="win32"&&await H("GrixConnectorDaemon",{platform:"win32"});try{await j(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}C(),a.info("main",`grix-connector starting (PID ${process.pid})`),await v(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await S.start(t);const m=g.join(i.dataDir,"health-port");E(m,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let l="",r=0,o;process.on("uncaughtException",e=>{const u=e instanceof Error?e.stack??e.message:String(e);u===l?(r++,(r<=3||r%100===0)&&a.error("main",`Uncaught exception (x${r}): ${u}`)):(r>3&&a.error("main",`Previous exception repeated ${r} times total`),l=u,r=1,a.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),o||(o=setTimeout(()=>{r>3&&a.error("main",`Previous exception repeated ${r} times total`),l="",r=0,o=void 0},1e4).unref())),!T(e)&&D("uncaughtException")}),process.on("unhandledRejection",e=>{a.error("main",`Unhandled rejection: ${e}`),!T(e)&&D("unhandledRejection")}),S.setStatusProvider(()=>n.getAgentsStatus()),await n.start(V);const f=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);p.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e)}),p.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),p.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,u)=>n.probeOne(e,u)}),p.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)}),await p.start(f);const R=g.join(i.dataDir,"admin-token"),F=g.join(i.dataDir,"admin-port");B(R,A),E(F,String(f),"utf-8"),await v(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}X().catch(t=>{a.error("main",`Fatal: ${t}`),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(1)});const q=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function T(t){return t instanceof Error&&"code"in t?q.has(t.code):!1}
30
+ right-click the terminal and select "Run as administrator".`);const t=v(),m=s["config-dir"]??(s.profile?g.join(t.configDir,s.profile):void 0),l=g.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),r=new j({cliPath:l,nodePath:process.execPath});try{let o;switch(d){case"start":(await r.status({rootDir:t.rootDir})).installed?o=await r.start({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"stop":o=await r.stop({rootDir:t.rootDir});break;case"restart":(await r.status({rootDir:t.rootDir})).installed?o=await r.restart({rootDir:t.rootDir}):o=await r.install({rootDir:t.rootDir,configDir:m});break;case"status":o=await r.status({rootDir:t.rootDir});break}console.log(JSON.stringify(o,null,2)),process.exit(0)}catch(o){console.error(`${d} failed: ${o instanceof Error?o.message:o}`),process.exit(1)}}else d&&(console.error(`Unknown command: ${d}
31
+ Valid commands: ${I.join(", ")}`),process.exit(1));const i=v(),J=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),n=new $,S=new U,R=V(),p=new B(R);let T=!1;async function D(t){if(T)return;T=!0,a.info("main",`Received ${t}, shutting down...`),S.markShuttingDown();const m=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)},1e4);try{await n.stop(),await p.stop(),await S.stop(),await P(),await w(i.daemonLockFile),await M(i.daemonStatusFile).catch(()=>{}),clearTimeout(m),h(),a.info("main","Shutdown complete"),process.exit(0)}catch(l){a.error("main",`Shutdown error: ${l}`),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(2)}}async function K(){L(),N(),q(),b(i.stdoutLogFile,i.stderrLogFile),C(!1),process.platform==="win32"&&await _("GrixConnectorDaemon",{platform:"win32"});try{await W(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}H(),a.info("main",`grix-connector starting (PID ${process.pid})`),await k(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await S.start(t);const m=g.join(i.dataDir,"health-port");x(m,String(t),"utf-8"),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGTERM",()=>D("SIGTERM"));let l="",r=0,o;process.on("uncaughtException",e=>{const u=e instanceof Error?e.stack??e.message:String(e);u===l?(r++,(r<=3||r%100===0)&&a.error("main",`Uncaught exception (x${r}): ${u}`)):(r>3&&a.error("main",`Previous exception repeated ${r} times total`),l=u,r=1,a.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),o||(o=setTimeout(()=>{r>3&&a.error("main",`Previous exception repeated ${r} times total`),l="",r=0,o=void 0},1e4).unref())),!F(e)&&(E(e,"uncaughtException"),D("uncaughtException"))}),process.on("unhandledRejection",e=>{a.error("main",`Unhandled rejection: ${e}`),!F(e)&&(E(e,"unhandledRejection"),D("unhandledRejection"))}),S.setStatusProvider(()=>n.getAgentsStatus()),await n.start(J);const f=parseInt(s["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);p.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e)}),p.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),p.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,u)=>n.probeOne(e,u)}),p.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)}),await p.start(f);const y=g.join(i.dataDir,"admin-token"),O=g.join(i.dataDir,"admin-port");X(y,R),x(O,String(f),"utf-8"),await k(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}K().catch(async t=>{a.error("main",`Fatal: ${t}`),E(t,"startup"),await P(),w(i.daemonLockFile).catch(()=>{}),h(),process.exit(1)});const z=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function F(t){return t instanceof Error&&"code"in t?z.has(t.code):!1}
package/dist/log.js ADDED
@@ -0,0 +1,3 @@
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
+ `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
package/dist/main.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import x from"node:path";import{Manager as E}from"./manager.js";import{ensureGrixDirs as k,initLogger as O,log as a}from"./core/log/index.js";import{HealthServer as I}from"./core/runtime/index.js";import{writePidFile as R,removePidFile as l}from"./core/runtime/index.js";import{resolveRuntimePaths as g}from"./core/config/index.js";import{ServiceManager as T}from"./service/service-manager.js";import{acquireDaemonLock as $,releaseDaemonLock as p}from"./runtime/daemon-lock.js";import{writeDaemonStatus as f,removeDaemonStatus as F}from"./runtime/service-state.js";const n=process.argv.slice(2),m=[],s={};for(let e=0;e<n.length;e++)n[e].startsWith("--")&&n[e+1]&&!n[e+1].startsWith("--")?(s[n[e].slice(2)]=n[e+1],e++):n[e].startsWith("--")?s[n[e].slice(2)]="true":m.push(n[e]);if(s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
+
4
+ Usage: grix-connector [options]
5
+ grix-connector service <action> [options]
6
+
7
+ Actions (service):
8
+ install Install and start as OS service
9
+ start Start the OS service
10
+ stop Stop the OS service
11
+ restart Restart the OS service
12
+ uninstall Uninstall the OS service
13
+ status Show service and daemon status
14
+
15
+ Options:
16
+ --config-dir <path> Config directory (default: ~/.grix/config)
17
+ --profile <name> Profile name for config subdirectory
18
+ --health-port <port> Health check port (default: 19579)
19
+ --help Show this help message
20
+
21
+ Platform services:
22
+ macOS: launchd (LaunchAgent)
23
+ Linux: systemd --user
24
+ Windows: Task Scheduler
25
+
26
+ Examples:
27
+ grix-connector # Run in foreground
28
+ grix-connector service install # Install as OS service
29
+ grix-connector service status # Check service status
30
+ grix-connector service uninstall # Remove OS service
31
+ `),process.exit(0)),m[0]==="service"){const e=m[1],t=["install","start","stop","restart","uninstall","status"];(!e||!t.includes(e))&&(console.error(`Usage: grix-connector service <${t.join("|")}>`),process.exit(1));const o=g(),w=s["config-dir"]??(s.profile?`${o.configDir}/${s.profile}`:void 0),D=x.resolve(process.argv[1]||`${o.rootDir}/dist/main.js`),c=new T({cliPath:D,nodePath:process.execPath});try{let r;switch(e){case"install":r=await c.install({rootDir:o.rootDir,configDir:w});break;case"start":r=await c.start({rootDir:o.rootDir});break;case"stop":r=await c.stop({rootDir:o.rootDir});break;case"restart":r=await c.restart({rootDir:o.rootDir});break;case"uninstall":r=await c.uninstall({rootDir:o.rootDir});break;case"status":r=await c.status({rootDir:o.rootDir});break}console.log(JSON.stringify(r,null,2)),process.exit(0)}catch(r){console.error(`service ${e} failed: ${r instanceof Error?r.message:r}`),process.exit(1)}}const i=g(),N=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),h=new E,d=new I;let S=!1;async function u(e){if(S)return;S=!0,a.info("main",`Received ${e}, shutting down...`),d.markShuttingDown();const t=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)},1e4);try{await h.stop(),await d.stop(),await p(i.daemonLockFile),await F(i.daemonStatusFile).catch(()=>{}),clearTimeout(t),l(),a.info("main","Shutdown complete"),process.exit(0)}catch(o){a.error("main",`Shutdown error: ${o}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)}}async function P(){k(),O();try{await $(i.daemonLockFile,i.rootDir)}catch(t){console.error(t instanceof Error?t.message:t),process.exit(1)}R(),a.info("main",`grix-connector starting (PID ${process.pid})`),await f(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const e=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await d.start(e),process.on("SIGINT",()=>u("SIGINT")),process.on("SIGTERM",()=>u("SIGTERM")),process.on("uncaughtException",t=>{a.error("main",`Uncaught exception: ${t instanceof Error?t.stack:t}`),!v(t)&&u("uncaughtException")}),process.on("unhandledRejection",t=>{a.error("main",`Unhandled rejection: ${t}`),!v(t)&&u("unhandledRejection")}),d.setStatusProvider(()=>h.getAgentsStatus()),await h.start(N),await f(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}P().catch(e=>{a.error("main",`Fatal: ${e}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(1)});const A=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH"]);function v(e){return e instanceof Error&&"code"in e?A.has(e.code):!1}