grix-connector 1.0.4 → 1.0.5

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 (55) hide show
  1. package/README.md +1 -0
  2. package/dist/adapter/acp/acp-adapter.js +10 -12
  3. package/dist/adapter/claude/claude-adapter.js +1 -2
  4. package/dist/adapter/codewhale/codewhale-adapter.js +1 -2
  5. package/dist/adapter/cursor/cursor-adapter.js +1 -2
  6. package/dist/adapter/opencode/opencode-adapter.js +1 -3
  7. package/dist/adapter/openhuman/openhuman-adapter.js +1 -3
  8. package/dist/adapter/pi/pi-adapter.js +2 -5
  9. package/dist/bridge/acp-toolbar-persist.js +1 -0
  10. package/dist/bridge/adapter-pool.js +1 -1
  11. package/dist/bridge/bridge.js +6 -10
  12. package/dist/bridge/event-queue.js +1 -1
  13. package/dist/bridge/send-controller.js +1 -2
  14. package/dist/bridge/session-controller.js +8 -8
  15. package/dist/core/aibot/client.js +2 -2
  16. package/dist/core/aibot/connection-handle.js +1 -1
  17. package/dist/core/persistence/session-binding-store.js +1 -1
  18. package/dist/core/runtime/copilot-resolve.js +1 -0
  19. package/dist/manager.js +2 -2
  20. package/dist/protocol/acp-client.js +1 -1
  21. package/dist/service/process-control.js +1 -1
  22. package/dist/service/service-manager.js +1 -1
  23. package/openclaw-plugin/index.js +32 -32
  24. package/package.json +1 -1
  25. package/dist/adapter/claude/claude-bridge-server.js +0 -1
  26. package/dist/adapter/claude/claude-tools.js +0 -1
  27. package/dist/adapter/claude/claude-worker-client.js +0 -1
  28. package/dist/adapter/claude/mcp-http-launcher.js +0 -2
  29. package/dist/adapter/claude/result-timeout.js +0 -1
  30. package/dist/adapter/deepseek/deepseek-adapter.js +0 -6
  31. package/dist/adapter/deepseek/index.js +0 -1
  32. package/dist/adapter/qwen/index.js +0 -1
  33. package/dist/adapter/qwen/qwen-adapter.js +0 -4
  34. package/dist/aibot/client.js +0 -1
  35. package/dist/aibot/index.js +0 -1
  36. package/dist/aibot/types.js +0 -0
  37. package/dist/core/file-ops/handler.js +0 -1
  38. package/dist/core/file-ops/list-files.js +0 -1
  39. package/dist/core/file-ops/types.js +0 -0
  40. package/dist/log.js +0 -3
  41. package/dist/main.js +0 -31
  42. package/dist/mcp/stream-http/config.js +0 -1
  43. package/dist/mcp/stream-http/connection-binding.js +0 -1
  44. package/dist/mcp/stream-http/event-tool-executor.js +0 -1
  45. package/dist/mcp/stream-http/gateway.js +0 -1
  46. package/dist/mcp/stream-http/index.js +0 -1
  47. package/dist/mcp/stream-http/security.js +0 -1
  48. package/dist/mcp/stream-http/session-manager.js +0 -1
  49. package/dist/mcp/stream-http/tool-executor.js +0 -1
  50. package/dist/mcp/stream-http/tool-registry.js +0 -1
  51. package/dist/mcp/stream-http/tool-schemas.js +0 -1
  52. package/dist/session/index.js +0 -1
  53. package/dist/session/manager.js +0 -1
  54. package/dist/transport/index.js +0 -1
  55. package/dist/transport/json-rpc.js +0 -3
package/README.md CHANGED
@@ -12,6 +12,7 @@ Grix is an AI Agent scheduling platform. It lets you manage and interact with mu
12
12
  |---|---|---|
13
13
  | `claude` | Claude Code (Anthropic) | `claude` |
14
14
  | `codex` | Codex (OpenAI) | `codex` |
15
+ | `copilot` | GitHub Copilot | `copilot` or `gh` |
15
16
  | `gemini` | Gemini (Google) | `gemini` |
16
17
  | `qwen` | Qwen (Alibaba) | `qwen` |
17
18
  | `codewhale` | CodeWhale | `codewhale` |
@@ -1,15 +1,13 @@
1
- import{fileURLToPath as b}from"node:url";import p from"node:path";import{homedir as I}from"node:os";import{promises as m}from"node:fs";import{EventEmitter as S}from"node:events";import{spawn as A}from"node:child_process";import{AgentProcess as R}from"../../agent/process.js";import{AcpClient as w,AcpAuthRequiredError as T,isAuthRequiredError as _}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as y}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as $}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as k}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as E}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as M}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as P}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as x}from"../claude/skill-scanner.js";const v=p.dirname(b(import.meta.url)),q=200,N=60*1e3,D=600*1e3,B=2e3;function C(c){return!(!(c instanceof Error)||c.code!==-32603||_(c))}function g(c){if(!(c instanceof Error))return String(c);let e=c.message;const s=c.data;return s&&(e+=` data=${JSON.stringify(s)}`),e}function j(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class ie extends S{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpMcpTools;rawTransport;approvalMode;autoInjectArgs;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;constructor(e,s,t){if(super(),this.config=e,this.callbacks=s,this.acpAuthMethod=t?.acpAuthMethod,this.acpInitialMode=t?.acpInitialMode,this.acpMcpTools=!1,this.rawTransport=t?.rawTransport??!1,this.approvalMode=t?.approvalMode??"default",this.autoInjectArgs=t?.autoInjectArgs,this.bindingStore=t?.bindingStore??null,this.currentAibotSessionId=t?.aibotSessionId?String(t.aibotSessionId).trim():void 0,t?.eventResultsPath&&(this.eventResults=new $(t.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const i=this.bindingStore.get(this.currentAibotSessionId);i?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,i.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=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.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,s){}async destroySession(e){}sendPrompt(e){const s=new L(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}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?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;const e=p.resolve(v,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,s,t){try{switch(e){case"model":{const i=s.trim();if(i)return await this.setModel(i)?{status:"ok",message:`Model set to ${i}`}:{status:"failed",message:`Failed to set model: ${i}`};const n=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${n.currentModelId||"unknown"}`,data:n}}case"mode":{const i=s.trim();if(i)return await this.setMode(i)?{status:"ok",message:`Mode set to ${i}`}:{status:"failed",message:`Failed to set mode: ${i}`};const n=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${n.currentModeId||"unknown"}`,data:n}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus(),n=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Model: ${n?.currentModelId??"unknown"}, Mode: ${n?.currentModeId??"unknown"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,model:n?.currentModelId??"",mode:n?.currentModeId??""}}}case"skills":{const i=x({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),n=i.map(a=>`- ${a.name}${a.trigger?` (${a.trigger})`:""} [${a.source}]: ${a.description}`);return{status:"ok",message:n.length>0?n.join(`
2
- `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}buildToolbarContext(e,s){const t=this.acpClient?.sessionOptions,i=t?.currentModeId??"",n=t?.currentModelId??"",a=t?.modes.map(d=>({id:d.id,name:d.name}))??[],o=t?.models.map(d=>({modelId:d.modelId,name:d.name}))??[],l=t?.models.map(d=>({id:d.modelId,displayName:d.name}))??[],u=t?.modes.map(d=>({id:d.id,displayName:d.name}))??[],h={outcome:e,...s?{cwd:s}:{},model_id:n,mode_id:i,currentModelId:n,currentModeId:i,available_models:l,available_modes:u,availableModels:l,availableModes:u,models:o,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(l.map(d=>d.id))} currentModelId=${n}`),h}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,s){const t=this.pendingApprovals.get(e);return t?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(t,{behavior:s}).catch(i=>{r.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,s){this.acpClient&&this.acpClient.respondPermission(e,s).catch(t=>{r.error("acp-adapter",`Failed to respond to permission: ${t}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const s=e.action_type??"",t=e.params??{};if(s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"){const i=String(t.tool_call_id??t.approval_command_id??t.approval_id??t.exec_context_id??""),n=s==="exec_approve"||s==="permission_approve",a=String(t.decision??"");let o;return n&&(a==="allow-once"||a==="allow-always")?o=a:n?o="allow":o="deny",i?this.handleAcpApprovalAction(i,o)?(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"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,s){const t=this.sessionBindings.get(e);if(t)try{return await m.stat(t),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${t} no longer exists, allowing rebind to ${s}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,s),this.bindingStore&&this.bindingStore.set(e,s),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(s).then(()=>!0).catch(i=>(r.error("acp-adapter",`Failed to create session on bind: ${g(i)}`),this.callbacks.sendUpdateBindingCard(e,"failed",s),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",s,this.buildToolbarContext("binding_ready",s))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}replayDeferredEvents(e){const s=this.deferredEvents.get(e);if(!(!s||s.length===0)){this.deferredEvents.delete(e),this.cancelDeferredTimer(e),r.info("acp-adapter",`Replaying ${s.length} deferred events for session ${e}`);for(const{event:t}of s){if(this.activeRun){r.info("acp-adapter",`Cannot replay ${t.event_id}: agent busy, dropping`);continue}this.startRun(t,!1)}}}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const s=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${s.status})`),this.callbacks.sendEventResult(e.event_id,s.status,s.msg);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,s){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const s=this.deferredEvents.get(e.session_id)??[];s.some(t=>t.event.event_id===e.event_id)||(s.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,s),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${s.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[s,t]of this.deferredEvents)for(const{event:i}of t)r.info("acp-adapter",`Rejecting deferred event ${i.event_id}: ${e}`),this.callbacks.sendEventResult(i.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const s=this.deferredTimers.get(e);s&&(clearTimeout(s),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const s=3e4,t=setTimeout(()=>{this.deferredTimers.delete(e);const i=this.deferredEvents.get(e);if(!(!i||i.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${s/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of i)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},s);this.deferredTimers.set(e,t)}startRun(e,s){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const t=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:t,currentClientMsgId:t,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new k,markdownSegmenter:new E,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:s,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const i=this.activeRun,n=M(e,this.resolveCwd());this.resetIdleTimer(i),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const a=P(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectRecoveryContext(i.sessionId,a);this.sendPromptWithRetry(i,o)}injectRecoveryContext(e,s){const t=this.recoveryContextBySessionId.get(e);return t?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${t.length})`),`${t}
1
+ import{fileURLToPath as A}from"node:url";import p from"node:path";import{homedir as I}from"node:os";import{promises as m}from"node:fs";import{EventEmitter as S}from"node:events";import{spawn as R}from"node:child_process";import{AgentProcess as w}from"../../agent/process.js";import{AcpClient as T,AcpAuthRequiredError as y,isAuthRequiredError as _}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as k}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as $}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as E}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as M}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as P}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as x}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as C}from"../claude/skill-scanner.js";const v=p.dirname(A(import.meta.url)),q=200,N=60*1e3,D=600*1e3,j=2e3;function b(c){return!(!(c instanceof Error)||c.code!==-32603||_(c))}function g(c){if(!(c instanceof Error))return String(c);let e=c.message;const t=c.data;return t&&(e+=` data=${JSON.stringify(t)}`),e}function B(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class se extends S{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;rawTransport;approvalMode;autoInjectArgs;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;constructor(e,t,i){if(super(),this.config=e,this.callbacks=t,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.acpMcpTools=!1,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new $(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const s=this.bindingStore.get(this.currentAibotSessionId);s?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,s.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=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.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new L(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),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?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;const e=p.resolve(v,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,t,i){try{switch(e){case"model":{const s=t.trim();if(s)return await this.setModel(s)?{status:"ok",message:`Model set to ${s}`}:{status:"failed",message:`Failed to set model: ${s}`};const n=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${n.currentModelId||"unknown"}`,data:n}}case"mode":{const s=t.trim();if(s)return await this.setMode(s)?{status:"ok",message:`Mode set to ${s}`}:{status:"failed",message:`Failed to set mode: ${s}`};const n=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${n.currentModeId||"unknown"}`,data:n}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"status":{const s=this.getStatus(),n=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${s.alive}, Busy: ${s.busy}, Model: ${n?.currentModelId??"unknown"}, Mode: ${n?.currentModeId??"unknown"}`,data:{alive:s.alive,busy:s.busy,sessions:s.sessions,model:n?.currentModelId??"",mode:n?.currentModeId??""}}}case"skills":{const s=C({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),n=s.map(a=>`- ${a.name}${a.trigger?` (${a.trigger})`:""} [${a.source}]: ${a.description}`);return{status:"ok",message:n.length>0?n.join(`
2
+ `):"No skills found",data:s}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(s){return{status:"failed",message:s instanceof Error?s.message:String(s)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}buildToolbarContext(e,t){const i=this.acpClient?.sessionOptions,s=i?.currentModeId??"",n=i?.currentModelId??"",a=i?.modes.map(d=>({id:d.id,name:d.name}))??[],o=i?.models.map(d=>({modelId:d.modelId,name:d.name}))??[],l=i?.models.map(d=>({id:d.modelId,displayName:d.name}))??[],h=i?.modes.map(d=>({id:d.id,displayName:d.name}))??[],u={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:l,available_modes:h,availableModels:l,availableModes:h,models:o,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(l.map(d=>d.id))} currentModelId=${n}`),u}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,t){const i=this.pendingApprovals.get(e);return i?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(i,{behavior:t}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{r.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",a=String(i.decision??"");let o;return n&&(a==="allow-once"||a==="allow-always")?o=a:n?o="allow":o="deny",s?this.handleAcpApprovalAction(s,o)?(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: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,t){const i=this.sessionBindings.get(e);if(i)try{return await m.stat(i),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${i} no longer exists, allowing rebind to ${t}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,t),this.bindingStore&&this.bindingStore.set(e,t),this.maybeRefreshKiroSkills(t),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(t).then(()=>!0).catch(s=>(r.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",t),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",t,this.buildToolbarContext("binding_ready",t))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}maybeRefreshKiroSkills(e){if(this.config.command!=="kiro-cli"||!this.callbacks.onSkillsUpdate)return;const t=String(e??"").trim()||void 0;let i=[];try{i=C({mode:"kiro",projectDir:t})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!(!t||t.length===0)){this.deferredEvents.delete(e),this.cancelDeferredTimer(e),r.info("acp-adapter",`Replaying ${t.length} deferred events for session ${e}`);for(const{event:i}of t){if(this.activeRun){r.info("acp-adapter",`Cannot replay ${i.event_id}: agent busy, dropping`);continue}this.startRun(i,!1)}}}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-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("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.callbacks.sendEventResult(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const t=this.deferredTimers.get(e);t&&(clearTimeout(t),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new E,markdownSegmenter:new M,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const s=this.activeRun,n=P(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const a=x(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectRecoveryContext(s.sessionId,a);this.sendPromptWithRetry(s,o)}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),`${i}
3
3
 
4
4
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
5
- ${s}`):s}sendPromptWithRetry(e,s,t=0){this.acpClient.send(s).catch(i=>{if(r.error("acp-adapter",`Prompt failed (attempt ${t+1}): ${g(i)}`),t===0&&C(i)){r.info("acp-adapter",`Retrying prompt for event ${e.eventId} after retryable error`),e.buffer="",e.chunkSeq=0,e.currentSegmentIndex=0,e.currentClientMsgId=e.clientMsgIdBase,e.markdownSegmenter.reset(),e.responded=!1,e.awaitingToolResult=!1,e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),setTimeout(()=>{this.activeRun?.eventId===e.eventId&&this.acpClient?.isAlive?(this.resetIdleTimer(e),this.sendPromptWithRetry(e,s,1)):this.finishRun("failed",i instanceof Error?i.message:String(i))},B);return}const n=C(i)?"Agent \u9047\u5230\u5185\u90E8\u9519\u8BEF\uFF0C\u81EA\u52A8\u91CD\u8BD5\u540E\u4ECD\u7136\u5931\u8D25\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F\uFF0C\u6216\u53D1\u9001 /grix restart \u91CD\u542F agent\u3002":i instanceof Error?i.message:String(i);this.finishRun("failed",n)})}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),this.agentProcess=new R;const s=this.resolveCwd();try{if(!(await m.stat(s)).isDirectory())throw new Error(`Bound path is not a directory: ${s}`)}catch(t){throw String(t?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${s}. Please rebind with /grix open <valid-directory>.`):t}try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:s,env:this.config.env})}catch(t){throw r.error("acp-adapter",`Failed to spawn agent process: ${t}`),this.rejectDeferredEvents(`agent spawn failed: ${t instanceof Error?t.message:String(t)}`),this.emit("exit",null),t}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",t=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${t.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${t.message}`),this.rejectDeferredEvents(`agent process error: ${t.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",t=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${t})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${t})`),this.rejectDeferredEvents(`agent process exited (code=${t})`),this.emit("exit",t))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let s;this.acpMcpTools&&(s=await this.startInternalApiAndMcp()),this.acpClient=new w,this.acpClient.on("event",o=>this.handleAcpEvent(o)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.emit("exit",null))});const t=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,i=async o=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,cwd:e||this.resolveCwd(),mcpServers:s,sessionId:o})};let n=!1,a;try{await i(t)}catch(o){if(o instanceof T){await this.handleAuthRequired(o);return}if(!t)throw this.handleConnectFailure(o),o;if(r.warn("acp-adapter",`Failed to load persisted session ${t}, fallback to session/new: ${g(o)}`),n=!0,a=t,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${t}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,t,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${t}`)}await i(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[o,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(o,this.acpClient.sessionId);const u=n?"binding_recreated":"binding_ready",h=this.buildToolbarContext(u,l);n&&a&&(h.previous_session_id=a,h.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(o,"ready",l,h)}}async buildKiroRecoveryContext(e,s,t){try{const i=p.join(I(),".kiro","sessions","cli",`${s}.json`);await m.access(i),r.info("acp-adapter",`Reading kiro source session file: ${i}`);const n=await this.generateSummaryByKiroCli(i,t);if(!n)return;const a=p.join(I(),".grix","data","session-summaries");await m.mkdir(a,{recursive:!0});const o=p.join(a,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${i}`,"",n.trim(),""].join(`
6
- `);return await m.writeFile(o,l,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${o} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${i}`,`SUMMARY_FILE: ${o}`,"",n.trim()].join(`
7
- `)}catch(i){r.warn("acp-adapter",`Failed to build kiro recovery context: ${g(i)}`);return}}async generateSummaryByKiroCli(e,s){const i=["chat","--no-interactive","--trust-tools=read,grep",["\u8BF7\u57FA\u4E8E\u4E0B\u9762\u5F15\u7528\u7684 Kiro \u539F\u59CB\u4F1A\u8BDD\u6587\u4EF6\u751F\u6210\u6458\u8981\uFF0C\u8F93\u51FA\u5FC5\u987B\u4E3A Markdown\u3002","\u8981\u6C42\uFF1A1) \u7B80\u660E\u51C6\u786E\uFF1B2) \u5305\u542B goals/constraints/decisions/open_items \u56DB\u90E8\u5206\uFF1B3) \u4E0D\u8981\u8F93\u51FA\u4EE3\u7801\u5757\u56F4\u680F\u3002",`\u4F1A\u8BDD\u6587\u4EF6\uFF1A@"${e}"`].join(`
8
- `)],n=await this.runCommandCapture("kiro-cli",i,s,45e3);if(!n)return;const a=j(n).trim(),o=a.indexOf("> "),l=o>=0?a.slice(o+2).trim():a;if(l)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${a.length}, body_chars=${l.length})`),l.split(`
9
- `).filter(u=>!u.includes("Credits:")&&!u.includes("Time:")).join(`
10
- `).trim()}runCommandCapture(e,s,t,i){return new Promise((n,a)=>{const o=A(e,s,{cwd:t,stdio:["ignore","pipe","pipe"]});let l="",u="";const h=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${i}ms`))},i);o.stdout.on("data",d=>{l+=String(d)}),o.stderr.on("data",d=>{u+=String(d)}),o.on("error",d=>{clearTimeout(h),a(d)}),o.on("close",d=>{if(clearTimeout(h),d===0){n(l||u);return}a(new Error(`${e} exited with code ${d}: ${u||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new y,this.internalApi.setInvokeHandler(async(s,t)=>this.callbacks.agentInvoke(s,t)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`));const e=p.resolve(v,"../../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("acp-adapter",`Failed to start MCP tools: ${e}`);return}}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const s=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!s)throw e;const t=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(t,`Authentication required (${s.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(t,`Please open this URL to authenticate:
11
- ${i}
5
+ ${t}`):t}sendPromptWithRetry(e,t,i=0){this.acpClient.send(t).catch(s=>{if(r.error("acp-adapter",`Prompt failed (attempt ${i+1}): ${g(s)}`),i===0&&b(s)){r.info("acp-adapter",`Retrying prompt for event ${e.eventId} after retryable error`),e.buffer="",e.chunkSeq=0,e.currentSegmentIndex=0,e.currentClientMsgId=e.clientMsgIdBase,e.markdownSegmenter.reset(),e.responded=!1,e.awaitingToolResult=!1,e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),setTimeout(()=>{this.activeRun?.eventId===e.eventId&&this.acpClient?.isAlive?(this.resetIdleTimer(e),this.sendPromptWithRetry(e,t,1)):this.finishRun("failed",s instanceof Error?s.message:String(s))},j);return}const n=b(s)?"Agent \u9047\u5230\u5185\u90E8\u9519\u8BEF\uFF0C\u81EA\u52A8\u91CD\u8BD5\u540E\u4ECD\u7136\u5931\u8D25\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F\uFF0C\u6216\u53D1\u9001 /grix restart \u91CD\u542F agent\u3002":s instanceof Error?s.message:String(s);this.finishRun("failed",n)})}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new w;const i=this.resolveCwd();try{if(!(await m.stat(i)).isDirectory())throw new Error(`Bound path is not a directory: ${i}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${i}. Please rebind with /grix open <valid-directory>.`):s}try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:i,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp()),this.acpClient=new T,this.acpClient.on("event",o=>this.handleAcpEvent(o)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async o=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:o})};let n=!1,a;try{await s(i)}catch(o){if(o instanceof y){await this.handleAuthRequired(o);return}if(!i)throw this.handleConnectFailure(o),o;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${g(o)}`),n=!0,a=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await s(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[o,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(o,this.acpClient.sessionId);const h=n?"binding_recreated":"binding_ready",u=this.buildToolbarContext(h,l);n&&a&&(u.previous_session_id=a,u.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(o,"ready",l,u)}}async buildKiroRecoveryContext(e,t,i){try{const s=p.join(I(),".kiro","sessions","cli",`${t}.json`);await m.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const a=p.join(I(),".grix","data","session-summaries");await m.mkdir(a,{recursive:!0});const o=p.join(a,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
6
+ `);return await m.writeFile(o,l,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${o} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${o}`,"",n.trim()].join(`
7
+ `)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${g(s)}`);return}}async generateSummaryByKiroCli(e,t){const s=["chat","--no-interactive","--trust-tools=read,grep",["\u8BF7\u57FA\u4E8E\u4E0B\u9762\u5F15\u7528\u7684 Kiro \u539F\u59CB\u4F1A\u8BDD\u6587\u4EF6\u751F\u6210\u6458\u8981\uFF0C\u8F93\u51FA\u5FC5\u987B\u4E3A Markdown\u3002","\u8981\u6C42\uFF1A1) \u7B80\u660E\u51C6\u786E\uFF1B2) \u5305\u542B goals/constraints/decisions/open_items \u56DB\u90E8\u5206\uFF1B3) \u4E0D\u8981\u8F93\u51FA\u4EE3\u7801\u5757\u56F4\u680F\u3002",`\u4F1A\u8BDD\u6587\u4EF6\uFF1A@"${e}"`].join(`
8
+ `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const a=B(n).trim(),o=a.indexOf("> "),l=o>=0?a.slice(o+2).trim():a;if(l)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${a.length}, body_chars=${l.length})`),l.split(`
9
+ `).filter(h=>!h.includes("Credits:")&&!h.includes("Time:")).join(`
10
+ `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,a)=>{const o=R(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});let l="",h="";const u=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${s}ms`))},s);o.stdout.on("data",d=>{l+=String(d)}),o.stderr.on("data",d=>{h+=String(d)}),o.on("error",d=>{clearTimeout(u),a(d)}),o.on("close",d=>{if(clearTimeout(u),d===0){n(l||h);return}a(new Error(`${e} exited with code ${d}: ${h||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new k,this.internalApi.setInvokeHandler(async(t,i)=>this.callbacks.agentInvoke(t,i)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`));const e=p.resolve(v,"../../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("acp-adapter",`Failed to start MCP tools: ${e}`);return}}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const t=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
11
+ ${s}
12
12
 
13
- Waiting for authentication to complete...`);try{await this.acpClient.authenticate(s.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(t,"Authentication successful. Resuming...");const n=this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[p.resolve(v,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const s=/https?:\/\/[^\s"')\]]+/;let t=!1;const i=n=>{if(t)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(s);o&&(t=!0,this.agentProcess.removeListener("stderr",i),e(o[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{t||(t=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}handleAcpEvent(e){if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}const s=this.activeRun;if(!s)return;s.responded||(s.responded=!0);let t=!1;switch(e.type){case f.Text:{if(e.content){t=!0;const i=s.quotedStream.consume(e.content);i.quotedMessageId&&(s.quotedMessageId=i.quotedMessageId),i.deltaContent&&this.appendToStream(s,i.deltaContent)}break}case f.ToolUse:{t=!0,s.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(s,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(s.eventId,s.sessionId,e.toolName,e.toolInput??""));break}case f.ToolResult:{s.awaitingToolResult=!1,e.content&&(t=!0,this.emitRawEventEnvelope(s,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(s.eventId,s.sessionId,e.toolName??"",e.content));break}case f.Thinking:{e.content&&(t=!0,this.emitRawEventEnvelope(s,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(s.eventId,s.sessionId,e.content));break}case f.Error:{this.emitRawEventEnvelope(s,{type:"error",payload:{message:String(e.error??"unknown error")}}),r.error("acp-adapter",`ACP error: ${e.error}`);break}case f.Result:{this.emitRawEventEnvelope(s,{type:"result",payload:{done:e.done??!0}});const i=s.quotedStream.flush();i.deltaContent&&this.appendToStream(s,i.deltaContent),i.quotedMessageId&&(s.quotedMessageId=i.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}t&&(s.lastProgressAt=Date.now(),s.idleNoProgressCount=0),this.resetIdleTimer(s)}handlePermissionRequest(e){const s=e.permissionRequest;if(!s||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${s.toolName}`),this.acpClient.respondPermission(s.requestId,{behavior:"allow"}).catch(()=>{});return}const t=s.toolCallId;this.pendingApprovals.set(t,e.requestId);const i=this.activeRun;i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:s.requestId,tool_call_id:t,tool_name:s.toolName,tool_title:s.toolTitle,options:s.options}})||this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:t,toolName:s.toolName,toolTitle:s.toolTitle,options:s.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${s.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(t))}emitRawEventEnvelope(e,s){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:s.type,payload:s.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const s=e.awaitingToolResult?D:N;e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient;t?.isAlive?t.ping(5e3).then(i=>{if(this.activeRun?.eventId===e.eventId)if(i){const n=e.lastProgressAt>e.lastIdleCheckAt;e.lastIdleCheckAt=Date.now(),n?(e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(r.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.emit("stuck")):(r.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}else r.error("acp-adapter",`Agent idle for ${s/1e3}s and ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${s/1e3}s`),this.emit("stuck")}).catch(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Agent idle for ${s/1e3}s and ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${s/1e3}s`),this.emit("stuck"))}):(r.error("acp-adapter",`Agent idle for ${s/1e3}s (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${s/1e3}s`),this.emit("stuck"))},s)}appendToStream(e,s){e.buffer+=s,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),q))}emitSegmentedStream(e,s){if(!s)return;const t=e.markdownSegmenter.push(s);for(const i of t)i.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,i.text,++e.chunkSeq,i.closeAfter===!0,e.currentClientMsgId),i.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${i.reason??"threshold"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const s=e.buffer;e.buffer="",this.emitSegmentedStream(e,s)}finishRun(e,s){const t=this.activeRun;if(!t)return;this.activeRun=null,this.emit("eventDone",t.eventId),t.flushTimer&&(clearTimeout(t.flushTimer),t.flushTimer=null),t.idleTimer&&(clearTimeout(t.idleTimer),t.idleTimer=null);const i=t.quotedStream.flush();i.deltaContent&&(t.buffer+=i.deltaContent),i.quotedMessageId&&(t.quotedMessageId=i.quotedMessageId),t.buffer&&(this.emitSegmentedStream(t,t.buffer),t.buffer=""),s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const n=++t.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n,t.currentClientMsgId).then(()=>{t.silent||this.callbacks.sendEventResult(t.eventId,e,s),this.persistEventResult(t,e,s)}).catch(a=>{r.error("acp-adapter",`finalStreamChunk ACK failed event=${t.eventId}: ${a}`),this.callbacks.sendStreamChunk(t.eventId,t.sessionId,`
14
- `,n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,s),this.persistEventResult(t,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,`
15
- `,n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,s),this.persistEventResult(t,e,s))}persistEventResult(e,s,t){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:s,msg:t,updatedAt:Date.now()})}}class L extends S{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ie as AcpAdapter};
13
+ Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const n=this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[p.resolve(v,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);o&&(i=!0,this.agentProcess.removeListener("stderr",s),e(o[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}const t=this.activeRun;if(!t)return;t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{i=!0,t.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??""));break}case f.ToolResult:{t.awaitingToolResult=!1,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,e.toolName??"",e.content));break}case f.Thinking:{e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{this.emitRawEventEnvelope(t,{type:"error",payload:{message:String(e.error??"unknown error")}}),r.error("acp-adapter",`ACP error: ${e.error}`);break}case f.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const i=t.toolCallId;this.pendingApprovals.set(i,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})||this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?D:N;e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const i=this.acpClient;i?.isAlive?i.ping(5e3).then(s=>{if(this.activeRun?.eventId===e.eventId)if(s){const n=e.lastProgressAt>e.lastIdleCheckAt;e.lastIdleCheckAt=Date.now(),n?(e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(r.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.emit("stuck")):(r.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}else r.error("acp-adapter",`Agent idle for ${t/1e3}s and ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${t/1e3}s`),this.emit("stuck")}).catch(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Agent idle for ${t/1e3}s and ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${t/1e3}s`),this.emit("stuck"))}):(r.error("acp-adapter",`Agent idle for ${t/1e3}s (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${t/1e3}s`),this.emit("stuck"))},t)}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),q))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}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.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.activeRun=null,this.emit("eventDone",i.eventId),i.flushTimer&&(clearTimeout(i.flushTimer),i.flushTimer=null),i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer=""),t&&this.callbacks.sendRunError(i.eventId,i.sessionId,t);const n=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,n,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t)}).catch(a=>{r.error("acp-adapter",`finalStreamChunk ACK failed event=${i.eventId}: ${a}`),this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t)}):(this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class L extends S{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{se as AcpAdapter};
@@ -2,8 +2,7 @@ import{spawn as H,spawnSync as fe,execSync as L}from"node:child_process";import{
2
2
  `):"No skills found",data:n}}catch(n){return{status:"failed",message:`Failed to scan skills: ${n instanceof Error?n.message:n}`}}if(!this.claudeProcess&&!this.claudePty)return{status:"failed",message:"Claude process is not running"};if(this.getStatus().busy)return{status:"failed",message:"Claude is busy, try again later"};const s=t?`/${e} ${t}`:`/${e}`;try{return this.claudePty?this.claudePty.write(`${s}\r`):this.claudeProcess?.stdin?.write(`${s}\r`),{status:"ok",message:`Sent: ${s}`}}catch(n){return{status:"failed",message:`Failed to send: ${n instanceof Error?n.message:n}`}}}handleStatusLineUpdate(e){try{const t=e.context_window,i=e.cost,s=e.model,n=e.rate_limits??e.rateLimits,r=t?.current_usage;if(this.sessionState={contextWindow:{usedPercentage:t?.used_percentage!=null?g(t.used_percentage):null,remainingPercentage:t?.remaining_percentage!=null?g(t.remaining_percentage):null,totalInputTokens:g(t?.total_input_tokens),totalOutputTokens:g(t?.total_output_tokens),contextWindowSize:g(t?.context_window_size)||2e5,currentUsage:r?{inputTokens:g(r.input_tokens),outputTokens:g(r.output_tokens),cacheCreationInputTokens:g(r.cache_creation_input_tokens),cacheReadInputTokens:g(r.cache_read_input_tokens)}:null},cost:{totalCostUsd:g(i?.total_cost_usd),totalDurationMs:g(i?.total_duration_ms),totalApiDurationMs:g(i?.total_api_duration_ms),totalLinesAdded:g(i?.total_lines_added),totalLinesRemoved:g(i?.total_lines_removed)},rateLimits:this.parseStatusRateLimits(n),model:{id:String(s?.id??""),displayName:String(s?.display_name??"")},fastMode:e.fast_mode===!0,effort:e.effort!=null?String(e.effort):void 0,thinkingEnabled:e.thinking_enabled===!0,exceeds200kTokens:e.exceeds_200k_tokens===!0,version:String(e.version??""),sampledAt:Date.now()},n){const a=this.sessionState.rateLimits?.fiveHour,c=this.sessionState.rateLimits?.sevenDay;o.info("claude-adapter",`[rate-limits] statusLine parsed: fiveHour=${a?`${a.usedPercentage}% resetsAt=${a.resetsAt}`:"n/a"} sevenDay=${c?`${c.usedPercentage}% resetsAt=${c.resetsAt}`:"n/a"}`)}else o.debug("claude-adapter","[rate-limits] statusLine: no rate_limits in payload");try{this.bridgeCallbacks.onStatusLineUpdated?.(this.sessionState)}catch(a){o.warn("claude-adapter",`onStatusLineUpdated callback error: ${a instanceof Error?a.message:a}`)}}catch(t){o.warn("claude-adapter",`Failed to parse statusLine payload: ${t instanceof Error?t.message:t}`)}}parseStatusRateLimits(e){if(!e)return;const t=n=>{if(!n||typeof n!="object")return;const r=n,a=g(r.used_percentage??r.usedPercent),c=g(r.resets_at??r.resetsAt);if(!(a<=0&&c<=0))return{usedPercentage:a,resetsAt:c}},i=t(e.five_hour??e.fiveHour),s=t(e.seven_day??e.sevenDay);if(!(!i&&!s))return{...i?{fiveHour:i}:{},...s?{sevenDay:s}:{}}}deliverInboundEvent(e){if(this.sessionId||(this.sessionId=e.session_id),this.pruneCompletedEvents(),this.completedEventIds.has(e.event_id)){o.info("claude-adapter",`Event ${e.event_id} rejected: duplicate`),this.bridgeCallbacks.sendEventResult(e.event_id,"responded","duplicate event");return}if(Date.now()<this.authFailureUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: auth cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude authentication failed \u2014 please re-login");return}if(Date.now()<this.usageLimitUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: usage limit cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude usage limit reached \u2014 waiting for reset");return}if(this.activeEvent){if(this.activeEvent.eventId===e.event_id){o.info("claude-adapter",`Event ${e.event_id} ignored: same as active event`);return}if(this.queuedEventIds.has(e.event_id))return;if(this.queuedEvents.length>=ce){o.warn("claude-adapter",`Event ${e.event_id} rejected: queue full (size=${this.queuedEvents.length})`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","agent queue full");return}this.queuedEvents.push(e),this.queuedEventIds.add(e.event_id),o.info("claude-adapter",`Queue event ${e.event_id} (active=${this.activeEvent.eventId}, size=${this.queuedEvents.length})`);return}if(this.stopHookBarrierSessionId===e.session_id){if(this.queuedEventIds.has(e.event_id))return;if(this.queuedEvents.length>=ce){o.warn("claude-adapter",`Event ${e.event_id} rejected: queue full during barrier (size=${this.queuedEvents.length})`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","agent queue full");return}this.queuedEvents.push(e),this.queuedEventIds.add(e.event_id),o.info("claude-adapter",`Queue event ${e.event_id} (barrier session=${e.session_id})`);return}this.activeEvent={eventId:e.event_id,sessionId:e.session_id,rawEvent:e},this.lastClearedEvent=null,this.markActiveEventActivity(e.event_id,e.session_id),this.resetActiveEventHardTimer(e.event_id),this.ensureClaudeAndPushEvent(e)}deliverStopEvent(e,t){if(this.activeEvent?.eventId!==e)return;const i=this.activeEvent.sessionId;o.info("claude-adapter",`Stop requested for active event=${e} \u2014 killing Claude process`),this.mcpServerReady&&this.pushNotification("notifications/event_stop",{event_id:e,session_id:i,stop_id:O()}),this.bridgeCallbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActiveEvent(),this.killClaudeProcess("stop")}async handleLocalAction(e){if(!this.mcpServerReady)return{handled:!1,kind:""};const t=String(e.action_type??"");return t==="claude_interaction_reply"?this.handleQuestionReply(e):t==="exec_approve"||t==="exec_reject"?this.handlePermissionApproval(e):(this.pushNotification("notifications/local_action",e),{handled:!0,kind:""})}async handleQuestionReply(e){const t=e.params??{},i=String(t.request_id??"");if(!i)return o.warn("claude-adapter","Question reply missing request_id"),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=S(),n=new ne(s.questionRequestsDir),r=t.resolution??{},a=String(r.type??""),c=r;if(a==="action"&&String(r.value??"")==="cancel"){o.info("claude-adapter",`Question cancelled by user: request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,c);const f=this.pendingQuestion.get(i);return f&&this.activeEvent?.eventId===f.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"}}let u="";if(a==="text"?u=String(r.value??""):a==="map"&&(u=(Array.isArray(r.entries)?r.entries:[]).map(f=>f.value).join(", ")),!u){o.warn("claude-adapter",`Empty answer for question reply request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,c);const p=this.pendingQuestion.get(i);return p&&this.activeEvent?.eventId===p.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"}}o.info("claude-adapter",`Resolving question in store: request_id=${i} answer=${u.slice(0,80)}`),await n.resolveRequest(i,"answer",u,c);const l=this.pendingQuestion.get(i);return l&&this.activeEvent?.eventId===l.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"answer"}),{handled:!0,kind:"question_reply"}}async handlePermissionApproval(e){const t=e.params??{},i=String(t.approval_id??t.approval_command_id??t.tool_call_id??""),s=String(e.action_type??"")==="exec_approve";if(!i)return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_id_required","approval_id is required"),{handled:!0,kind:"permission_approval"};if(!this.pendingPermissions.get(i))return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending permission for approval_id: ${i}`),{handled:!0,kind:"permission_approval"};const r=S(),a=new q(r.permissionRequestsDir),c=s?"allow":"deny";return await a.resolveRequest(i,c),this.pendingPermissions.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok"),o.info("claude-adapter",`Permission ${c}: approvalId=${i}`),{handled:!0,kind:"permission_approval"}}async ensureClaudeAndPushEvent(e,t=0){t===0&&!this.claudeProcess&&this.startComposing(e.session_id,"preparing");try{if(this.mcpChannelBroken){await this.runSingleTurnFallback(e,"mcp channel unavailable");return}if(await this.ensureClaudeProcessReady(),!this.mcpServerReady)throw new Error("MCP stdio server not available");if(await this.waitForNotifyPort(3e4),process.platform==="win32"?await this.waitForWindowsChannelReady(oe):await this.waitForChannelListening(oe),this.activeEvent?.eventId!==e.event_id||this.mcpChannelBroken)return;const s={},n=[["chat_id",e.session_id],["event_id",e.event_id],["event_type","user_chat"],["message_id",e.msg_id],["sender_id",e.sender_id],["user",e.sender_id],["user_id",e.sender_id],["msg_id",e.msg_id],["session_type",e.session_type!=null?String(e.session_type):void 0],["msg_type",e.msg_type!=null?String(e.msg_type):void 0],["quoted_message_id",e.quoted_message_id],["ts",e.created_at!=null?new Date(Number(e.created_at)).toISOString():void 0],["owner_id",e.owner_id],["agent_id",e.agent_id],["attachments_json",e.attachments_json],["context_messages_json",e.context_messages_json]];for(const[r,a]of n)a!=null&&a!==""&&(s[r]=a);this.pushNotification("notifications/claude/channel",{content:e.content??"",meta:s}),o.info("claude-adapter",`Event ${e.event_id} pushed to MCP stdio server`)}catch(s){if(this.stopped){this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"canceled","adapter stopped"),this.clearActiveEvent());return}if(s instanceof Error&&(s.message.includes("exited before")||s.message.includes("timeout")||s.message.includes("spawn failed"))&&t<3)return o.warn("claude-adapter",`Spawn failed (attempt ${t+1}/4), retrying: ${s}`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),U(this.claudeCliSessionId),K(this.claudeCliSessionId,[]),await new Promise(r=>setTimeout(r,(t+1)*2e3)),this.ensureClaudeAndPushEvent(e,t+1);if(o.error("claude-adapter",`Failed to deliver event ${e.event_id}: ${s}`),this.shouldFallbackToSingleTurn(s)){o.warn("claude-adapter",`Channel path failed, switching to single-turn fallback: ${s instanceof Error?s.message:String(s)}`);try{await this.runSingleTurnFallback(e,s instanceof Error?s.message:String(s));return}catch(r){o.error("claude-adapter",`Single-turn fallback failed for ${e.event_id}: ${r instanceof Error?r.message:String(r)}`)}}this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"failed",s instanceof Error?s.message:String(s)),this.clearActiveEvent())}}shouldFallbackToSingleTurn(e){const t=(e instanceof Error?e.message:String(e)).toLowerCase();return t?t.includes("mcp server startup failed")||t.includes("claude channel listener not ready")||t.includes("channels are not currently available")||t.includes("mcp stdio server not available")||t.includes("notify port not ready"):!1}buildSingleTurnPrompt(e){const t=e.context_messages_json?String(e.context_messages_json):"",i=["Reply to the latest user message directly and concisely."];return t&&(i.push("Conversation context (JSON):"),i.push(t)),i.push("Latest user message:"),i.push(e.content??""),i.join(`
3
3
  `)}extractSingleTurnDelta(e){const t=e.trim();if(!t)return"";try{const i=JSON.parse(t),s=String(i.type??"");if(s==="content_block_delta"){const n=i.delta;if(n&&typeof n=="object"){const r=String(n.text??"");if(r)return r}}return s==="result"?String(i.result??""):typeof i.text=="string"&&i.text?i.text:""}catch{return t}}getCliVersion(){if(this.cachedCliVersion)return this.cachedCliVersion;const e=this.config.command||"claude";try{const i=L(`${e} --version`,{encoding:"utf-8",timeout:5e3}).trim().match(/^([\d.]+)/);this.cachedCliVersion=i?.[1]??"0.0.0"}catch{this.cachedCliVersion="0.0.0"}return this.cachedCliVersion}static versionGte(e,t){const i=e.split(".").map(Number),s=t.split(".").map(Number);for(let n=0;n<Math.max(i.length,s.length);n++){const r=i[n]??0,a=s[n]??0;if(r>a)return!0;if(r<a)return!1}return!0}buildClaudeRuntimeEnv(){const e={...process.env,...this.config.env};return delete e.CLAUDECODE,e.CLAUDE_PLUGIN_DATA=S().dataDir,e.GRIX_HOOK_SIGNALS_PATH=this.resolveHookSignalsPath(),this.internalApi&&(e.GRIX_CLAUDE_INTERNAL_API_URL=this.internalApi.baseUrl,e.GRIX_CLAUDE_INTERNAL_API_TOKEN=this.internalApi.token),e}async runSingleTurnFallback(e,t){const i=this.resolveSessionRuntime(),s=this.config.options??{},n=this.config.command||"claude",r=(this.config.args??[]).filter(I=>{const m=String(I).trim().toLowerCase();return m!=="--dangerously-load-development-channels"&&m!=="--session-id"&&m!=="--resume"}),a=String(i.cwd??"").trim(),c=String(i.modelId??"").trim(),u=ue(i.modeId);await this.ensureWorkspaceTrust(a),await this.ensureGrowthBookFeatures(a),await this.injectStatusLineSettings(a),await this.ensureStdioMcpServer();const l=this.buildClaudeRuntimeEnv(),p=i.pluginDir??s.pluginDir??await this.ensureClaudePluginDir(),f=this.buildSingleTurnPrompt(e),_=[...r,"-p","--output-format","stream-json","--include-partial-messages","--no-session-persistence"];X.versionGte(this.getCliVersion(),"2.1.150")&&_.push("--verbose"),_.push("--tools","","--plugin-dir",p,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath,"--permission-mode",u===M.fullAuto?"bypassPermissions":"default"),c&&_.push("--model",c),_.push(f),o.warn("claude-adapter",`Running single-turn fallback for event ${e.event_id}: ${t}`),this.bridgeCallbacks.sendEventAck(e.event_id,e.session_id),this.startComposing(e.session_id,"fallback_single_turn"),await new Promise((I,m)=>{const $=H(n,_,{cwd:a||process.cwd(),env:l,stdio:["ignore","pipe","pipe"],detached:!0,windowsHide:!0});let C="",T=0,y=!1;const v=`fallback_${e.event_id}_${Date.now()}`;let w="";$.stdout?.setEncoding("utf8"),$.stdout?.on("data",E=>{w+=E;let A=w.indexOf(`
4
4
  `);for(;A>=0;){const pe=w.slice(0,A);w=w.slice(A+1);const Z=this.extractSingleTurnDelta(pe);Z&&(T+=1,y=!0,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,Z,T,!1,v,e.quoted_message_id)),A=w.indexOf(`
5
- `)}}),$.stderr?.setEncoding("utf8"),$.stderr?.on("data",E=>{C.length>=V||(C+=E,C.length>V&&(C=C.slice(0,V)))}),$.on("error",E=>m(E)),$.on("close",E=>{if(w.trim()){const A=this.extractSingleTurnDelta(w);A&&(T+=1,y=!0,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,A,T,!1,v,e.quoted_message_id))}if(E!==0&&!y){const A=C.trim();m(new Error(A||`claude single-turn exited with code ${E}`));return}T+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,`
6
- `,T,!0,v,e.quoted_message_id),I()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=this.buildClaudeRuntimeEnv(),c=ue(s.modeId),u=String(s.modelId??"").trim(),l=String(s.cwd??"").trim();this.claudeSessionCwd=l;const p=String(s.claudeSessionId??"").trim()||O();this.claudeCliSessionId=p,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(p);const f=Ke(p,l||void 0);if(f||(K(p,[]),U(p)),this.runtimeResolver&&!l)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!l)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(l),await this.ensureGrowthBookFeatures(l),await this.injectStatusLineSettings(l);let _=!1,I="",m=null;try{t(),this.notifyPort=await He([this.internalApiPort]),_=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const C=i.pluginDir||await this.ensureClaudePluginDir(),T=f?["--resume",p]:["--session-id",p],y=[...r,"--name",`grix-${this.sessionId}`,...T,"--plugin-dir",C,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];if(u&&(/^claude/i.test(u)?y.push("--model",u):(o.warn("claude-adapter",`Skipping --model ${u}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=u)),c===M.fullAuto&&y.push("--dangerously-skip-permissions"),y.push("--dangerously-load-development-channels",`server:${x}`),process.platform==="win32"){const v=de.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),w=Ve(this.claudeCliSessionId);await Ye(w,v),y.push("--append-system-prompt-file",w)}else y.push("--append-system-prompt",de);if(process.platform==="win32")if(j){const v="cmd.exe",w=["/c",n,...y];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`);const E=j.spawn(v,w,{name:"xterm-256color",cols:120,rows:30,cwd:l,env:a,useConpty:!0,conptyInheritCursor:!1});this.claudePty=E,this.claudeChildPid=E.pid,this.startPtyAutoConfirm(E),m=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`),m=H(n,y,{cwd:l,env:a,stdio:["pipe","pipe","pipe"],detached:!0,shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`);try{B("/usr/bin/expect")}catch{throw new Error("/usr/bin/expect not found. Install it with: apt install expect / dnf install expect / brew install expect")}const v=h(ye(),`grix-claude-${O()}`);await b(v,{recursive:!0});const{expectPath:w,pidPath:E}=await Be(v,n,y);I=E,t(),m=H("/usr/bin/expect",[w],{cwd:l,env:a,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=m,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,m&&m.on("error",v=>{o.error("claude-adapter","Claude process spawn error: "+v),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=m?.pid??0;else{const v=await ze(m,I);if(t(),!Number.isFinite(v)||v<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=v}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(C){if(m?.pid&&D(m,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===m&&(this.claudeProcess=null),this.claudeChildPid=0,_&&this.releaseNotifyPortReservation(),this.stopMcpServer(),C}const $=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+$+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}setupProcessOrPtyHandlers(){this.claudeProcess&&(this.claudeProcess.on("exit",(e,t)=>{if(o.info("claude-adapter",`Claude process exited (code=${e}, signal=${t})`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const i=this.activeEvent.replied?"responded":"failed",s=this.activeEvent.replied?void 0:"Claude process exited";o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, replied=${!!this.activeEvent.replied}, sending ${i}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,s),this.clearActiveEvent()}this.stopped||this.emit("exit",e)}}),this.claudeProcess.stdout?.on("data",e=>{this.handleClaudeOutput(e.toString())}),this.claudeProcess.stderr?.on("data",e=>{const t=e.toString().trim();t&&o.info("claude-adapter",`[claude stderr] ${t}`),this.checkFailurePatterns(t)})),this.claudePty&&(this.claudePty.onExit(({exitCode:e,signal:t})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${e}, signal=${t})`),this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const i=this.activeEvent.replied?"responded":"failed",s=this.activeEvent.replied?void 0:"Claude process exited";o.error("claude-adapter",`Claude PTY exited with active event ${this.activeEvent.eventId}, replied=${!!this.activeEvent.replied}, sending ${i}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,s),this.clearActiveEvent()}this.stopped||this.emit("exit",e)}}),this.claudePty.onData(e=>{this.handleClaudeOutput(e),this.checkFailurePatterns(e)})),this.watchGrowthBookCache()}handleClaudeOutput(e){const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,c=>`\\x${c.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const c=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(u=>!!u&&u>0);K(a,c)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&/trust.*folder|quick.*safety.*check/.test(n)&&(o.info("claude-adapter","Auto-accepting workspace trust dialog"),this.claudePty.write("1\r")),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 development channels require an Anthropic model (e.g. claude-*). If using a non-Anthropic model, the --model flag was skipped to allow channels to load.'),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},Ne))}}checkFailurePatterns(e){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0),Me.some(t=>t.test(e))&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+ae),Le.some(t=>t.test(e))&&(o.error("claude-adapter",`Usage limit: ${e}`),this.usageLimitUntil=Date.now()+ae)}async ensureClaudePluginDir(){const e=this.resolveProjectRoot(),t=h(S().dataDir,"claude-plugin"),i=h(t,".claude-plugin"),s=h(i,"plugin.json"),n=h(e,".claude-plugin","plugin.json");await b(i,{recursive:!0});let r="";try{r=await P(n,"utf8")}catch{r=`${JSON.stringify({name:"grix-connector",version:"0.1.0",description:"Claude Code channel plugin for grix-connector.",license:"MIT"},null,2)}
5
+ `)}}),$.stderr?.setEncoding("utf8"),$.stderr?.on("data",E=>{C.length>=V||(C+=E,C.length>V&&(C=C.slice(0,V)))}),$.on("error",E=>m(E)),$.on("close",E=>{if(w.trim()){const A=this.extractSingleTurnDelta(w);A&&(T+=1,y=!0,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,A,T,!1,v,e.quoted_message_id))}if(E!==0&&!y){const A=C.trim();m(new Error(A||`claude single-turn exited with code ${E}`));return}T+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,"",T,!0,v,e.quoted_message_id),I()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=this.buildClaudeRuntimeEnv(),c=ue(s.modeId),u=String(s.modelId??"").trim(),l=String(s.cwd??"").trim();this.claudeSessionCwd=l;const p=String(s.claudeSessionId??"").trim()||O();this.claudeCliSessionId=p,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(p);const f=Ke(p,l||void 0);if(f||(K(p,[]),U(p)),this.runtimeResolver&&!l)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!l)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(l),await this.ensureGrowthBookFeatures(l),await this.injectStatusLineSettings(l);let _=!1,I="",m=null;try{t(),this.notifyPort=await He([this.internalApiPort]),_=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const C=i.pluginDir||await this.ensureClaudePluginDir(),T=f?["--resume",p]:["--session-id",p],y=[...r,"--name",`grix-${this.sessionId}`,...T,"--plugin-dir",C,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];if(u&&(/^claude/i.test(u)?y.push("--model",u):(o.warn("claude-adapter",`Skipping --model ${u}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=u)),c===M.fullAuto&&y.push("--dangerously-skip-permissions"),y.push("--dangerously-load-development-channels",`server:${x}`),process.platform==="win32"){const v=de.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),w=Ve(this.claudeCliSessionId);await Ye(w,v),y.push("--append-system-prompt-file",w)}else y.push("--append-system-prompt",de);if(process.platform==="win32")if(j){const v="cmd.exe",w=["/c",n,...y];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`);const E=j.spawn(v,w,{name:"xterm-256color",cols:120,rows:30,cwd:l,env:a,useConpty:!0,conptyInheritCursor:!1});this.claudePty=E,this.claudeChildPid=E.pid,this.startPtyAutoConfirm(E),m=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`),m=H(n,y,{cwd:l,env:a,stdio:["pipe","pipe","pipe"],detached:!0,shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${l} mode=${c} ${f?"resume":"new"} ${n} ${y.join(" ")}`);try{B("/usr/bin/expect")}catch{throw new Error("/usr/bin/expect not found. Install it with: apt install expect / dnf install expect / brew install expect")}const v=h(ye(),`grix-claude-${O()}`);await b(v,{recursive:!0});const{expectPath:w,pidPath:E}=await Be(v,n,y);I=E,t(),m=H("/usr/bin/expect",[w],{cwd:l,env:a,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=m,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,m&&m.on("error",v=>{o.error("claude-adapter","Claude process spawn error: "+v),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=m?.pid??0;else{const v=await ze(m,I);if(t(),!Number.isFinite(v)||v<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=v}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(C){if(m?.pid&&D(m,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===m&&(this.claudeProcess=null),this.claudeChildPid=0,_&&this.releaseNotifyPortReservation(),this.stopMcpServer(),C}const $=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+$+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}setupProcessOrPtyHandlers(){this.claudeProcess&&(this.claudeProcess.on("exit",(e,t)=>{if(o.info("claude-adapter",`Claude process exited (code=${e}, signal=${t})`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const i=this.activeEvent.replied?"responded":"failed",s=this.activeEvent.replied?void 0:"Claude process exited";o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, replied=${!!this.activeEvent.replied}, sending ${i}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,s),this.clearActiveEvent()}this.stopped||this.emit("exit",e)}}),this.claudeProcess.stdout?.on("data",e=>{this.handleClaudeOutput(e.toString())}),this.claudeProcess.stderr?.on("data",e=>{const t=e.toString().trim();t&&o.info("claude-adapter",`[claude stderr] ${t}`),this.checkFailurePatterns(t)})),this.claudePty&&(this.claudePty.onExit(({exitCode:e,signal:t})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${e}, signal=${t})`),this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const i=this.activeEvent.replied?"responded":"failed",s=this.activeEvent.replied?void 0:"Claude process exited";o.error("claude-adapter",`Claude PTY exited with active event ${this.activeEvent.eventId}, replied=${!!this.activeEvent.replied}, sending ${i}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,s),this.clearActiveEvent()}this.stopped||this.emit("exit",e)}}),this.claudePty.onData(e=>{this.handleClaudeOutput(e),this.checkFailurePatterns(e)})),this.watchGrowthBookCache()}handleClaudeOutput(e){const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,c=>`\\x${c.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const c=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(u=>!!u&&u>0);K(a,c)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&/trust.*folder|quick.*safety.*check/.test(n)&&(o.info("claude-adapter","Auto-accepting workspace trust dialog"),this.claudePty.write("1\r")),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 development channels require an Anthropic model (e.g. claude-*). If using a non-Anthropic model, the --model flag was skipped to allow channels to load.'),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},Ne))}}checkFailurePatterns(e){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0),Me.some(t=>t.test(e))&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+ae),Le.some(t=>t.test(e))&&(o.error("claude-adapter",`Usage limit: ${e}`),this.usageLimitUntil=Date.now()+ae)}async ensureClaudePluginDir(){const e=this.resolveProjectRoot(),t=h(S().dataDir,"claude-plugin"),i=h(t,".claude-plugin"),s=h(i,"plugin.json"),n=h(e,".claude-plugin","plugin.json");await b(i,{recursive:!0});let r="";try{r=await P(n,"utf8")}catch{r=`${JSON.stringify({name:"grix-connector",version:"0.1.0",description:"Claude Code channel plugin for grix-connector.",license:"MIT"},null,2)}
7
6
  `}let a="";try{a=await P(s,"utf8")}catch{}a!==r&&(await k(s,r,"utf8"),o.info("claude-adapter",`Wrote Claude plugin manifest: ${s}`));const c=h(e,"dist","scripts"),u=m=>`"${String(m).replace(/"/g,'\\"')}"`,l=m=>`${u(process.execPath)} ${u(h(c,m))}`,p=`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],Elicitation:[{hooks:[{type:"command",command:l("elicitation-hook.js")}]}],ElicitationResult:[{matcher:"",hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],UserPromptSubmit:[{hooks:[{type:"command",command:l("user-prompt-submit-hook.js")}]}],PreToolUse:[{matcher:"ExitPlanMode",hooks:[{type:"command",command:l("approve-plan-hook.js")}]},{matcher:"",hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],PostToolUse:[{matcher:"",hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],PostToolUseFailure:[{matcher:"",hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],PermissionRequest:[{matcher:"",hooks:[{type:"command",command:l("permission-hook.js")}]}],Notification:[{matcher:"idle_prompt",hooks:[{type:"command",command:l("notification-hook.js")}]}],PermissionDenied:[{matcher:"",hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],Stop:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],StopFailure:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],PreCompact:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],PostCompact:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}],ConfigChange:[{hooks:[{type:"command",command:l("lifecycle-hook.js")}]}]}},null,2)}
8
7
  `,f=h(t,"hooks"),_=h(f,"hooks.json");await b(f,{recursive:!0});let I="";try{I=await P(_,"utf8")}catch{}return I!==p&&(await k(_,p,"utf8"),o.info("claude-adapter",`Wrote Claude hooks config: ${_}`)),t}async ensureStdioMcpServer(){const e=this.resolveProjectRoot(),t=this.resolveStdioServerPath(e);if(this.ensureStdioServerArtifact(e,t),!F(t))throw new Error(`MCP stdio server entry point not found: ${t}`);const i=this.getInternalApiUrl(),s=this.notifyPort,n=[t,"--handle-url",i,"--notify-port",String(s)],r=Je(this.claudeCliSessionId);await We(r,n),this.claudeMcpConfigPath=r,this.mcpServerReady=!0,this.startActivityTracking()}resolveProjectRoot(){const e=we(import.meta.url);return _e(e,"..","..","..","..")}resolveStdioServerPath(e=this.resolveProjectRoot()){return h(e,"dist","mcp","stdio","server.js")}ensureStdioServerArtifact(e,t){if(F(t)||le)return;le=!0;const i=h(e,"node_modules","typescript","bin","tsc"),s=h(e,"tsconfig.json");if(!F(i)||!F(s))return;o.warn("claude-adapter",`MCP stdio server artifact missing, attempting build: ${t}`);const n=fe(process.execPath,[i,"-p",s],{cwd:e,env:process.env,encoding:"utf8",timeout:6e4});if(n.status!==0){const r=`${n.stderr??""}${n.stdout??""}`.trim();throw new Error(`MCP stdio server build failed: ${r||`exit ${n.status??"unknown"}`}`)}}getInternalApiUrl(){return this.internalApi?this.internalApi.url:process.env.GRIX_CONNECTOR_INTERNAL_API?process.env.GRIX_CONNECTOR_INTERNAL_API:`http://127.0.0.1:${this.internalApiPort}`}internalApiPort=0;notifyPort=0;notifySocket=null;async waitForNotifyPort(e){if(this.notifyPort<=0)return;const t=Date.now();for(;Date.now()-t<e;)try{await new Promise((i,s)=>{const n=G.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{n.destroy(),i()});n.on("error",s),setTimeout(()=>{n.destroy(),s(new Error("probe timeout"))},2e3)});return}catch{await new Promise(i=>setTimeout(i,500))}throw new Error(`Notify port ${this.notifyPort} not ready within ${e}ms`)}async waitForChannelListening(e){const t=Date.now(),i=8e3;for(;Date.now()-t<e;){if(this.startupChannelListening){const s=this.startupChannelListeningAt||Date.now(),n=Date.now()-s;if(n>=N)return;await new Promise(r=>setTimeout(r,N-n));return}if(this.alive&&this.notifyPort>0&&Date.now()-t>i){o.info("claude-adapter",`Channel listener fallback: notify port connected, assuming ready after ${Date.now()-t}ms (resume mode may skip "Listening" output)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(s=>setTimeout(s,200))}throw new Error(`Claude channel listener not ready within ${e}ms`)}ptyAutoConfirmTimer=null;startPtyAutoConfirm(e){this.ptyAutoConfirmTimer&&clearInterval(this.ptyAutoConfirmTimer);let t=!1;const i=()=>{if(this.startupChannelListening||this.stopped){this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null);return}try{e.write("\r"),t||(o.info("claude-adapter","PTY auto-confirm: sending Enter for dev channels dialog"),t=!0)}catch{}};setTimeout(i,1e3),this.ptyAutoConfirmTimer=setInterval(i,2e3),setTimeout(()=>{this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null)},3e4).unref()}sendDeferredModelSwitch(){if(!this.deferredModelId)return;const e=this.deferredModelId;this.deferredModelId=null,setTimeout(()=>{const t=this.claudePty??this.claudeProcess?.stdin;if(!(!t||this.stopped))try{const i=`/model ${e}
9
8
  `;"write"in t?t.write(i):t.write(i,()=>{}),o.info("claude-adapter",`Deferred model switch: /model ${e}`)}catch{}},3e3)}async waitForWindowsChannelReady(e){const t=Date.now();let i=!1;const s=setInterval(()=>{if(this.startupChannelListening){clearInterval(s);return}try{this.claudePty?(this.claudePty.write("\r"),i||(o.info("claude-adapter","Windows PTY: sending Enter to auto-confirm dev channels dialog"),i=!0)):this.claudeProcess?.stdin?.writable&&(this.claudeProcess.stdin.write("\r"),i||(o.info("claude-adapter","Windows shell: sending Enter to auto-confirm dev channels dialog"),i=!0))}catch{}},3e3);try{for(;Date.now()-t<e;){if(this.startupChannelListening){const a=this.startupChannelListeningAt||Date.now(),c=Date.now()-a;if(c>=N)return;await new Promise(u=>setTimeout(u,N-c));return}const n=Date.now()-t,r=this.claudePty?Ue:12e3;if(this.alive&&this.mcpServerReady&&n>r){o.info("claude-adapter",`Windows ${this.claudePty?"PTY":"shell"} fallback: assuming channel ready after ${n}ms (no stdout detection, MCP server connected)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(a=>setTimeout(a,500))}}finally{clearInterval(s)}throw new Error(`Windows channel ready check timed out within ${e}ms`)}pushNotification(e,t){if(!this.mcpServerReady||this.notifyPort<=0)return;const i=JSON.stringify({jsonrpc:"2.0",method:e,params:t});!this.notifySocket||this.notifySocket.destroyed?(this.notifySocket=G.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{this.notifySocket.write(i+`
@@ -3,5 +3,4 @@ ${e.contextMessages.map(d=>`[${d.senderId??"unknown"}]: ${d.content}`).join(`
3
3
  `)}
4
4
 
5
5
  Latest user message:
6
- ${s}`);const a=this.config.command||"codewhale",c=this.buildExecArgs(s),l={...process.env,...this.config.env},S=v(a,typeof l.PATH=="string"?l.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${S} ${c.slice(0,5).join(" ")}...`);try{if(!(await _(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):r}const u=E(S,c,{cwd:this.cwd,env:l}).process;return this.activeProcess=u,u.stderr?.on("data",r=>{const d=r.toString().trim();d&&n.info("codewhale-adapter",`[codewhale stderr] ${d}`)}),new Promise((r,d)=>{let p=!1,m="";const g=()=>{this.activeProcess=null};u.on("error",h=>{p||(p=!0,g(),d(h))}),u.on("exit",h=>{if(m.trim()&&this.handleOutputLine(m.trim(),i),m="",p){g();return}if(p=!0,g(),h!==0&&i&&this.activeEventId===i){d(new Error(`codewhale exec exited with code ${h}`));return}t.emitDone({status:"completed"}),r()}),y({input:u.stdout}).on("line",h=>{h.trim()&&this.handleOutputLine(h.trim(),i)}),u.stdin?.end()})}handleOutputLine(e,t){let i;try{i=JSON.parse(e)}catch{n.error("codewhale-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(i.type){case"content":{const s=i.content;s&&t&&this.activeEventId===t&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(t,this.activeSessionId,s,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(t));break}case"session_capture":{const s=i.content;s&&(this.deepSeekSessionId=s,this.persistSessionId(s),n.info("codewhale-adapter",`Session captured: ${s}`));break}case"metadata":{const s=i.meta;if(s){n.info("codewhale-adapter",`Metadata: model=${s.model}, tokens_in=${s.input_tokens}, tokens_out=${s.output_tokens}`);const a=Number(s.input_tokens??0),c=Number(s.output_tokens??0);if(a>0||c>0){const l=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(l?.turns??0)+1,total:{input:(l?.total.input??0)+a,output:(l?.total.output??0)+c}}}}break}case"tool_use":{const s=i.name,a=typeof i.input=="string"?i.input:JSON.stringify(i.input??{});s&&t&&this.activeEventId===t&&this.activeSessionId&&(n.info("codewhale-adapter",`Tool use: ${s}`),this.callbacks.sendToolUse(t,this.activeSessionId,s,a),this.resetIdleTimer(t));break}case"tool_result":{const s=i.name,a=i.output;t&&this.activeEventId===t&&this.activeSessionId&&(this.callbacks.sendToolResult(t,this.activeSessionId,s??"unknown",a??""),this.resetIdleTimer(t));break}case"done":{this.handleMessageCompleted(t);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const t=this.activeSessionId??"",i=this.activeClientMsgId??void 0;t&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,t,`
7
- `,this.chunkSeq,!0,i)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;if(this.activeProcess=null,e?.pid)try{e.kill("SIGTERM")}catch{}}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodeWhaleThreadId(this.aibotSessionId,e)}startComposing(){}stopComposing(){}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(n.error("codewhale-adapter",`Agent idle for ${f/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${f/1e3}s`),this.clearActive(),this.emit("stuck"))},f)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class k extends w{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){n.warn("codewhale-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{W as CodeWhaleAdapter};
6
+ ${s}`);const a=this.config.command||"codewhale",c=this.buildExecArgs(s),l={...process.env,...this.config.env},S=v(a,typeof l.PATH=="string"?l.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${S} ${c.slice(0,5).join(" ")}...`);try{if(!(await _(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):r}const u=E(S,c,{cwd:this.cwd,env:l}).process;return this.activeProcess=u,u.stderr?.on("data",r=>{const d=r.toString().trim();d&&n.info("codewhale-adapter",`[codewhale stderr] ${d}`)}),new Promise((r,d)=>{let p=!1,m="";const g=()=>{this.activeProcess=null};u.on("error",h=>{p||(p=!0,g(),d(h))}),u.on("exit",h=>{if(m.trim()&&this.handleOutputLine(m.trim(),i),m="",p){g();return}if(p=!0,g(),h!==0&&i&&this.activeEventId===i){d(new Error(`codewhale exec exited with code ${h}`));return}t.emitDone({status:"completed"}),r()}),y({input:u.stdout}).on("line",h=>{h.trim()&&this.handleOutputLine(h.trim(),i)}),u.stdin?.end()})}handleOutputLine(e,t){let i;try{i=JSON.parse(e)}catch{n.error("codewhale-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(i.type){case"content":{const s=i.content;s&&t&&this.activeEventId===t&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(t,this.activeSessionId,s,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(t));break}case"session_capture":{const s=i.content;s&&(this.deepSeekSessionId=s,this.persistSessionId(s),n.info("codewhale-adapter",`Session captured: ${s}`));break}case"metadata":{const s=i.meta;if(s){n.info("codewhale-adapter",`Metadata: model=${s.model}, tokens_in=${s.input_tokens}, tokens_out=${s.output_tokens}`);const a=Number(s.input_tokens??0),c=Number(s.output_tokens??0);if(a>0||c>0){const l=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(l?.turns??0)+1,total:{input:(l?.total.input??0)+a,output:(l?.total.output??0)+c}}}}break}case"tool_use":{const s=i.name,a=typeof i.input=="string"?i.input:JSON.stringify(i.input??{});s&&t&&this.activeEventId===t&&this.activeSessionId&&(n.info("codewhale-adapter",`Tool use: ${s}`),this.callbacks.sendToolUse(t,this.activeSessionId,s,a),this.resetIdleTimer(t));break}case"tool_result":{const s=i.name,a=i.output;t&&this.activeEventId===t&&this.activeSessionId&&(this.callbacks.sendToolResult(t,this.activeSessionId,s??"unknown",a??""),this.resetIdleTimer(t));break}case"done":{this.handleMessageCompleted(t);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const t=this.activeSessionId??"",i=this.activeClientMsgId??void 0;t&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,t,"",this.chunkSeq,!0,i)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;if(this.activeProcess=null,e?.pid)try{e.kill("SIGTERM")}catch{}}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodeWhaleThreadId(this.aibotSessionId,e)}startComposing(){}stopComposing(){}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(n.error("codewhale-adapter",`Agent idle for ${f/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${f/1e3}s`),this.clearActive(),this.emit("stuck"))},f)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class k extends w{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){n.warn("codewhale-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{W as CodeWhaleAdapter};
@@ -3,7 +3,6 @@ import{createInterface as C}from"node:readline";import{EventEmitter as I}from"no
3
3
  `)}
4
4
 
5
5
  [Current user message]
6
- ${e.text}`}handleStdoutLineForActive(e,s){if(e.done)return;const o=s.trim();if(!o)return;const{event:t}=e;let i;try{i=JSON.parse(o)}catch{this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,{type:"raw_text",text:o});return}this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,i);const n=this.extractAssistantText(i);if(n&&(e.seq+=1,this.callbacks.sendStreamChunk(t.event_id,t.session_id,n,e.seq,!1)),i?.type==="result"){const r=String(i?.session_id??"").trim();if(r){const h=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...h,cursorSessionId:r})}const d=i?.usage??{};this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(this.lastUsageBySession.get(e.request.adapterSessionId)?.turns??0)+1,total:{input:Number(d.inputTokens??0),output:Number(d.outputTokens??0),cacheRead:Number(d.cacheReadTokens??0),cacheWrite:Number(d.cacheWriteTokens??0)}})}}extractAssistantText(e){if(e?.type!=="assistant")return"";const s=e?.message?.content;return Array.isArray(s)?s.map(t=>t?.type==="text"?String(t?.text??""):"").filter(Boolean).join(""):""}finishActive(e,s,o){const t=this.activeBySession.get(e);t&&(t.done=!0,t.timer&&clearTimeout(t.timer),t.seq>0&&(t.seq+=1,this.callbacks.sendStreamChunk(t.event.event_id,t.event.session_id,`
7
- `,t.seq,!0)),c.info("cursor-adapter",`job finish: event=${t.event.event_id} session=${t.event.session_id} status=${s}${o?` msg=${o}`:""}`),this.callbacks.sendEventResult(t.event.event_id,s,o),s==="responded"?t.handle.emitDone({status:"completed"}):s==="canceled"?t.handle.emitDone({status:"canceled",error:o}):t.handle.emitDone({status:"failed",error:o}),this.emit("eventDone",t.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const s=e.stderr.toLowerCase();return s.includes("continue")&&(s.includes("not found")||s.includes("session not found")||s.includes("no previous session"))}ensureCursorMcpConfig(e,s){try{const o=v(e,".cursor"),t=v(o,"mcp.json");A(o,{recursive:!0});let i={};try{i=JSON.parse(R(t,"utf8"))}catch{i={}}const n={command:process.execPath,args:[v(process.cwd(),"dist","mcp","stdio","server.js"),"--handle-url",s]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=n,x(t,`${JSON.stringify(i,null,2)}
6
+ ${e.text}`}handleStdoutLineForActive(e,s){if(e.done)return;const o=s.trim();if(!o)return;const{event:t}=e;let i;try{i=JSON.parse(o)}catch{this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,{type:"raw_text",text:o});return}this.callbacks.sendRawEventEnvelope?.(t.event_id,t.session_id,i);const n=this.extractAssistantText(i);if(n&&(e.seq+=1,this.callbacks.sendStreamChunk(t.event_id,t.session_id,n,e.seq,!1)),i?.type==="result"){const r=String(i?.session_id??"").trim();if(r){const h=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...h,cursorSessionId:r})}const d=i?.usage??{};this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(this.lastUsageBySession.get(e.request.adapterSessionId)?.turns??0)+1,total:{input:Number(d.inputTokens??0),output:Number(d.outputTokens??0),cacheRead:Number(d.cacheReadTokens??0),cacheWrite:Number(d.cacheWriteTokens??0)}})}}extractAssistantText(e){if(e?.type!=="assistant")return"";const s=e?.message?.content;return Array.isArray(s)?s.map(t=>t?.type==="text"?String(t?.text??""):"").filter(Boolean).join(""):""}finishActive(e,s,o){const t=this.activeBySession.get(e);t&&(t.done=!0,t.timer&&clearTimeout(t.timer),t.seq>0&&(t.seq+=1,this.callbacks.sendStreamChunk(t.event.event_id,t.event.session_id,"",t.seq,!0)),c.info("cursor-adapter",`job finish: event=${t.event.event_id} session=${t.event.session_id} status=${s}${o?` msg=${o}`:""}`),this.callbacks.sendEventResult(t.event.event_id,s,o),s==="responded"?t.handle.emitDone({status:"completed"}):s==="canceled"?t.handle.emitDone({status:"canceled",error:o}):t.handle.emitDone({status:"failed",error:o}),this.emit("eventDone",t.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const s=e.stderr.toLowerCase();return s.includes("continue")&&(s.includes("not found")||s.includes("session not found")||s.includes("no previous session"))}ensureCursorMcpConfig(e,s){try{const o=v(e,".cursor"),t=v(o,"mcp.json");A(o,{recursive:!0});let i={};try{i=JSON.parse(R(t,"utf8"))}catch{i={}}const n={command:process.execPath,args:[v(process.cwd(),"dist","mcp","stdio","server.js"),"--handle-url",s]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=n,x(t,`${JSON.stringify(i,null,2)}
8
7
  `,"utf8"),this.recordCursorMcpRegistry(e,t,n),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${t}`)}catch(o){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(o)}`)}}recordCursorMcpRegistry(e,s,o){try{const t=v(T.data,"cursor"),i=v(t,"mcp-registry.json");A(t,{recursive:!0});let n={};try{n=JSON.parse(R(i,"utf8"))}catch{n={}}n[e]={mcp_path:s,server:o,updated_at:new Date().toISOString()},x(i,`${JSON.stringify(n,null,2)}
9
8
  `,"utf8")}catch(t){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(t)}`)}}}export{y as CursorAdapter};
@@ -1,8 +1,6 @@
1
1
  import{EventEmitter as g}from"node:events";import{stat as w}from"node:fs/promises";import{existsSync as k,mkdirSync as y,readFileSync as b,writeFileSync as E}from"node:fs";import{join as $,resolve as A,dirname as P}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 I}from"../../core/runtime/spawn.js";import{InternalApiServer as F}from"../../core/mcp/internal-api-server.js";import{OpenCodeTransport as B}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as T}from"../../core/protocol/index.js";class j 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 N=200,D=2e3,H=12e4,L=6e5,S=3e4,U="127.0.0.1",J=0,q=1e3;class se extends g{type="opencode";config;callbacks;options;process=null;transport=new B;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??U,s=this.options.port??J,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{I(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{I(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 j(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=A(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 startInternalApiAndInjectMcp(){try{this.internalApi=new F,this.internalApi.setInvokeHandler(async(o,a)=>this.callbacks.agentInvoke(o,a)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=$(_(),".config","opencode","opencode.json");y(P(s),{recursive:!0});let t={};try{k(s)&&(t=JSON.parse(b(s,"utf8")))}catch{}const i=t.mcp&&typeof t.mcp=="object"?t.mcp:{};i[e.name]={type:"local",command:e.command,args:e.args},t.mcp=i,E(s,`${JSON.stringify(t,null,2)}
2
2
  `,"utf8"),n.info("opencode-adapter",`MCP config injected into ${s}`)}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 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,...d]=this.options.model.split("/");r&&d.length>0&&(p.agents={coder:{model:d.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="",c=!1;const u=setTimeout(()=>{c||(c=!0,o(new Error(`opencode serve did not start after ${S/1e3}s`)))},S);this.process.stdout?.on("data",r=>{if(v+=r.toString(),!c)for(const d of v.split(`
3
- `)){const R=d.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(u),c=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&n.info("opencode-adapter",`[stderr] ${d}`)}),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()),c?this.stopped||this.emit("exit",1):(c=!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()),c?this.stopped||this.emit("exit",r??1):(c=!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(l=>{n.warn("opencode-adapter",`Permission response failed: ${l}`)}),{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>q){const l=[...this.completedEvents].slice(-500);this.completedEvents=new Set(l)}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,`
4
- `,i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,`
5
- `,i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const l of T(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,l,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>=D){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},N))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of T(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)?L:H;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(`
3
+ `)){const R=d.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(u),c=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&n.info("opencode-adapter",`[stderr] ${d}`)}),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()),c?this.stopped||this.emit("exit",1):(c=!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()),c?this.stopped||this.emit("exit",r??1):(c=!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(l=>{n.warn("opencode-adapter",`Permission response failed: ${l}`)}),{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>q){const l=[...this.completedEvents].slice(-500);this.completedEvents=new Set(l)}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 l of T(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,l,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>=D){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},N))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of T(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)?L:H;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(`
6
4
  `)+`
7
5
 
8
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(`
@@ -1,7 +1,5 @@
1
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}from"../../core/runtime/spawn.js";import{InternalApiServer as w}from"../../core/mcp/internal-api-server.js";import{OpenHumanTransport as S,readBearerToken as b}from"./openhuman-transport.js";import{log as i}from"../../core/log/index.js";import{splitTextForAibotProtocol as y}from"../../core/protocol/index.js";class $ 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 A=200,C=2e3,L=12e4,F=500,v=3e4,P=7788,M="127.0.0.1";class K extends m{type="openhuman";config;callbacks;options;process=null;transport=new S;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??M,e=this.options.port??P;await this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),n=await b(s),a=`http://${t}:${e}`;if(await this.transport.connect(a,n),this.transport.on("event",r=>this.handleSocketEvent(r)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),i.info("openhuman-adapter","Session token stored")}catch(r){i.warn("openhuman-adapter",`Failed to store session token: ${r}`)}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 $(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,a=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,a).then(r=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=r.request_id,r.accepted||(i.warn("openhuman-adapter",`web_chat not accepted: ${r.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(r=>{i.error("openhuman-adapter",`web_chat failed: ${r}`),this.finishRun("failed",String(r))})}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 startInternalApiAndInjectMcp(){try{this.internalApi=new w,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"),a=E(),r=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 ('${a}', 'grix-connector-tools', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${r.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),a=this.config.args??[],c=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],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(a=>setTimeout(a,F))}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 r=this.completedEvents.values();for(let h=0;h<500;h++)r.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,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,`
3
- `,n,!0,a),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,`
4
- `,n,!0,a),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>=C){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},A))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of y(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)},L))}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(`
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),a=this.config.args??[],c=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],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(a=>setTimeout(a,F))}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 r=this.completedEvents.values();for(let h=0;h<500;h++)r.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,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,a),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,a),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>=C){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},A))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of y(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)},L))}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(`
5
3
  `)+`
6
4
 
7
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(a=>`[context] ${a.sender_id??"unknown"}: ${a.content}`).join(`