grix-connector 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1,6 +1,6 @@
1
- import{execFileSync as I}from"node:child_process";import{stat as y}from"node:fs/promises";import{resolveCommandPath as w,spawnCommand as E,killProcessGroup as k}from"../../core/runtime/spawn.js";import{createInterface as A}from"node:readline";import{EventEmitter as b}from"node:events";import{resolve as C}from"node:path";import{fileURLToPath as M}from"node:url";import{InternalApiServer as T}from"../../core/mcp/internal-api-server.js";import{buildSimpleProbeReport as x}from"../shared/probe-util.js";import{formatInboundMessageReferenceText as P}from"../../core/protocol/message-reference.js";import{log as n}from"../../core/log/index.js";import{SessionBindingStore as $}from"../../core/persistence/session-binding-store.js";const S=120*1e3;function R(m){try{return process.kill(m,0),!0}catch(e){return e.code==="EPERM"}}class K extends b{type="codewhale";config;callbacks;alive=!1;stopped=!1;internalApi=null;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;bindingStore=null;aibotSessionId="";cwd;lastUsage=null;currentModel=null;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};if(this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof $?i.bindingStore:null,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const o=this.bindingStore.getCodeWhaleThreadId(this.aibotSessionId);o&&(this.deepSeekSessionId=o)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,await this.startInternalApiAndRegisterMcp(),this.notifyBindingReady(),n.info("codewhale-adapter","Ready (exec mode)")}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),t}async resumeSession(e,t){}async destroySession(e){this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const t=new _(e.adapterSessionId);return this.runMessage(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){if(this.stopped||!this.alive)return!1;const t=this.activeProcess;return!(t?.pid&&!R(t.pid))}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const e=C(M(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async probe(e){const t=this.getStatus();return x(this.config.command||"codewhale",{alive:t.alive,busy:t.busy,started:this.alive},e)}getUsageSnapshot(){return this.lastUsage}async startInternalApiAndRegisterMcp(){try{this.internalApi=new T,this.internalApi.setInvokeHandler(async(a,c)=>this.callbacks.agentInvoke(a,c)),await this.internalApi.start(0),n.info("codewhale-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,o=w(this.config.command||"codewhale",i);try{I(o,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}const s=["mcp","add",e.name,"--command",e.command];for(const a of e.args)s.push("--arg",a);I(o,s,{env:t,timeout:1e4,stdio:"ignore"}),n.info("codewhale-adapter",`Registered MCP server: ${e.name}`)}catch(e){n.warn("codewhale-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,t,i){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(t){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel}),{handled:!0,kind:"get_context"};case"set_model":{const o=String(i.model_id??"").trim();return o?(this.currentModel=o,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:o}),n.info("codewhale-adapter",`Model set to: ${o}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}deliverInboundEvent(e){const t=P(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.activeEventId){n.info("codewhale-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,t)}deliverStopEvent(e,t){this.activeEventId===e&&(this.killActiveProcess(),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const i={adapterSessionId:this.deepSeekSessionId??"",text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(s=>({senderId:s.sender_id??"unknown",content:s.content})):void 0},o=new _(this.deepSeekSessionId??"");this.runMessage(i,o,e.event_id,e.session_id).catch(s=>{n.error("codewhale-adapter",`Message failed: ${s}`),this.callbacks.sendEventResult(e.event_id,"failed",s instanceof Error?s.message:String(s)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const t=["exec","--output-format","stream-json"];return this.currentModel&&t.push("--model",this.currentModel),this.deepSeekSessionId&&t.push("--resume",this.deepSeekSessionId),t.push("--",e),t}async runMessage(e,t,i,o){let s=e.text;e.contextMessages&&e.contextMessages.length>0&&(s=`Conversation context:
2
- ${e.contextMessages.map(d=>`[${d.senderId??"unknown"}]: ${d.content}`).join(`
1
+ import{execFileSync as I}from"node:child_process";import{stat as y}from"node:fs/promises";import{resolveCommandPath as k,spawnCommand as E,killProcessGroup as w}from"../../core/runtime/spawn.js";import{createInterface as A}from"node:readline";import{EventEmitter as b}from"node:events";import{join as T,resolve as C}from"node:path";import{homedir as M}from"node:os";import{fileURLToPath as $}from"node:url";import{InternalApiServer as x}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as P}from"../../default-skills/index.js";import{buildSimpleProbeReport as R}from"../shared/probe-util.js";import{formatInboundMessageReferenceText as D}from"../../core/protocol/message-reference.js";import{log as n}from"../../core/log/index.js";import{SessionBindingStore as L}from"../../core/persistence/session-binding-store.js";const v=120*1e3;function N(f){try{return process.kill(f,0),!0}catch(e){return e.code==="EPERM"}}class Y extends b{type="codewhale";config;callbacks;alive=!1;stopped=!1;internalApi=null;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;bindingStore=null;aibotSessionId="";cwd;lastUsage=null;currentModel=null;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};if(this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof L?i.bindingStore:null,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const o=this.bindingStore.getCodeWhaleThreadId(this.aibotSessionId);o&&(this.deepSeekSessionId=o)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,await this.startInternalApiAndRegisterMcp(),this.notifyBindingReady(),n.info("codewhale-adapter","Ready (exec mode)")}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),t}async resumeSession(e,t){}async destroySession(e){this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const t=new _(e.adapterSessionId);return this.runMessage(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){if(this.stopped||!this.alive)return!1;const t=this.activeProcess;return!(t?.pid&&!N(t.pid))}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const e=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 probe(e){const t=this.getStatus();return R(this.config.command||"codewhale",{alive:t.alive,busy:t.busy,started:this.alive},e)}getUsageSnapshot(){return this.lastUsage}async startInternalApiAndRegisterMcp(){try{this.internalApi=new x,this.internalApi.setInvokeHandler(async(r,p)=>this.callbacks.agentInvoke(r,p)),await this.internalApi.start(0),n.info("codewhale-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,o=k(this.config.command||"codewhale",i);try{I(o,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}const s=["mcp","add",e.name,"--command",e.command];for(const r of e.args)s.push("--arg",r);I(o,s,{env:t,timeout:1e4,stdio:"ignore"}),n.info("codewhale-adapter",`Registered MCP server: ${e.name}`);const a=T(M(),".codewhale","skills"),d=P(a);d.length>0&&n.info("codewhale-adapter",`Synced connector skills to ${a}: [${d.join(", ")}]`)}catch(e){n.warn("codewhale-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,t,i){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(t){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel}),{handled:!0,kind:"get_context"};case"set_model":{const o=String(i.model_id??"").trim();return o?(this.currentModel=o,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:o}),n.info("codewhale-adapter",`Model set to: ${o}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}deliverInboundEvent(e){const t=D(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.activeEventId){n.info("codewhale-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,t)}deliverStopEvent(e,t){this.activeEventId===e&&(this.killActiveProcess(),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const i={adapterSessionId:this.deepSeekSessionId??"",text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(s=>({senderId:s.sender_id??"unknown",content:s.content})):void 0},o=new _(this.deepSeekSessionId??"");this.runMessage(i,o,e.event_id,e.session_id).catch(s=>{n.error("codewhale-adapter",`Message failed: ${s}`),this.callbacks.sendEventResult(e.event_id,"failed",s instanceof Error?s.message:String(s)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const t=["exec","--output-format","stream-json"];return this.currentModel&&t.push("--model",this.currentModel),this.deepSeekSessionId&&t.push("--resume",this.deepSeekSessionId),t.push("--",e),t}async runMessage(e,t,i,o){let s=e.text;e.contextMessages&&e.contextMessages.length>0&&(s=`Conversation context:
2
+ ${e.contextMessages.map(l=>`[${l.senderId??"unknown"}]: ${l.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},v=w(a,typeof l.PATH=="string"?l.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${v} ${c.slice(0,5).join(" ")}...`);try{if(!(await y(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(v,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,f="";const g=()=>{this.activeProcess=null};u.on("error",h=>{p||(p=!0,g(),d(h))}),u.on("exit",h=>{if(f.trim()&&this.handleOutputLine(f.trim(),i),f="",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()}),A({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;this.activeProcess=null,e?.pid&&(k(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{k(e,"SIGKILL")}catch{}},5e3).unref())}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 ${S/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${S/1e3}s`),this.clearActive(),this.emit("stuck"))},S)}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 _ extends b{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{K as CodeWhaleAdapter};
6
+ ${s}`);const a=this.config.command||"codewhale",d=this.buildExecArgs(s),r={...process.env,...this.config.env},p=k(a,typeof r.PATH=="string"?r.PATH:void 0);n.info("codewhale-adapter",`Spawning: ${p} ${d.slice(0,5).join(" ")}...`);try{if(!(await y(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(c){throw String(c?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):c}const u=E(p,d,{cwd:this.cwd,env:r}).process;return this.activeProcess=u,u.stderr?.on("data",c=>{const l=c.toString().trim();l&&n.info("codewhale-adapter",`[codewhale stderr] ${l}`)}),new Promise((c,l)=>{let m=!1,g="";const S=()=>{this.activeProcess=null};u.on("error",h=>{m||(m=!0,S(),l(h))}),u.on("exit",h=>{if(g.trim()&&this.handleOutputLine(g.trim(),i),g="",m){S();return}if(m=!0,S(),h!==0&&i&&this.activeEventId===i){l(new Error(`codewhale exec exited with code ${h}`));return}t.emitDone({status:"completed"}),c()}),A({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),d=Number(s.output_tokens??0);if(a>0||d>0){const r=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(r?.turns??0)+1,total:{input:(r?.total.input??0)+a,output:(r?.total.output??0)+d}}}}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;this.activeProcess=null,e?.pid&&(w(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{w(e,"SIGKILL")}catch{}},5e3).unref())}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 ${v/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${v/1e3}s`),this.clearActive(),this.emit("stuck"))},v)}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 _ extends b{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{Y as CodeWhaleAdapter};
@@ -1,10 +1,10 @@
1
- import{execFileSync as A}from"node:child_process";import{createInterface as W}from"node:readline";import{EventEmitter as E}from"node:events";import{stat as M}from"node:fs/promises";import{join as C,resolve as B}from"node:path";import{fileURLToPath as J}from"node:url";import{resolveCommandPath as G,spawnCommand as X,killProcessGroup as R,hasChildProcesses as V}from"../../core/runtime/spawn.js";import{InternalApiServer as Y}from"../../core/mcp/internal-api-server.js";import{formatInboundMessageReferenceText as K}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as Z}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as Q}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as ee}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as te,isCodexCommand as se,readCodexProviderSettings as T}from"./codex-trust.js";import{checkCodexSessionActivity as ie}from"./session-activity.js";import{findRolloutFile as ne}from"./rollout-locator.js";import{checkCodexLock as P}from"./lock-monitor.js";import{resolveCliPath as oe,getCliVersion as re}from"../../core/util/cli-probe.js";import{scanSkills as ae}from"../claude/skill-scanner.js";const de=60*1e3,ce=600*1e3,le=5*1e3,$=2,he=20,ue=8e3,L=80,pe=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!0}],N=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function O(a,e){const t=String(a??"").trim()||"codex",i=G(t,e);if(i!==t)return i;const s=process.platform==="win32"?[]:["/opt/homebrew/bin/codex","/usr/local/bin/codex","/usr/bin/codex"],n=process.platform!=="win32"?[]:[C(process.env.LOCALAPPDATA??"","npm","codex.cmd"),C(process.env.APPDATA??"","npm","codex.cmd"),C(process.env.LOCALAPPDATA??"","Programs","codex","codex.exe")],o=[...s,...n];for(const d of o)try{return require("node:fs").accessSync(d,require("node:fs").constants.X_OK),d}catch{continue}return r.warn("codex-adapter",`resolveCodexCommandPath: failed to resolve "${t}". envPath=${e?"provided":"missing (using process.env.PATH)"}. fallbacks tried: [${o.join(", ")}]`),t}class q extends E{type="codex";config;callbacks;process=null;alive=!1;stopped=!1;internalApi=null;bridgeStatus="starting";pendingRequests=new Map;requestId=0;threadId=null;initialized=!1;cwd;approvalPolicy;sandboxMode;model;collaborationMode;reasoningEffort;idleTimer=null;lastProgressAt=0;lastIdleCheckAt=0;idleNoProgressCount=0;inFlightToolOps=0;pendingApprovals=new Map;needsHistoryInjection=!1;lastInjectedSessionId=null;bindingStore=null;aibotSessionId="";threadResumePending=!1;autoTrustProject="auto";codexHome;codexModelOptions=[];codexModelOptionsCachedAt=0;static MODEL_CACHE_TTL_MS=3600*1e3;rateLimitSnapshot=null;currentThreadTokenUsage=null;currentModelContextWindow=null;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};if(this.approvalPolicy=i.approvalPolicy??"never",this.sandboxMode=typeof i.sandboxMode=="string"&&i.sandboxMode.trim()&&i.sandboxMode.trim()!=="default"?i.sandboxMode.trim():void 0,this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof ee?i.bindingStore:null,this.model=i.model,this.collaborationMode=i.collaborationMode,this.reasoningEffort=i.reasoningEffort,this.bindingStore&&this.aibotSessionId&&(this.model=this.model??this.bindingStore.getCodexModelId(this.aibotSessionId),this.collaborationMode=this.collaborationMode??this.bindingStore.getCodexModeId(this.aibotSessionId),this.reasoningEffort=this.reasoningEffort??this.bindingStore.getCodexReasoningEffort(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof i.autoTrustProject=="boolean"?i.autoTrustProject:"auto",this.codexHome=typeof i.codexHome=="string"&&i.codexHome.trim()?i.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const s=this.bindingStore.getCodexThreadId(this.aibotSessionId);s&&(this.threadId=s,this.threadResumePending=!0)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){await this.ensureProjectTrusted(),await this.startInternalApiAndRegisterMcp(),await this.spawnCodex(),await this.initializeHandshake(),await this.notifyBindingReadyWithContext(),r.info("codex-adapter",`Ready (pid=${this.process?.pid})`)}async ensureProjectTrusted(){if(this.shouldAutoTrustProject())try{const e=await te(this.cwd,{codexHome:this.codexHome});e.changed&&r.info("codex-adapter",`Trusted Codex project ${this.cwd} in ${e.configPath}`)}catch(e){const t=e instanceof Error?e.message:String(e);throw r.error("codex-adapter",`Failed to trust Codex project ${this.cwd}: ${t}`),e}}async startInternalApiAndRegisterMcp(){try{this.internalApi=new Y,this.internalApi.setInvokeHandler(async(n,o)=>this.callbacks.agentInvoke(n,o)),await this.internalApi.start(0),r.info("codex-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,s=O(this.config.command||"codex",i);try{A(s,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}A(s,["mcp","add",e.name,"--",e.command,...e.args],{env:t,timeout:1e4,stdio:"ignore"}),r.info("codex-adapter",`Registered MCP server: ${e.name}`)}catch(e){r.warn("codex-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}shouldAutoTrustProject(){return this.autoTrustProject===!1?!1:this.autoTrustProject===!0?!0:se(this.config.command)}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}async notifyBindingReadyWithContext(){if(!(!this.aibotSessionId||!this.cwd))try{this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,await this.buildToolbarContextResult("binding_ready"))}catch(e){r.warn("codex-adapter",`Failed to attach toolbar context to binding update: ${e instanceof Error?e.message:String(e)}`),this.notifyBindingReady()}}getEffortMeta(){const e=this.currentModelId(),t=this.getModelOption(e),i=t?.supportedReasoningEfforts??[];return i.length===0?{}:{available_efforts:i,reasoning_effort:this.reasoningEffort??t?.defaultReasoningEffort??null}}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.rejectAllPending("adapter stopped"),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null);for(const t of this.compactionDeferred.splice(0))this.callbacks.sendEventResult(t.event_id,"failed","adapter stopped");this.internalApi&&(await this.internalApi.stop(),this.internalApi=null);const e=this.process;if(this.process=null,e?.pid&&typeof e.once=="function")R(e,"SIGTERM"),await Promise.race([new Promise(i=>{e.once("exit",()=>i(!0))}),new Promise(i=>{setTimeout(()=>i(!1),5e3)})])||R(e,"SIGKILL");else if(e)try{e.kill("SIGTERM")}catch{}}isAlive(){return this.alive}async createSession(e){if(this.initialized||await this.initializeHandshake(),this.threadId||await this.startNewThread(),!this.threadId)throw new Error("Failed to create session: thread ID is missing");return await this.notifyBindingReadyWithContext(),this.threadId}async resumeSession(e,t){await this.ensureThreadResumed()}async destroySession(e){this.threadId=null,this.threadResumePending=!1,this.persistThreadId(void 0)}sendPrompt(e){const t=new S(e.adapterSessionId);return this.runTurn(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){if(this.threadId)try{await this.sendRequest("turn/interrupt",{threadId:this.threadId,turnId:this.currentTurnId??""},5e3)}catch{}}setPermissionHandler(e){this.permissionHandler=e}permissionHandler=null;async ping(e){if(!this.alive||!this.process)return!1;if(!this.process.pid){const t=!!(this.process.stdin&&!this.process.stdin.destroyed),i=!!(this.process.stdout&&!this.process.stdout.destroyed);return t&&i}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const t=++this.requestId,i={jsonrpc:"2.0",id:t,method:"ping",params:{}};return await new Promise(s=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(t)),s(!1)},e);this.pendingRequests.set(String(t),{resolve:()=>s(!0),reject:()=>s(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(i)}
2
- `)})}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.currentTurnId!==null||this.compacting,sessions:this.threadId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.activeEventId=null}setModel(e){this.model=e,this.persistCodexContext(),r.info("codex-adapter",`Model set to: ${e}`)}setMode(e){const t=_(e);if(!t){r.info("codex-adapter",`Ignoring unsupported mode: ${e}`);return}this.collaborationMode=t,this.persistCodexContext(),r.info("codex-adapter",`Mode set to: ${t}`)}setReasoningEffort(e){this.reasoningEffort=e,this.persistCodexContext(),r.info("codex-adapter",`Reasoning effort set to: ${e}`)}setSandboxMode(e){this.sandboxMode=e,this.persistCodexContext(),r.info("codex-adapter",`Sandbox mode set to: ${e} (applied on next restart)`)}async threadCompact(){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/compact/start",{threadId:this.threadId},12e4)}async threadRollback(e){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/rollback",{threadId:this.threadId,numTurns:e},1e4)}getThreadId(){return this.threadId}getMcpConfig(){if(!this.internalApi)return null;const e=B(J(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?V(e,[e]):!1}async probe(e){const t=this.config.command||"codex",i=await oe(t),s=i!==null;let n=null,o;if(s){const m=await re(t);n=m.version,m.error&&(o=m.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const h=T().baseUrl??null,u=this.currentModelId()||null,f=this.getStatus(),p=e?.conversation?{attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"codex does not support minimal prompt probe"}}:{attempted:!1,ok:!1,latency_ms:null},x=await this.probeSessionRecord(),v=P({codexHome:this.codexHome});return{cli:{command:t,installed:s,path:i,version:n,...o?{error:o}:{}},conversation:p,config:{model:u,base_url:h,source:{model:u?"runtime":"unknown",base_url:h?"file":"unknown"}},process:{started:!!this.process,alive:f.alive,busy:f.busy},session:x,lock:{present:v.present,locked:v.locked,stale:v.stale,path:v.path}}}async probeSessionRecord(){const e=this.threadId?ne(this.threadId,this.codexHome):null;if(!e)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const t=await M(e);return{recordPath:e,lastActivityMs:t.mtimeMs,freshMs:Date.now()-t.mtimeMs}}catch{return{recordPath:e,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"compact",description:"Compact thread to reduce context size"},{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[default|plan]"},{name:"rollback",description:"Roll back thread turns",args:"[num_turns]"},{name:"interrupt",description:"Interrupt current turn"},{name:"rate_limits",description:"Show current rate limits"},{name:"status",description:"Show thread and session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,t,i){try{switch(e){case"compact":{if(!this.threadId)return{status:"failed",message:"No active thread"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive()),await this.ensureThreadResumed(),this.currentTurnId)return{status:"failed",message:"agent busy"};if(this.needsHistoryInjection)return{status:"failed",message:"\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"};this.compacting=!0;const s=new Promise(n=>{this.compactionDoneResolver=n});try{return await this.threadCompact(),this.armCompactionFallback(),await s,{status:"ok",message:"Thread compacted"}}catch(n){this.finishCompaction("compact-error");const o=n instanceof Error?n.message:String(n);if(/thread.*(not found|not exist|unknown|invalid)/i.test(o)||/not found.*thread/i.test(o))return this.threadId=null,this.persistThreadId(void 0),{status:"failed",message:"\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"};throw n}}case"model":{const s=t.trim();return s?this.getModelOptions().some(o=>o.id===s)?(this.setModel(s),{status:"ok",message:`Model set to ${s}`}):{status:"failed",message:`Unknown model: ${s}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const s=t.trim();if(s){const n=_(s);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${s}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:N}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const s=Math.max(1,parseInt(t.trim(),10)||1);return this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive()),await this.threadRollback(s),{status:"ok",message:`Rolled back ${s} turn(s)`}}case"interrupt":return this.threadId?(await this.cancel(this.threadId),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Turn interrupted"}):{status:"failed",message:"No active thread"};case"rate_limits":return{status:"ok",message:this.rateLimitSnapshot?`Primary: ${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}%, Secondary: ${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`:"Rate limits not available",data:{rateLimits:this.rateLimitSnapshot}};case"status":return{status:"ok",message:`Thread: ${this.threadId??"none"}, Model: ${this.currentModelId()}, Mode: ${this.collaborationMode??"default"}`,data:{threadId:this.threadId,model:this.currentModelId(),mode:this.collaborationMode??"default",cwd:this.cwd,alive:this.alive}};case"skills":{const s=ae({mode:"codex",projectDir:this.cwd}),n=s.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
3
- `):"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)}}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${t} action_id=${e.action_id} session_id=${i.session_id??""}`),t){case"set_model":{const s=g(l(i.model_id),l(i.modelId),l(i.value));return s&&this.setModel(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const s=i.mode_id;return s&&this.setMode(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const s=g(l(i.reasoning_effort),l(i.reasoning_eff),l(i.effort));return s&&this.setReasoningEffort(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_sandbox_mode":{const s=g(l(i.sandbox_mode),l(i.sandboxMode),l(i.value));return s&&this.setSandboxMode(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("sandbox_mode_set",!1)),{handled:!0,kind:"set_sandbox_mode"}}case"thread_compact":{r.info("codex-adapter",`thread_compact start action_id=${e.action_id} threadId=${this.threadId}`),this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive());try{if(await this.ensureThreadResumed(),this.needsHistoryInjection)return r.info("codex-adapter",`thread_compact aborted: thread expired, new thread created action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"thread_expired","\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"),{handled:!0,kind:"thread_compact"};this.compacting=!0;const s=new Promise(n=>{this.compactionDoneResolver=n});await this.threadCompact(),this.armCompactionFallback(),await s,r.info("codex-adapter",`thread_compact done action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(s){this.finishCompaction("compact-error");const n=s instanceof Error?s.message:String(s);/thread.*(not found|not exist|unknown|invalid)/i.test(n)||/not found.*thread/i.test(n)?(r.warn("codex-adapter",`thread_compact thread expired, resetting threadId action_id=${e.action_id} err=${n}`),this.threadId=null,this.persistThreadId(void 0),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"thread_expired","\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5")):(r.info("codex-adapter",`thread_compact failed action_id=${e.action_id} err=${n}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n))}return{handled:!0,kind:"thread_compact"}}case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{threadId:this.threadId,model:this.model,mode:this.collaborationMode,approvalPolicy:this.approvalPolicy,cwd:this.cwd,...await this.buildToolbarContextResult("context")}),{handled:!0,kind:"get_context"};case"exec_approve":case"file_approve":{const s=this.resolvePendingApproval(e);if(!s)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:t};const n=i.decision||"allow-once",o=fe[n]??"accept";return this.sendApprovalDecision(s.requestId,o),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,approval_command_id:s.approvalCommandId,decision:n}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t}}case"exec_reject":case"file_reject":{const s=this.resolvePendingApproval(e);return s?(this.sendApprovalDecision(s.requestId,"deny"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,approval_command_id:s.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:t})}case"permission_approve":{const s=this.resolvePendingApproval(e);return!s||s.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:t}):(this.sendApprovalDecision(s.requestId,"accept"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,decision:"approve"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t})}case"permission_reject":{const s=this.resolvePendingApproval(e);return!s||s.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:t}):(this.sendApprovalDecision(s.requestId,"deny"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,decision:"deny"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(s){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,s instanceof Error?s.message:String(s))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const s=Number(i.numTurns??i.num_turns??1);if(!this.threadId)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"no_thread","No active thread"),{handled:!0,kind:"thread_rollback"};this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive());try{await this.threadRollback(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:s})}catch(n){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n instanceof Error?n.message:String(n))}return{handled:!0,kind:"thread_rollback"}}case"get_rate_limits":{const s=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return s?r.info("codex-adapter",`[rate-limits] responding: primary=${s.primary.usedPercent.toFixed(1)}% window=${s.primary.windowMinutes}min resetsAt=${s.primary.resetsAt} secondary=${s.secondary.usedPercent.toFixed(1)}% window=${s.secondary.windowMinutes}min resetsAt=${s.secondary.resetsAt}`):r.info("codex-adapter","[rate-limits] responding: no rateLimitSnapshot available"),r.debug("codex-adapter",`[cp-diagnose] get_rate_limits: threadId=${this.threadId??"-"} contextWindow=${JSON.stringify(n)} tokenUsage=${JSON.stringify(d)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok",{adapterType:"codex",available:s!==null,cached:!1,sampledAt:o,rateLimits:s,contextWindow:n,tokenUsage:d}),{handled:!0,kind:"get_rate_limits"}}default:return{handled:!1,kind:""}}}async buildToolbarContextResult(e,t=!0){t&&await this.refreshCodexModelOptions();const i=_(this.collaborationMode)??"default",s=this.currentModelId(),n=this.getModelOption(s),o=this.getModelOptions(),d=N,h=this.reasoningEffort??n?.defaultReasoningEffort??null,u=n?.supportedReasoningEfforts??[],f=T(this.codexHome),p=!!(f.baseUrl&&D(f.baseUrl)),x=p?[]:u,v=p?null:h,m={outcome:e,session_context:{modelId:s,modeId:i,reasoningEffort:v,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:s,mode_id:i,currentModelId:s,currentModeId:i,available_models:o,available_modes:d,available_efforts:x,availableModels:o,availableModes:d,reasoning_effort:v,sandbox_mode:this.sandboxMode??null,models:o.map(c=>({modelId:c.id,name:c.displayName})),modes:d.map(c=>({id:c.id,name:c.displayName}))};return this.rateLimitSnapshot&&(m.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,m.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,m.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,m.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),m}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const t=this.getModelOptions();return t.find(i=>i.isDefault)?.id??t[0]?.id??""}getModelOption(e){const t=e.trim().toLowerCase();return this.getModelOptions().find(i=>i.id.trim().toLowerCase()===t)}getModelOptions(){const e=this.config.options??{},t=e.available_models??e.availableModels??e.models,i=Ie(t),s=this.codexModelOptions,n=[],o=String(this.model??"").trim(),d=o&&!s.some(p=>p.id.trim().toLowerCase()===o.toLowerCase()),h=s.length>0?s:d?[]:pe;if(o){const p=[...s,...i].find(x=>x.id.trim().toLowerCase()===o.toLowerCase());n.push({id:o,displayName:p?.displayName??y(o),defaultReasoningEffort:p?.defaultReasoningEffort??null,supportedReasoningEfforts:p?.supportedReasoningEfforts??[],isDefault:p?.isDefault??!1})}n.push(...i),n.push(...h);const u=[],f=new Set;for(const p of n){const x=p.id.trim(),v=x.toLowerCase();!x||f.has(v)||(f.add(v),u.push({id:x,displayName:p.displayName.trim()||x,defaultReasoningEffort:p.defaultReasoningEffort,supportedReasoningEfforts:[...p.supportedReasoningEfforts],isDefault:p.isDefault}))}return u}async refreshCodexModelOptions(){if(!this.initialized||!this.alive||!this.process?.stdin||Date.now()-this.codexModelOptionsCachedAt<q.MODEL_CACHE_TTL_MS)return;const e=T(this.codexHome);if(e.baseUrl&&D(e.baseUrl)){try{const t=await be(e.baseUrl,e.apiKey);if(t.length>0){this.codexModelOptions=j(t),this.codexModelOptionsCachedAt=Date.now(),r.info("codex-adapter",`Loaded ${t.length} models from external provider ${e.baseUrl}`);return}}catch(t){r.warn("codex-adapter",`External provider /v1/models failed (${t instanceof Error?t.message:String(t)}), falling back`)}this.codexModelOptions=[],this.codexModelOptionsCachedAt=Date.now();return}try{const t=[];let i;do{const s=await this.sendRequest("model/list",{cursor:i??null,includeHidden:!1,limit:100},15e3),n=_e(s);t.push(...n.models),i=n.nextCursor}while(i);t.length>0&&(this.codexModelOptions=j(t),this.codexModelOptionsCachedAt=Date.now())}catch(t){r.warn("codex-adapter",`Failed to load Codex model list: ${t instanceof Error?t.message:String(t)}`)}}currentTurnId=null;compacting=!1;pendingAutoCompact=!1;compactionDeferred=[];compactingTimer=null;compactionDoneResolver=null;activeEventId=null;activeSessionId=null;visibleAgentMessageIds=new Set;hiddenAgentMessageIds=new Set;agentMessagePhases=new Map;codexSequence=0;turnError=null;deliverInboundEvent(e){if(this.compacting){r.info("codex-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.compactionDeferred.push(e);return}const t=K(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.currentTurnId&&t.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(t).catch(i=>{const s=i instanceof Error?i.message:String(i);if(r.info("codex-adapter",`Steer failed, falling through: ${s}`),!this.currentTurnId){this.startNewTurn(e,t);return}if(Ce(s)){r.info("codex-adapter","Unsteerable turn detected, clearing active state and starting new turn"),this.clearActive(),this.startNewTurn(e,t);return}this.callbacks.sendEventResult(e.event_id,"failed",`failed to steer message into active turn: ${s}`)});return}if(this.currentTurnId){r.info("codex-adapter",`Event ${e.event_id} rejected: busy and empty content`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewTurn(e,t)}startNewTurn(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.codexSequence=0,this.startComposing();const i=this.threadId??"",s=new S(i),n={adapterSessionId:i,text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(o=>({senderId:o.sender_id??"unknown",content:o.content})):void 0};this.runTurn(n,s,e.event_id,e.session_id).catch(o=>{r.error("codex-adapter",`Turn failed: ${o}`),this.callbacks.sendEventResult(e.event_id,"failed",o instanceof Error?o.message:String(o)),this.clearActive()}),this.resetIdleTimer(e.event_id)}deliverStopEvent(e,t){this.activeEventId===e&&(this.cancel(this.threadId??"").catch(()=>{}),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}async spawnCodex(){const e={...process.env,...this.config.env},t=typeof e.PATH=="string"?e.PATH:void 0;t||r.warn("codex-adapter",`spawnCodex: env.PATH is missing! process.env.PATH=${process.env.PATH?"set":"missing"}, config.env=${JSON.stringify(this.config.env)}`);const i=O(this.config.command||"codex",t),s=me(this.config.args);this.sandboxMode&&s.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${i} ${s.join(" ")}`);try{if(!(await M(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):o}const n=P({codexHome:this.codexHome});if(n.locked&&!n.stale)throw new Error(`Codex session appears locked by another process: ${n.path}`);n.stale&&r.warn("codex-adapter",`Ignoring stale Codex lock: ${n.path}`);try{this.process=X(i,s,{env:e,cwd:this.cwd}).process}catch(o){throw r.error("codex-adapter",`Codex spawn threw: ${o}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",o}this.process.on("error",o=>{r.error("codex-adapter",`Codex process spawn error: ${o}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("spawn failed: "+(o instanceof Error?o.message:String(o))),this.stopComposing(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed","Codex process spawn failed"),this.activeEventId=null),this.emit("exit",1)}),this.process.on("exit",o=>{r.info("codex-adapter",`Codex process exited (code=${o})`),this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("process exited"),this.stopComposing(),this.emit("exit",o)}),this.process.stderr?.on("data",o=>{const d=o.toString().trim();d&&r.info("codex-adapter",`[codex stderr] ${d}`)}),this.bindStdout(),this.alive=!0}bindStdout(){if(!this.process?.stdout)return;this.process.stdout.setEncoding("utf8"),W({input:this.process.stdout}).on("line",t=>{if(!t.trim())return;let i;try{i=JSON.parse(t)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${t.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),i.id!=null){const s=String(i.id),n=this.pendingRequests.get(s);if(n){clearTimeout(n.timer),this.pendingRequests.delete(s),n.resolve(i);return}}this.handleNotification(i)})}handleNotification(e){const t=e.method,i=e.params??{};switch(this.captureContextWindowFromPayload(t,i),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&t&&t.startsWith("item/")&&!t.endsWith("/requestApproval")&&this.startComposing(),t){case"item/started":{const s=i.item,n=s?.id,o=s?.type,d=s?.phase;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps+=1),o==="agentMessage"&&n&&(typeof d=="string"&&d.trim()?this.agentMessagePhases.set(n,d.trim().toLowerCase()):this.agentMessagePhases.delete(n),Q(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const s=i.delta,n=i.itemId;s&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const s=i.item,n=s?.id,o=s?.type;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps=Math.max(0,this.inFlightToolOps-1)),this.activeEventId&&o!=="agentMessage"&&this.emitCodexEvent("item/completed",e),o==="agentMessage"&&n&&(this.visibleAgentMessageIds.delete(n),this.hiddenAgentMessageIds.delete(n),this.agentMessagePhases.delete(n));break}case"turn/started":{const n=i.turn?.id;n&&(this.currentTurnId=n,this.turnError=null,r.info("codex-adapter",`Turn started: ${n}`),this.startComposing());break}case"item/fileChange/requestApproval":{this.handleApprovalRequest(e,"file");break}case"item/commandExecution/requestApproval":{this.handleApprovalRequest(e,"exec");break}case"item/permissions/requestApproval":{this.activeEventId&&this.emitCodexEvent("item/permissions/requestApproval",e),this.handleApprovalRequest(e,"permission");break}case"turn/completed":{this.handleTurnCompleted(i);break}case"error":{const s=i.message??JSON.stringify(i);r.error("codex-adapter",`Codex error: ${s}`),s&&(this.turnError=s),this.activeEventId&&this.callbacks.sendRunError(this.activeEventId,this.activeSessionId??"",s);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(i),this.rateLimitSnapshot&&(r.info("codex-adapter",`Rate limits updated: primary=${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}% secondary=${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`),this.callbacks.onRateLimitsUpdated?.(this.rateLimitSnapshot));break}case"thread/tokenUsage/updated":{const s=i.threadId,n=i.tokenUsage,o=n?.last;(!s||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!s)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(i)}`),s&&n&&o&&(this.currentThreadTokenUsage={inputTokens:o.inputTokens??0,outputTokens:o.outputTokens??0,cacheReadInputTokens:o.cachedInputTokens??0,cacheCreationInputTokens:0},r.info("codex-adapter",`[cp-diagnose] token_usage raw thread=${s}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${s}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.publishContextWindowSnapshot());break}default:break}}captureContextWindowFromPayload(e,t){const i=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],s=this.findNumericField(t,i);if(s==null||s<=0){const n=this.probeContextWindowFields(t,i);n.foundAnyCandidateField&&r.info("codex-adapter",`[cp-diagnose] context_window missing_or_invalid: method=${e??"-"} matchedFields=${n.matchedFields.join(",")||"-"} raw=${n.rawValues.join(",")||"-"} extracted=${String(s)}`);return}this.currentModelContextWindow!==s&&(this.currentModelContextWindow=s,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${s}`),this.publishContextWindowSnapshot())}probeContextWindowFields(e,t){const i=[],s=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const h of o)n(h);return}const d=o;for(const h of t)Object.prototype.hasOwnProperty.call(d,h)&&(i.push(h),s.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:i.length>0,matchedFields:i,rawValues:s}}findNumericField(e,t){if(!e||typeof e!="object")return null;const i=e;for(const s of t){const n=i[s];if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){const o=Number(n);if(Number.isFinite(o))return o}}for(const s of Object.values(i)){if(Array.isArray(s)){for(const o of s){const d=this.findNumericField(o,t);if(d!=null)return d}continue}const n=this.findNumericField(s,t);if(n!=null)return n}return null}buildContextWindowSnapshot(){const e=this.currentModelContextWindow;if(!e||e<=0)return r.debug("codex-adapter",`[cp-diagnose] context_window snapshot skipped: invalid size=${String(e)}`),null;const t=this.currentThreadTokenUsage,i=t?t.inputTokens+t.outputTokens:null,s=i==null?null:Math.max(0,e-i),n=i==null?null:Math.min(100,i/e*100),o=i==null?null:Math.max(0,100-(n??0));return r.info("codex-adapter",`[cp-diagnose] context_window computed: threadId=${this.threadId??"-"} size=${e} usedTokens=${String(i)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:i,remainingTokens:s,usedPercentage:n,remainingPercentage:o,source:"codex:model_context_window"}}publishContextWindowSnapshot(){const e=this.buildContextWindowSnapshot();this.callbacks.onContextWindowUpdated?.(e),this.maybeScheduleAutoCompact(e)}maybeScheduleAutoCompact(e){const t=e?.usedPercentage;typeof t!="number"||t<L||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("codex-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${L}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.alive||this.currentTurnId||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.activeSessionId??"").then(e=>{r.info("codex-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`),e.status==="failed"&&/agent busy/i.test(e.message??"")&&(this.pendingAutoCompact=!0)}).catch(e=>{r.warn("codex-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:e}`)})))}parseRateLimitSnapshot(e){try{const i=e.rateLimitsByLimitId?.codex??e.rateLimits??e,s=i.primary,n=i.secondary,o=i.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const I=Number(c);if(Number.isFinite(I))return I}return 0},h=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,u=c=>{if(c==null)return null;if(typeof c=="string"){const I=c.trim();if(!I)return null;const w=Number(I);return Number.isFinite(w)&&w>1e9?new Date(w*1e3).toISOString():I}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const I=c>1e12?c/1e3:c;return new Date(I*1e3).toISOString()}return null},f=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:u(c.resets_at??c.resetsAt)}),p=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),x=()=>{const c=i.windows;if(!Array.isArray(c))return null;const I=c.map(b=>b&&typeof b=="object"?f(b):p()).filter(b=>b.windowMinutes>0);if(I.length===0)return null;const w=[...I].sort((b,z)=>b.windowMinutes-z.windowMinutes),H=w.find(b=>Math.abs(b.windowMinutes-300)<=30)??w[0],U=w.find(b=>Math.abs(b.windowMinutes-10080)<=120)??w[w.length-1];return{primary:H,secondary:U}},m=(s||n?{primary:s?f(s):p(),secondary:n?f(n):p()}:null)??x();return m?{primary:m.primary,secondary:m.secondary,credits:o?{hasCredits:h(o.has_credits??o.hasCredits),unlimited:h(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,t){const i=e.id;if(i==null)return;const s=e.method,n=`${i}`.trim(),o=`codex_${F(this.activeSessionId??"")}_${F(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(i,"accept");return}this.activeEventId&&this.emitCodexEvent(s,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:i,kind:t}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${t})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const t=e.params??{},i=g(t.approval_id,t.approvalId);if(i)return this.pendingApprovals.get(i)??null;const s=g(t.approval_command_id,t.approvalCommandId);if(s){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===s)return n}return null}sendApprovalDecision(e,t){if(e==null||!this.process?.stdin)return;const i={jsonrpc:"2.0",id:e,result:{decision:t}};this.process.stdin.write(`${JSON.stringify(i)}
1
+ import{execFileSync as A}from"node:child_process";import{createInterface as W}from"node:readline";import{EventEmitter as E}from"node:events";import{stat as M}from"node:fs/promises";import{join as C,resolve as B}from"node:path";import{fileURLToPath as J}from"node:url";import{homedir as G}from"node:os";import{syncDefaultSkillsToDir as X}from"../../default-skills/index.js";import{resolveCommandPath as V,spawnCommand as Y,killProcessGroup as R,hasChildProcesses as K}from"../../core/runtime/spawn.js";import{InternalApiServer as Z}from"../../core/mcp/internal-api-server.js";import{formatInboundMessageReferenceText as Q}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as ee}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as te}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as se}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as ie,isCodexCommand as ne,readCodexProviderSettings as k}from"./codex-trust.js";import{checkCodexSessionActivity as oe}from"./session-activity.js";import{findRolloutFile as re}from"./rollout-locator.js";import{checkCodexLock as P}from"./lock-monitor.js";import{resolveCliPath as ae,getCliVersion as de}from"../../core/util/cli-probe.js";import{scanSkills as ce}from"../claude/skill-scanner.js";const le=60*1e3,he=600*1e3,ue=5*1e3,$=2,pe=20,fe=8e3,L=80,me=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!0}],N=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function O(a,e){const t=String(a??"").trim()||"codex",i=V(t,e);if(i!==t)return i;const s=process.platform==="win32"?[]:["/opt/homebrew/bin/codex","/usr/local/bin/codex","/usr/bin/codex"],n=process.platform!=="win32"?[]:[C(process.env.LOCALAPPDATA??"","npm","codex.cmd"),C(process.env.APPDATA??"","npm","codex.cmd"),C(process.env.LOCALAPPDATA??"","Programs","codex","codex.exe")],o=[...s,...n];for(const d of o)try{return require("node:fs").accessSync(d,require("node:fs").constants.X_OK),d}catch{continue}return r.warn("codex-adapter",`resolveCodexCommandPath: failed to resolve "${t}". envPath=${e?"provided":"missing (using process.env.PATH)"}. fallbacks tried: [${o.join(", ")}]`),t}class q extends E{type="codex";config;callbacks;process=null;alive=!1;stopped=!1;internalApi=null;bridgeStatus="starting";pendingRequests=new Map;requestId=0;threadId=null;initialized=!1;cwd;approvalPolicy;sandboxMode;model;collaborationMode;reasoningEffort;idleTimer=null;lastProgressAt=0;lastIdleCheckAt=0;idleNoProgressCount=0;inFlightToolOps=0;pendingApprovals=new Map;needsHistoryInjection=!1;lastInjectedSessionId=null;bindingStore=null;aibotSessionId="";threadResumePending=!1;autoTrustProject="auto";codexHome;codexModelOptions=[];codexModelOptionsCachedAt=0;static MODEL_CACHE_TTL_MS=3600*1e3;rateLimitSnapshot=null;currentThreadTokenUsage=null;currentModelContextWindow=null;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};if(this.approvalPolicy=i.approvalPolicy??"never",this.sandboxMode=typeof i.sandboxMode=="string"&&i.sandboxMode.trim()&&i.sandboxMode.trim()!=="default"?i.sandboxMode.trim():void 0,this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof se?i.bindingStore:null,this.model=i.model,this.collaborationMode=i.collaborationMode,this.reasoningEffort=i.reasoningEffort,this.bindingStore&&this.aibotSessionId&&(this.model=this.model??this.bindingStore.getCodexModelId(this.aibotSessionId),this.collaborationMode=this.collaborationMode??this.bindingStore.getCodexModeId(this.aibotSessionId),this.reasoningEffort=this.reasoningEffort??this.bindingStore.getCodexReasoningEffort(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof i.autoTrustProject=="boolean"?i.autoTrustProject:"auto",this.codexHome=typeof i.codexHome=="string"&&i.codexHome.trim()?i.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const s=this.bindingStore.getCodexThreadId(this.aibotSessionId);s&&(this.threadId=s,this.threadResumePending=!0)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){await this.ensureProjectTrusted(),await this.startInternalApiAndRegisterMcp(),await this.spawnCodex(),await this.initializeHandshake(),await this.notifyBindingReadyWithContext(),r.info("codex-adapter",`Ready (pid=${this.process?.pid})`)}async ensureProjectTrusted(){if(this.shouldAutoTrustProject())try{const e=await ie(this.cwd,{codexHome:this.codexHome});e.changed&&r.info("codex-adapter",`Trusted Codex project ${this.cwd} in ${e.configPath}`)}catch(e){const t=e instanceof Error?e.message:String(e);throw r.error("codex-adapter",`Failed to trust Codex project ${this.cwd}: ${t}`),e}}async startInternalApiAndRegisterMcp(){try{this.internalApi=new Z,this.internalApi.setInvokeHandler(async(d,h)=>this.callbacks.agentInvoke(d,h)),await this.internalApi.start(0),r.info("codex-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t={...process.env,...this.config.env},i=typeof t.PATH=="string"?t.PATH:void 0,s=O(this.config.command||"codex",i);try{A(s,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}A(s,["mcp","add",e.name,"--",e.command,...e.args],{env:t,timeout:1e4,stdio:"ignore"}),r.info("codex-adapter",`Registered MCP server: ${e.name}`);const n=C(G(),".codex","skills"),o=X(n);o.length>0&&r.info("codex-adapter",`Synced connector skills to ${n}: [${o.join(", ")}]`)}catch(e){r.warn("codex-adapter",`Failed to register MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}shouldAutoTrustProject(){return this.autoTrustProject===!1?!1:this.autoTrustProject===!0?!0:ne(this.config.command)}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}async notifyBindingReadyWithContext(){if(!(!this.aibotSessionId||!this.cwd))try{this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,await this.buildToolbarContextResult("binding_ready"))}catch(e){r.warn("codex-adapter",`Failed to attach toolbar context to binding update: ${e instanceof Error?e.message:String(e)}`),this.notifyBindingReady()}}getEffortMeta(){const e=this.currentModelId(),t=this.getModelOption(e),i=t?.supportedReasoningEfforts??[];return i.length===0?{}:{available_efforts:i,reasoning_effort:this.reasoningEffort??t?.defaultReasoningEffort??null}}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.rejectAllPending("adapter stopped"),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null);for(const t of this.compactionDeferred.splice(0))this.callbacks.sendEventResult(t.event_id,"failed","adapter stopped");this.internalApi&&(await this.internalApi.stop(),this.internalApi=null);const e=this.process;if(this.process=null,e?.pid&&typeof e.once=="function")R(e,"SIGTERM"),await Promise.race([new Promise(i=>{e.once("exit",()=>i(!0))}),new Promise(i=>{setTimeout(()=>i(!1),5e3)})])||R(e,"SIGKILL");else if(e)try{e.kill("SIGTERM")}catch{}}isAlive(){return this.alive}async createSession(e){if(this.initialized||await this.initializeHandshake(),this.threadId||await this.startNewThread(),!this.threadId)throw new Error("Failed to create session: thread ID is missing");return await this.notifyBindingReadyWithContext(),this.threadId}async resumeSession(e,t){await this.ensureThreadResumed()}async destroySession(e){this.threadId=null,this.threadResumePending=!1,this.persistThreadId(void 0)}sendPrompt(e){const t=new T(e.adapterSessionId);return this.runTurn(e,t).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){if(this.threadId)try{await this.sendRequest("turn/interrupt",{threadId:this.threadId,turnId:this.currentTurnId??""},5e3)}catch{}}setPermissionHandler(e){this.permissionHandler=e}permissionHandler=null;async ping(e){if(!this.alive||!this.process)return!1;if(!this.process.pid){const t=!!(this.process.stdin&&!this.process.stdin.destroyed),i=!!(this.process.stdout&&!this.process.stdout.destroyed);return t&&i}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const t=++this.requestId,i={jsonrpc:"2.0",id:t,method:"ping",params:{}};return await new Promise(s=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(t)),s(!1)},e);this.pendingRequests.set(String(t),{resolve:()=>s(!0),reject:()=>s(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(i)}
2
+ `)})}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.currentTurnId!==null||this.compacting,sessions:this.threadId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.activeEventId=null}setModel(e){this.model=e,this.persistCodexContext(),r.info("codex-adapter",`Model set to: ${e}`)}setMode(e){const t=_(e);if(!t){r.info("codex-adapter",`Ignoring unsupported mode: ${e}`);return}this.collaborationMode=t,this.persistCodexContext(),r.info("codex-adapter",`Mode set to: ${t}`)}setReasoningEffort(e){this.reasoningEffort=e,this.persistCodexContext(),r.info("codex-adapter",`Reasoning effort set to: ${e}`)}setSandboxMode(e){this.sandboxMode=e,this.persistCodexContext(),r.info("codex-adapter",`Sandbox mode set to: ${e} (applied on next restart)`)}async threadCompact(){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/compact/start",{threadId:this.threadId},12e4)}async threadRollback(e){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/rollback",{threadId:this.threadId,numTurns:e},1e4)}getThreadId(){return this.threadId}getMcpConfig(){if(!this.internalApi)return null;const e=B(J(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?K(e,[e]):!1}async probe(e){const t=this.config.command||"codex",i=await ae(t),s=i!==null;let n=null,o;if(s){const m=await de(t);n=m.version,m.error&&(o=m.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const h=k().baseUrl??null,u=this.currentModelId()||null,f=this.getStatus(),p=e?.conversation?{attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"codex does not support minimal prompt probe"}}:{attempted:!1,ok:!1,latency_ms:null},x=await this.probeSessionRecord(),v=P({codexHome:this.codexHome});return{cli:{command:t,installed:s,path:i,version:n,...o?{error:o}:{}},conversation:p,config:{model:u,base_url:h,source:{model:u?"runtime":"unknown",base_url:h?"file":"unknown"}},process:{started:!!this.process,alive:f.alive,busy:f.busy},session:x,lock:{present:v.present,locked:v.locked,stale:v.stale,path:v.path}}}async probeSessionRecord(){const e=this.threadId?re(this.threadId,this.codexHome):null;if(!e)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const t=await M(e);return{recordPath:e,lastActivityMs:t.mtimeMs,freshMs:Date.now()-t.mtimeMs}}catch{return{recordPath:e,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"compact",description:"Compact thread to reduce context size"},{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[default|plan]"},{name:"rollback",description:"Roll back thread turns",args:"[num_turns]"},{name:"interrupt",description:"Interrupt current turn"},{name:"rate_limits",description:"Show current rate limits"},{name:"status",description:"Show thread and session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,t,i){try{switch(e){case"compact":{if(!this.threadId)return{status:"failed",message:"No active thread"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive()),await this.ensureThreadResumed(),this.currentTurnId)return{status:"failed",message:"agent busy"};if(this.needsHistoryInjection)return{status:"failed",message:"\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"};this.compacting=!0;const s=new Promise(n=>{this.compactionDoneResolver=n});try{return await this.threadCompact(),this.armCompactionFallback(),await s,{status:"ok",message:"Thread compacted"}}catch(n){this.finishCompaction("compact-error");const o=n instanceof Error?n.message:String(n);if(/thread.*(not found|not exist|unknown|invalid)/i.test(o)||/not found.*thread/i.test(o))return this.threadId=null,this.persistThreadId(void 0),{status:"failed",message:"\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"};throw n}}case"model":{const s=t.trim();return s?this.getModelOptions().some(o=>o.id===s)?(this.setModel(s),{status:"ok",message:`Model set to ${s}`}):{status:"failed",message:`Unknown model: ${s}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const s=t.trim();if(s){const n=_(s);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${s}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:N}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const s=Math.max(1,parseInt(t.trim(),10)||1);return this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive()),await this.threadRollback(s),{status:"ok",message:`Rolled back ${s} turn(s)`}}case"interrupt":return this.threadId?(await this.cancel(this.threadId),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Turn interrupted"}):{status:"failed",message:"No active thread"};case"rate_limits":return{status:"ok",message:this.rateLimitSnapshot?`Primary: ${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}%, Secondary: ${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`:"Rate limits not available",data:{rateLimits:this.rateLimitSnapshot}};case"status":return{status:"ok",message:`Thread: ${this.threadId??"none"}, Model: ${this.currentModelId()}, Mode: ${this.collaborationMode??"default"}`,data:{threadId:this.threadId,model:this.currentModelId(),mode:this.collaborationMode??"default",cwd:this.cwd,alive:this.alive}};case"skills":{const s=ce({mode:"codex",projectDir:this.cwd}),n=s.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
3
+ `):"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)}}}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${t} action_id=${e.action_id} session_id=${i.session_id??""}`),t){case"set_model":{const s=g(l(i.model_id),l(i.modelId),l(i.value));return s&&this.setModel(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const s=i.mode_id;return s&&this.setMode(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const s=g(l(i.reasoning_effort),l(i.reasoning_eff),l(i.effort));return s&&this.setReasoningEffort(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_sandbox_mode":{const s=g(l(i.sandbox_mode),l(i.sandboxMode),l(i.value));return s&&this.setSandboxMode(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("sandbox_mode_set",!1)),{handled:!0,kind:"set_sandbox_mode"}}case"thread_compact":{r.info("codex-adapter",`thread_compact start action_id=${e.action_id} threadId=${this.threadId}`),this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive());try{if(await this.ensureThreadResumed(),this.needsHistoryInjection)return r.info("codex-adapter",`thread_compact aborted: thread expired, new thread created action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"thread_expired","\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5"),{handled:!0,kind:"thread_compact"};this.compacting=!0;const s=new Promise(n=>{this.compactionDoneResolver=n});await this.threadCompact(),this.armCompactionFallback(),await s,r.info("codex-adapter",`thread_compact done action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(s){this.finishCompaction("compact-error");const n=s instanceof Error?s.message:String(s);/thread.*(not found|not exist|unknown|invalid)/i.test(n)||/not found.*thread/i.test(n)?(r.warn("codex-adapter",`thread_compact thread expired, resetting threadId action_id=${e.action_id} err=${n}`),this.threadId=null,this.persistThreadId(void 0),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"thread_expired","\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u65E0\u53EF\u538B\u7F29\u7684\u5386\u53F2\uFF0C\u8BF7\u53D1\u9001\u4E00\u6761\u65B0\u6D88\u606F\u540E\u91CD\u8BD5")):(r.info("codex-adapter",`thread_compact failed action_id=${e.action_id} err=${n}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n))}return{handled:!0,kind:"thread_compact"}}case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{threadId:this.threadId,model:this.model,mode:this.collaborationMode,approvalPolicy:this.approvalPolicy,cwd:this.cwd,...await this.buildToolbarContextResult("context")}),{handled:!0,kind:"get_context"};case"exec_approve":case"file_approve":{const s=this.resolvePendingApproval(e);if(!s)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:t};const n=i.decision||"allow-once",o=ge[n]??"accept";return this.sendApprovalDecision(s.requestId,o),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,approval_command_id:s.approvalCommandId,decision:n}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t}}case"exec_reject":case"file_reject":{const s=this.resolvePendingApproval(e);return s?(this.sendApprovalDecision(s.requestId,"deny"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,approval_command_id:s.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:t})}case"permission_approve":{const s=this.resolvePendingApproval(e);return!s||s.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:t}):(this.sendApprovalDecision(s.requestId,"accept"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,decision:"approve"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t})}case"permission_reject":{const s=this.resolvePendingApproval(e);return!s||s.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:t}):(this.sendApprovalDecision(s.requestId,"deny"),this.pendingApprovals.delete(s.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:s.approvalId,decision:"deny"}),this.resumeAfterApproval(e,i,s),{handled:!0,kind:t})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(s){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,s instanceof Error?s.message:String(s))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const s=Number(i.numTurns??i.num_turns??1);if(!this.threadId)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"no_thread","No active thread"),{handled:!0,kind:"thread_rollback"};this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive());try{await this.threadRollback(s),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:s})}catch(n){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n instanceof Error?n.message:String(n))}return{handled:!0,kind:"thread_rollback"}}case"get_rate_limits":{const s=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return s?r.info("codex-adapter",`[rate-limits] responding: primary=${s.primary.usedPercent.toFixed(1)}% window=${s.primary.windowMinutes}min resetsAt=${s.primary.resetsAt} secondary=${s.secondary.usedPercent.toFixed(1)}% window=${s.secondary.windowMinutes}min resetsAt=${s.secondary.resetsAt}`):r.info("codex-adapter","[rate-limits] responding: no rateLimitSnapshot available"),r.debug("codex-adapter",`[cp-diagnose] get_rate_limits: threadId=${this.threadId??"-"} contextWindow=${JSON.stringify(n)} tokenUsage=${JSON.stringify(d)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok",{adapterType:"codex",available:s!==null,cached:!1,sampledAt:o,rateLimits:s,contextWindow:n,tokenUsage:d}),{handled:!0,kind:"get_rate_limits"}}default:return{handled:!1,kind:""}}}async buildToolbarContextResult(e,t=!0){t&&await this.refreshCodexModelOptions();const i=_(this.collaborationMode)??"default",s=this.currentModelId(),n=this.getModelOption(s),o=this.getModelOptions(),d=N,h=this.reasoningEffort??n?.defaultReasoningEffort??null,u=n?.supportedReasoningEfforts??[],f=k(this.codexHome),p=!!(f.baseUrl&&D(f.baseUrl)),x=p?[]:u,v=p?null:h,m={outcome:e,session_context:{modelId:s,modeId:i,reasoningEffort:v,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:s,mode_id:i,currentModelId:s,currentModeId:i,available_models:o,available_modes:d,available_efforts:x,availableModels:o,availableModes:d,reasoning_effort:v,sandbox_mode:this.sandboxMode??null,models:o.map(c=>({modelId:c.id,name:c.displayName})),modes:d.map(c=>({id:c.id,name:c.displayName}))};return this.rateLimitSnapshot&&(m.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,m.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,m.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,m.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),m}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const t=this.getModelOptions();return t.find(i=>i.isDefault)?.id??t[0]?.id??""}getModelOption(e){const t=e.trim().toLowerCase();return this.getModelOptions().find(i=>i.id.trim().toLowerCase()===t)}getModelOptions(){const e=this.config.options??{},t=e.available_models??e.availableModels??e.models,i=we(t),s=this.codexModelOptions,n=[],o=String(this.model??"").trim(),d=o&&!s.some(p=>p.id.trim().toLowerCase()===o.toLowerCase()),h=s.length>0?s:d?[]:me;if(o){const p=[...s,...i].find(x=>x.id.trim().toLowerCase()===o.toLowerCase());n.push({id:o,displayName:p?.displayName??y(o),defaultReasoningEffort:p?.defaultReasoningEffort??null,supportedReasoningEfforts:p?.supportedReasoningEfforts??[],isDefault:p?.isDefault??!1})}n.push(...i),n.push(...h);const u=[],f=new Set;for(const p of n){const x=p.id.trim(),v=x.toLowerCase();!x||f.has(v)||(f.add(v),u.push({id:x,displayName:p.displayName.trim()||x,defaultReasoningEffort:p.defaultReasoningEffort,supportedReasoningEfforts:[...p.supportedReasoningEfforts],isDefault:p.isDefault}))}return u}async refreshCodexModelOptions(){if(!this.initialized||!this.alive||!this.process?.stdin||Date.now()-this.codexModelOptionsCachedAt<q.MODEL_CACHE_TTL_MS)return;const e=k(this.codexHome);if(e.baseUrl&&D(e.baseUrl)){try{const t=await _e(e.baseUrl,e.apiKey);if(t.length>0){this.codexModelOptions=j(t),this.codexModelOptionsCachedAt=Date.now(),r.info("codex-adapter",`Loaded ${t.length} models from external provider ${e.baseUrl}`);return}}catch(t){r.warn("codex-adapter",`External provider /v1/models failed (${t instanceof Error?t.message:String(t)}), falling back`)}this.codexModelOptions=[],this.codexModelOptionsCachedAt=Date.now();return}try{const t=[];let i;do{const s=await this.sendRequest("model/list",{cursor:i??null,includeHidden:!1,limit:100},15e3),n=Ce(s);t.push(...n.models),i=n.nextCursor}while(i);t.length>0&&(this.codexModelOptions=j(t),this.codexModelOptionsCachedAt=Date.now())}catch(t){r.warn("codex-adapter",`Failed to load Codex model list: ${t instanceof Error?t.message:String(t)}`)}}currentTurnId=null;compacting=!1;pendingAutoCompact=!1;compactionDeferred=[];compactingTimer=null;compactionDoneResolver=null;activeEventId=null;activeSessionId=null;visibleAgentMessageIds=new Set;hiddenAgentMessageIds=new Set;agentMessagePhases=new Map;codexSequence=0;turnError=null;deliverInboundEvent(e){if(this.compacting){r.info("codex-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.compactionDeferred.push(e);return}const t=Q(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.currentTurnId&&t.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(t).catch(i=>{const s=i instanceof Error?i.message:String(i);if(r.info("codex-adapter",`Steer failed, falling through: ${s}`),!this.currentTurnId){this.startNewTurn(e,t);return}if(Se(s)){r.info("codex-adapter","Unsteerable turn detected, clearing active state and starting new turn"),this.clearActive(),this.startNewTurn(e,t);return}this.callbacks.sendEventResult(e.event_id,"failed",`failed to steer message into active turn: ${s}`)});return}if(this.currentTurnId){r.info("codex-adapter",`Event ${e.event_id} rejected: busy and empty content`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewTurn(e,t)}startNewTurn(e,t){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.codexSequence=0,this.startComposing();const i=this.threadId??"",s=new T(i),n={adapterSessionId:i,text:t,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(o=>({senderId:o.sender_id??"unknown",content:o.content})):void 0};this.runTurn(n,s,e.event_id,e.session_id).catch(o=>{r.error("codex-adapter",`Turn failed: ${o}`),this.callbacks.sendEventResult(e.event_id,"failed",o instanceof Error?o.message:String(o)),this.clearActive()}),this.resetIdleTimer(e.event_id)}deliverStopEvent(e,t){this.activeEventId===e&&(this.cancel(this.threadId??"").catch(()=>{}),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}async spawnCodex(){const e={...process.env,...this.config.env},t=typeof e.PATH=="string"?e.PATH:void 0;t||r.warn("codex-adapter",`spawnCodex: env.PATH is missing! process.env.PATH=${process.env.PATH?"set":"missing"}, config.env=${JSON.stringify(this.config.env)}`);const i=O(this.config.command||"codex",t),s=xe(this.config.args);this.sandboxMode&&s.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${i} ${s.join(" ")}`);try{if(!(await M(this.cwd)).isDirectory())throw new Error(`Bound path is not a directory: ${this.cwd}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${this.cwd}. Please rebind with /grix open <valid-directory>.`):o}const n=P({codexHome:this.codexHome});if(n.locked&&!n.stale)throw new Error(`Codex session appears locked by another process: ${n.path}`);n.stale&&r.warn("codex-adapter",`Ignoring stale Codex lock: ${n.path}`);try{this.process=Y(i,s,{env:e,cwd:this.cwd}).process}catch(o){throw r.error("codex-adapter",`Codex spawn threw: ${o}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",o}this.process.on("error",o=>{r.error("codex-adapter",`Codex process spawn error: ${o}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("spawn failed: "+(o instanceof Error?o.message:String(o))),this.stopComposing(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed","Codex process spawn failed"),this.activeEventId=null),this.emit("exit",1)}),this.process.on("exit",o=>{r.info("codex-adapter",`Codex process exited (code=${o})`),this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("process exited"),this.stopComposing(),this.emit("exit",o)}),this.process.stderr?.on("data",o=>{const d=o.toString().trim();d&&r.info("codex-adapter",`[codex stderr] ${d}`)}),this.bindStdout(),this.alive=!0}bindStdout(){if(!this.process?.stdout)return;this.process.stdout.setEncoding("utf8"),W({input:this.process.stdout}).on("line",t=>{if(!t.trim())return;let i;try{i=JSON.parse(t)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${t.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),i.id!=null){const s=String(i.id),n=this.pendingRequests.get(s);if(n){clearTimeout(n.timer),this.pendingRequests.delete(s),n.resolve(i);return}}this.handleNotification(i)})}handleNotification(e){const t=e.method,i=e.params??{};switch(this.captureContextWindowFromPayload(t,i),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&t&&t.startsWith("item/")&&!t.endsWith("/requestApproval")&&this.startComposing(),t){case"item/started":{const s=i.item,n=s?.id,o=s?.type,d=s?.phase;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps+=1),o==="agentMessage"&&n&&(typeof d=="string"&&d.trim()?this.agentMessagePhases.set(n,d.trim().toLowerCase()):this.agentMessagePhases.delete(n),te(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const s=i.delta,n=i.itemId;s&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const s=i.item,n=s?.id,o=s?.type;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps=Math.max(0,this.inFlightToolOps-1)),this.activeEventId&&o!=="agentMessage"&&this.emitCodexEvent("item/completed",e),o==="agentMessage"&&n&&(this.visibleAgentMessageIds.delete(n),this.hiddenAgentMessageIds.delete(n),this.agentMessagePhases.delete(n));break}case"turn/started":{const n=i.turn?.id;n&&(this.currentTurnId=n,this.turnError=null,r.info("codex-adapter",`Turn started: ${n}`),this.startComposing());break}case"item/fileChange/requestApproval":{this.handleApprovalRequest(e,"file");break}case"item/commandExecution/requestApproval":{this.handleApprovalRequest(e,"exec");break}case"item/permissions/requestApproval":{this.activeEventId&&this.emitCodexEvent("item/permissions/requestApproval",e),this.handleApprovalRequest(e,"permission");break}case"turn/completed":{this.handleTurnCompleted(i);break}case"error":{const s=i.message??JSON.stringify(i);r.error("codex-adapter",`Codex error: ${s}`),s&&(this.turnError=s),this.activeEventId&&this.callbacks.sendRunError(this.activeEventId,this.activeSessionId??"",s);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(i),this.rateLimitSnapshot&&(r.info("codex-adapter",`Rate limits updated: primary=${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}% secondary=${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`),this.callbacks.onRateLimitsUpdated?.(this.rateLimitSnapshot));break}case"thread/tokenUsage/updated":{const s=i.threadId,n=i.tokenUsage,o=n?.last;(!s||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!s)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(i)}`),s&&n&&o&&(this.currentThreadTokenUsage={inputTokens:o.inputTokens??0,outputTokens:o.outputTokens??0,cacheReadInputTokens:o.cachedInputTokens??0,cacheCreationInputTokens:0},r.info("codex-adapter",`[cp-diagnose] token_usage raw thread=${s}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${s}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.publishContextWindowSnapshot());break}default:break}}captureContextWindowFromPayload(e,t){const i=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],s=this.findNumericField(t,i);if(s==null||s<=0){const n=this.probeContextWindowFields(t,i);n.foundAnyCandidateField&&r.info("codex-adapter",`[cp-diagnose] context_window missing_or_invalid: method=${e??"-"} matchedFields=${n.matchedFields.join(",")||"-"} raw=${n.rawValues.join(",")||"-"} extracted=${String(s)}`);return}this.currentModelContextWindow!==s&&(this.currentModelContextWindow=s,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${s}`),this.publishContextWindowSnapshot())}probeContextWindowFields(e,t){const i=[],s=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const h of o)n(h);return}const d=o;for(const h of t)Object.prototype.hasOwnProperty.call(d,h)&&(i.push(h),s.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:i.length>0,matchedFields:i,rawValues:s}}findNumericField(e,t){if(!e||typeof e!="object")return null;const i=e;for(const s of t){const n=i[s];if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){const o=Number(n);if(Number.isFinite(o))return o}}for(const s of Object.values(i)){if(Array.isArray(s)){for(const o of s){const d=this.findNumericField(o,t);if(d!=null)return d}continue}const n=this.findNumericField(s,t);if(n!=null)return n}return null}buildContextWindowSnapshot(){const e=this.currentModelContextWindow;if(!e||e<=0)return r.debug("codex-adapter",`[cp-diagnose] context_window snapshot skipped: invalid size=${String(e)}`),null;const t=this.currentThreadTokenUsage,i=t?t.inputTokens+t.outputTokens:null,s=i==null?null:Math.max(0,e-i),n=i==null?null:Math.min(100,i/e*100),o=i==null?null:Math.max(0,100-(n??0));return r.info("codex-adapter",`[cp-diagnose] context_window computed: threadId=${this.threadId??"-"} size=${e} usedTokens=${String(i)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:i,remainingTokens:s,usedPercentage:n,remainingPercentage:o,source:"codex:model_context_window"}}publishContextWindowSnapshot(){const e=this.buildContextWindowSnapshot();this.callbacks.onContextWindowUpdated?.(e),this.maybeScheduleAutoCompact(e)}maybeScheduleAutoCompact(e){const t=e?.usedPercentage;typeof t!="number"||t<L||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("codex-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${L}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.alive||this.currentTurnId||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.activeSessionId??"").then(e=>{r.info("codex-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`),e.status==="failed"&&/agent busy/i.test(e.message??"")&&(this.pendingAutoCompact=!0)}).catch(e=>{r.warn("codex-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:e}`)})))}parseRateLimitSnapshot(e){try{const i=e.rateLimitsByLimitId?.codex??e.rateLimits??e,s=i.primary,n=i.secondary,o=i.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const I=Number(c);if(Number.isFinite(I))return I}return 0},h=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,u=c=>{if(c==null)return null;if(typeof c=="string"){const I=c.trim();if(!I)return null;const w=Number(I);return Number.isFinite(w)&&w>1e9?new Date(w*1e3).toISOString():I}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const I=c>1e12?c/1e3:c;return new Date(I*1e3).toISOString()}return null},f=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:u(c.resets_at??c.resetsAt)}),p=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),x=()=>{const c=i.windows;if(!Array.isArray(c))return null;const I=c.map(b=>b&&typeof b=="object"?f(b):p()).filter(b=>b.windowMinutes>0);if(I.length===0)return null;const w=[...I].sort((b,z)=>b.windowMinutes-z.windowMinutes),H=w.find(b=>Math.abs(b.windowMinutes-300)<=30)??w[0],U=w.find(b=>Math.abs(b.windowMinutes-10080)<=120)??w[w.length-1];return{primary:H,secondary:U}},m=(s||n?{primary:s?f(s):p(),secondary:n?f(n):p()}:null)??x();return m?{primary:m.primary,secondary:m.secondary,credits:o?{hasCredits:h(o.has_credits??o.hasCredits),unlimited:h(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,t){const i=e.id;if(i==null)return;const s=e.method,n=`${i}`.trim(),o=`codex_${F(this.activeSessionId??"")}_${F(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(i,"accept");return}this.activeEventId&&this.emitCodexEvent(s,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:i,kind:t}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${t})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const t=e.params??{},i=g(t.approval_id,t.approvalId);if(i)return this.pendingApprovals.get(i)??null;const s=g(t.approval_command_id,t.approvalCommandId);if(s){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===s)return n}return null}sendApprovalDecision(e,t){if(e==null||!this.process?.stdin)return;const i={jsonrpc:"2.0",id:e,result:{decision:t}};this.process.stdin.write(`${JSON.stringify(i)}
4
4
  `)}armCompactionFallback(){this.compacting&&(this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>this.finishCompaction("fallback-timeout"),1e4),this.compactingTimer.unref?.())}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null);const t=this.compactionDeferred.splice(0);r.info("codex-adapter",`Compaction finished (${e}); replaying ${t.length} deferred events`);for(const s of t)this.deliverInboundEvent(s);const i=this.compactionDoneResolver;this.compactionDoneResolver=null,i?.()}handleTurnCompleted(e){if(this.currentTurnId=null,this.stopComposing(),this.activeEventId){const t=this.activeEventId,i={event_id:t,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:"turn/completed",codex_sequence:++this.codexSequence,codex_payload:e,codex_at:new Date().toISOString()},s=this.turnError;this.turnError=null;const n=!!s;this.callbacks.sendCodexEventReliable?(this.clearActive({runPendingAutoCompact:!1}),this.callbacks.sendCodexEventReliable(i).then(()=>{this.callbacks.sendEventResult(t,n?"failed":"responded",n?s??void 0:void 0),this.tryRunPendingAutoCompact()}).catch(o=>{r.error("codex-adapter",`sendCodexEventReliable failed event=${t}: ${o}`),this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(t,n?"failed":"responded",n?s??void 0:void 0),this.tryRunPendingAutoCompact()})):(this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(t,n?"failed":"responded",n?s??void 0:void 0),this.clearActive())}this.compacting&&this.finishCompaction("turn-completed")}emitCodexEvent(e,t){this.activeEventId&&this.callbacks.sendCodexEvent({event_id:this.activeEventId,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:e,codex_sequence:++this.codexSequence,codex_payload:t,codex_at:new Date().toISOString()})}startComposing(){}stopComposing(){}async sendRequest(e,t,i=3e4){return new Promise((s,n)=>{if(!this.process?.stdin){n(new Error("Codex process not available"));return}const o=++this.requestId,d={jsonrpc:"2.0",id:o,method:e,params:t??{}},h=setTimeout(()=>{this.pendingRequests.delete(String(o)),n(new Error(`Request timeout: ${e}`))},i);this.pendingRequests.set(String(o),{resolve:f=>{f.error?n(new Error(`JSON-RPC error: ${f.error.message}`)):s(f.result)},reject:n,timer:h});const u=JSON.stringify(d);this.process.stdin.write(`${u}
5
5
  `)})}sendNotification(e,t){if(!this.process?.stdin)return;const i={jsonrpc:"2.0",method:e,params:t??{}};this.process.stdin.write(`${JSON.stringify(i)}
6
- `)}async initializeHandshake(){if(this.initialized)return;const e=Z(),t=await this.sendRequest("initialize",{clientInfo:{name:"grix-connector",title:"Grix Connector",version:e},capabilities:{experimentalApi:!0}},15e3);r.info("codex-adapter",`Initialized: ${JSON.stringify(t)}`),this.sendNotification("initialized"),this.initialized=!0,await this.refreshCodexModelOptions(),this.fetchRateLimits()}fetchRateLimits(){Promise.resolve(this.sendRequest("account/rateLimits/read",void 0,1e4)).then(e=>{const t=this.parseRateLimitSnapshot(e);t&&(this.rateLimitSnapshot=t,r.info("codex-adapter",`Initial rate limits: primary=${t.primary.usedPercent.toFixed(1)}% secondary=${t.secondary.usedPercent.toFixed(1)}% credits=${t.credits.balance??"N/A"}`),this.callbacks.onRateLimitsUpdated?.(t))}).catch(e=>{r.warn("codex-adapter",`Failed to fetch rate limits: ${e instanceof Error?e.message:e}`)})}async runTurn(e,t,i,s){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[];if(this.needsHistoryInjection&&s){this.needsHistoryInjection=!1;const h=await this.loadHistoryForInjection(s);for(const u of h)n.push(u);h.length>0&&r.info("codex-adapter",`Injected ${h.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const h=e.contextMessages.map(u=>`[${u.senderId??"unknown"}]: ${u.content}`).join(`
6
+ `)}async initializeHandshake(){if(this.initialized)return;const e=ee(),t=await this.sendRequest("initialize",{clientInfo:{name:"grix-connector",title:"Grix Connector",version:e},capabilities:{experimentalApi:!0}},15e3);r.info("codex-adapter",`Initialized: ${JSON.stringify(t)}`),this.sendNotification("initialized"),this.initialized=!0,await this.refreshCodexModelOptions(),this.fetchRateLimits()}fetchRateLimits(){Promise.resolve(this.sendRequest("account/rateLimits/read",void 0,1e4)).then(e=>{const t=this.parseRateLimitSnapshot(e);t&&(this.rateLimitSnapshot=t,r.info("codex-adapter",`Initial rate limits: primary=${t.primary.usedPercent.toFixed(1)}% secondary=${t.secondary.usedPercent.toFixed(1)}% credits=${t.credits.balance??"N/A"}`),this.callbacks.onRateLimitsUpdated?.(t))}).catch(e=>{r.warn("codex-adapter",`Failed to fetch rate limits: ${e instanceof Error?e.message:e}`)})}async runTurn(e,t,i,s){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[];if(this.needsHistoryInjection&&s){this.needsHistoryInjection=!1;const h=await this.loadHistoryForInjection(s);for(const u of h)n.push(u);h.length>0&&r.info("codex-adapter",`Injected ${h.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const h=e.contextMessages.map(u=>`[${u.senderId??"unknown"}]: ${u.content}`).join(`
7
7
  `);n.push({type:"text",text:`Conversation context:
8
8
  ${h}
9
9
 
10
- Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const o={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},d=ve(this.collaborationMode,this.model);d&&(o.collaborationMode=d),this.reasoningEffort&&(o.effort=this.reasoningEffort),await this.sendRequest("turn/start",o),t.emitDone({status:"completed"})}async ensureThreadResumed(){if(!(!this.threadId||!this.threadResumePending))try{const e=await this.sendRequest("thread/resume",{threadId:this.threadId,approvalPolicy:this.approvalPolicy,model:this.model,excludeTurns:!0,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}});this.updateModelFromThreadResult(e),this.threadResumePending=!1}catch{this.threadId=null,this.needsHistoryInjection=!0,await this.startNewThread(),this.threadResumePending=!1}}async startNewThread(){try{const e=await this.sendRequest("thread/start",{approvalPolicy:this.approvalPolicy,model:this.model,cwd:this.cwd,experimentalRawEvents:!1,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}}),t=ge(e);if(!t)throw new Error("Codex thread id is missing in thread/start result");this.threadId=t,this.updateModelFromThreadResult(e),this.threadResumePending=!1,this.persistThreadId(t)}catch{throw new Error("Failed to start Codex thread")}}updateModelFromThreadResult(e){if(this.model)return;const t=xe(e);t&&(this.model=t,this.persistCodexContext())}persistThreadId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexThreadId(this.aibotSessionId,e)}persistCodexContext(){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexContext(this.aibotSessionId,{modelId:this.model,modeId:_(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const t=this.callbacks.getConversationLog();if(!t)return[];try{const i=await t.readHistory(e,he),s=[];for(const n of i){const o=(n.content??"").slice(0,ue);n.direction==="inbound"?s.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&s.push({type:"assistant",text:o,text_elements:[]})}return s}catch(i){return r.error("codex-adapter",`Failed to load history: ${i}`),[]}}async steerTurn(e){if(!this.threadId||!this.currentTurnId)throw new Error("No active turn to steer");await this.sendRequest("turn/steer",{threadId:this.threadId,expectedTurnId:this.currentTurnId,input:[{type:"text",text:e,text_elements:[]}]})}resumeAfterApproval(e,t,i){if(!this.threadId)return;const s=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=g(t.session_id,t.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new S(o),h={adapterSessionId:o,text:""};this.activeEventId=s,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,s,n).catch(u=>{r.error("codex-adapter",`Post-approval turn failed: ${u}`),this.callbacks.sendEventResult(s,"failed",u instanceof Error?u.message:String(u)),this.clearActive()}),this.resetIdleTimer(s)}clearActive(e){const t=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.currentTurnId=null,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.inFlightToolOps=0,this.clearIdleTimer(),this.pendingApprovals.clear(),t&&this.emit("eventDone",t),e?.runPendingAutoCompact!==!1&&this.tryRunPendingAutoCompact()}resetIdleTimer(e){const t=Date.now();this.lastProgressAt=t,this.lastIdleCheckAt=t,this.idleNoProgressCount=0,this.armIdleTimer(e)}armIdleTimer(e){this.clearIdleTimer();const t=this.inFlightToolOps>0?ce:de;this.idleTimer=setTimeout(()=>{this.onIdleTimeout(e,t)},t)}async onIdleTimeout(e,t){if(this.activeEventId!==e)return;let i=!1;try{i=await this.ping(le)}catch{i=!1}if(this.activeEventId!==e)return;if(!i){r.error("codex-adapter",`Agent idle ${t/1e3}s and ping failed, declaring stuck: ${e}`),this.declareStuck(e,`agent idle ${t/1e3}s, ping failed`);return}const s=this.lastIdleCheckAt,n=this.lastProgressAt>s,o=n?!1:await this.checkSessionActivitySince(s);if(this.activeEventId===e){if(this.lastIdleCheckAt=Date.now(),n||o){o&&r.info("codex-adapter",`Idle timer: no stream output but rollout still being written, continuing: ${e}`),this.idleNoProgressCount=0,this.armIdleTimer(e);return}this.idleNoProgressCount++,this.idleNoProgressCount>=$?(r.error("codex-adapter",`Agent alive but no progress for ${this.idleNoProgressCount} idle cycles, declaring stuck: ${e}`),this.declareStuck(e,`agent alive but no progress for ${this.idleNoProgressCount} idle cycles`)):(r.warn("codex-adapter",`Idle timer fired, ping ok, no progress (${this.idleNoProgressCount}/${$}): ${e}`),this.armIdleTimer(e))}}declareStuck(e,t){this.callbacks.sendEventResult(e,"failed",t),this.clearActive(),this.emit("stuck")}async checkSessionActivitySince(e){if(!this.threadId)return!1;try{return await ie(this.threadId,e,this.codexHome)}catch{return!1}}isLongRunningToolItemType(e){if(!e)return!1;const t=e.toLowerCase();return t==="commandexecution"||t==="command_execution"||t==="item/commandexecution"||t==="filechange"||t==="file_change"||t==="item/filechange"}shouldSuppressAgentMessageDeltaDuringToolRun(e){return this.inFlightToolOps<=0?!1:(r.info("codex-adapter",`Suppressing agentMessage delta during tool run item_id=${e??"unknown"}`),!0)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}rejectAllPending(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`Request canceled: ${e}`));this.pendingRequests.clear(),this.pendingApprovals.clear()}}const fe={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function me(a){return a&&a.length>0?[...a]:["app-server"]}function l(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function ge(a){if(!a||typeof a!="object")return null;const e=a,t=e.thread&&typeof e.thread=="object"?e.thread:void 0;return g(l(e.threadId),l(e.threadID),l(t?.id))??null}function xe(a){return!a||typeof a!="object"?null:l(a.model)??null}function _(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function ve(a,e){const t=_(a),i=l(e);if(!(!t||!i))return{mode:t,settings:{model:i}}}function Ie(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const o=t.trim();o&&e.push(ye(o,y(o)));continue}if(!t||typeof t!="object")continue;const i=t,s=g(l(i.id),l(i.model_id),l(i.modelId),l(i.value));if(!s)continue;const n=g(l(i.displayName),l(i.display_name),l(i.name),l(i.label))??y(s);e.push({id:s,displayName:n,defaultReasoningEffort:l(i.defaultReasoningEffort)??null,supportedReasoningEfforts:k(i.supportedReasoningEfforts),isDefault:i.isDefault===!0})}return e}function D(a){const e=a.toLowerCase().replace(/\/+$/,"");return!(e==="https://api.openai.com"||e==="https://api.openai.com/v1"||e.endsWith(".openai.com"))}async function be(a,e){const i=`${a.replace(/\/+$/,"")}/models`,s={Accept:"application/json"};e&&(s.Authorization=`Bearer ${e}`);const n=new AbortController,o=setTimeout(()=>n.abort(),1e4);try{const d=await fetch(i,{headers:s,signal:n.signal});if(!d.ok)return[];const h=await d.json();return we(h)}finally{clearTimeout(o)}}function we(a){if(!a||typeof a!="object")return[];const e=a,t=Array.isArray(e.data)?e.data:[],i=[];for(const s of t){if(!s||typeof s!="object")continue;const n=s,o=g(l(n.id),l(n.model));if(!o)continue;const d=g(l(n.displayName),l(n.display_name),l(n.name))??y(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:k(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return i}function _e(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,t=Array.isArray(e.data)?e.data:[],i=[];for(const s of t){if(!s||typeof s!="object")continue;const n=s;if(n.hidden===!0)continue;const o=g(l(n.id),l(n.model));if(!o)continue;const d=g(l(n.displayName),l(n.display_name),l(n.name))??y(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:k(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return{models:i,nextCursor:l(e.nextCursor)??null}}function j(a){const e=[],t=new Set;for(const i of a){const s=i.id.trim(),n=s.toLowerCase();!s||t.has(n)||(t.add(n),e.push({id:s,displayName:i.displayName.trim()||s,defaultReasoningEffort:i.defaultReasoningEffort,supportedReasoningEfforts:[...i.supportedReasoningEfforts],isDefault:i.isDefault}))}return e}function ye(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!1}}function k(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const s=t.trim();s&&e.push(s);continue}if(!t||typeof t!="object")continue;const i=l(t.reasoningEffort);i&&e.push(i)}return e}function y(a){return a.split(/[-_\s]+/).filter(Boolean).map(e=>/^(gpt|codex|o\d+)$/i.test(e)?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function g(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function F(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class S extends E{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("codex-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}function Ce(a){return["cannot steer a compact turn","turn not found","turn already completed","no active turn"].some(t=>a.includes(t))}export{q as CodexAdapter};
10
+ Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const o={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},d=be(this.collaborationMode,this.model);d&&(o.collaborationMode=d),this.reasoningEffort&&(o.effort=this.reasoningEffort),await this.sendRequest("turn/start",o),t.emitDone({status:"completed"})}async ensureThreadResumed(){if(!(!this.threadId||!this.threadResumePending))try{const e=await this.sendRequest("thread/resume",{threadId:this.threadId,approvalPolicy:this.approvalPolicy,model:this.model,excludeTurns:!0,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}});this.updateModelFromThreadResult(e),this.threadResumePending=!1}catch{this.threadId=null,this.needsHistoryInjection=!0,await this.startNewThread(),this.threadResumePending=!1}}async startNewThread(){try{const e=await this.sendRequest("thread/start",{approvalPolicy:this.approvalPolicy,model:this.model,cwd:this.cwd,experimentalRawEvents:!1,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}}),t=ve(e);if(!t)throw new Error("Codex thread id is missing in thread/start result");this.threadId=t,this.updateModelFromThreadResult(e),this.threadResumePending=!1,this.persistThreadId(t)}catch{throw new Error("Failed to start Codex thread")}}updateModelFromThreadResult(e){if(this.model)return;const t=Ie(e);t&&(this.model=t,this.persistCodexContext())}persistThreadId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexThreadId(this.aibotSessionId,e)}persistCodexContext(){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexContext(this.aibotSessionId,{modelId:this.model,modeId:_(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const t=this.callbacks.getConversationLog();if(!t)return[];try{const i=await t.readHistory(e,pe),s=[];for(const n of i){const o=(n.content??"").slice(0,fe);n.direction==="inbound"?s.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&s.push({type:"assistant",text:o,text_elements:[]})}return s}catch(i){return r.error("codex-adapter",`Failed to load history: ${i}`),[]}}async steerTurn(e){if(!this.threadId||!this.currentTurnId)throw new Error("No active turn to steer");await this.sendRequest("turn/steer",{threadId:this.threadId,expectedTurnId:this.currentTurnId,input:[{type:"text",text:e,text_elements:[]}]})}resumeAfterApproval(e,t,i){if(!this.threadId)return;const s=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=g(t.session_id,t.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new T(o),h={adapterSessionId:o,text:""};this.activeEventId=s,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,s,n).catch(u=>{r.error("codex-adapter",`Post-approval turn failed: ${u}`),this.callbacks.sendEventResult(s,"failed",u instanceof Error?u.message:String(u)),this.clearActive()}),this.resetIdleTimer(s)}clearActive(e){const t=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.currentTurnId=null,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.inFlightToolOps=0,this.clearIdleTimer(),this.pendingApprovals.clear(),t&&this.emit("eventDone",t),e?.runPendingAutoCompact!==!1&&this.tryRunPendingAutoCompact()}resetIdleTimer(e){const t=Date.now();this.lastProgressAt=t,this.lastIdleCheckAt=t,this.idleNoProgressCount=0,this.armIdleTimer(e)}armIdleTimer(e){this.clearIdleTimer();const t=this.inFlightToolOps>0?he:le;this.idleTimer=setTimeout(()=>{this.onIdleTimeout(e,t)},t)}async onIdleTimeout(e,t){if(this.activeEventId!==e)return;let i=!1;try{i=await this.ping(ue)}catch{i=!1}if(this.activeEventId!==e)return;if(!i){r.error("codex-adapter",`Agent idle ${t/1e3}s and ping failed, declaring stuck: ${e}`),this.declareStuck(e,`agent idle ${t/1e3}s, ping failed`);return}const s=this.lastIdleCheckAt,n=this.lastProgressAt>s,o=n?!1:await this.checkSessionActivitySince(s);if(this.activeEventId===e){if(this.lastIdleCheckAt=Date.now(),n||o){o&&r.info("codex-adapter",`Idle timer: no stream output but rollout still being written, continuing: ${e}`),this.idleNoProgressCount=0,this.armIdleTimer(e);return}this.idleNoProgressCount++,this.idleNoProgressCount>=$?(r.error("codex-adapter",`Agent alive but no progress for ${this.idleNoProgressCount} idle cycles, declaring stuck: ${e}`),this.declareStuck(e,`agent alive but no progress for ${this.idleNoProgressCount} idle cycles`)):(r.warn("codex-adapter",`Idle timer fired, ping ok, no progress (${this.idleNoProgressCount}/${$}): ${e}`),this.armIdleTimer(e))}}declareStuck(e,t){this.callbacks.sendEventResult(e,"failed",t),this.clearActive(),this.emit("stuck")}async checkSessionActivitySince(e){if(!this.threadId)return!1;try{return await oe(this.threadId,e,this.codexHome)}catch{return!1}}isLongRunningToolItemType(e){if(!e)return!1;const t=e.toLowerCase();return t==="commandexecution"||t==="command_execution"||t==="item/commandexecution"||t==="filechange"||t==="file_change"||t==="item/filechange"}shouldSuppressAgentMessageDeltaDuringToolRun(e){return this.inFlightToolOps<=0?!1:(r.info("codex-adapter",`Suppressing agentMessage delta during tool run item_id=${e??"unknown"}`),!0)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}rejectAllPending(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`Request canceled: ${e}`));this.pendingRequests.clear(),this.pendingApprovals.clear()}}const ge={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function xe(a){return a&&a.length>0?[...a]:["app-server"]}function l(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function ve(a){if(!a||typeof a!="object")return null;const e=a,t=e.thread&&typeof e.thread=="object"?e.thread:void 0;return g(l(e.threadId),l(e.threadID),l(t?.id))??null}function Ie(a){return!a||typeof a!="object"?null:l(a.model)??null}function _(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function be(a,e){const t=_(a),i=l(e);if(!(!t||!i))return{mode:t,settings:{model:i}}}function we(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const o=t.trim();o&&e.push(ke(o,y(o)));continue}if(!t||typeof t!="object")continue;const i=t,s=g(l(i.id),l(i.model_id),l(i.modelId),l(i.value));if(!s)continue;const n=g(l(i.displayName),l(i.display_name),l(i.name),l(i.label))??y(s);e.push({id:s,displayName:n,defaultReasoningEffort:l(i.defaultReasoningEffort)??null,supportedReasoningEfforts:S(i.supportedReasoningEfforts),isDefault:i.isDefault===!0})}return e}function D(a){const e=a.toLowerCase().replace(/\/+$/,"");return!(e==="https://api.openai.com"||e==="https://api.openai.com/v1"||e.endsWith(".openai.com"))}async function _e(a,e){const i=`${a.replace(/\/+$/,"")}/models`,s={Accept:"application/json"};e&&(s.Authorization=`Bearer ${e}`);const n=new AbortController,o=setTimeout(()=>n.abort(),1e4);try{const d=await fetch(i,{headers:s,signal:n.signal});if(!d.ok)return[];const h=await d.json();return ye(h)}finally{clearTimeout(o)}}function ye(a){if(!a||typeof a!="object")return[];const e=a,t=Array.isArray(e.data)?e.data:[],i=[];for(const s of t){if(!s||typeof s!="object")continue;const n=s,o=g(l(n.id),l(n.model));if(!o)continue;const d=g(l(n.displayName),l(n.display_name),l(n.name))??y(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:S(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return i}function Ce(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,t=Array.isArray(e.data)?e.data:[],i=[];for(const s of t){if(!s||typeof s!="object")continue;const n=s;if(n.hidden===!0)continue;const o=g(l(n.id),l(n.model));if(!o)continue;const d=g(l(n.displayName),l(n.display_name),l(n.name))??y(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:S(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return{models:i,nextCursor:l(e.nextCursor)??null}}function j(a){const e=[],t=new Set;for(const i of a){const s=i.id.trim(),n=s.toLowerCase();!s||t.has(n)||(t.add(n),e.push({id:s,displayName:i.displayName.trim()||s,defaultReasoningEffort:i.defaultReasoningEffort,supportedReasoningEfforts:[...i.supportedReasoningEfforts],isDefault:i.isDefault}))}return e}function ke(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!1}}function S(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const s=t.trim();s&&e.push(s);continue}if(!t||typeof t!="object")continue;const i=l(t.reasoningEffort);i&&e.push(i)}return e}function y(a){return a.split(/[-_\s]+/).filter(Boolean).map(e=>/^(gpt|codex|o\d+)$/i.test(e)?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function g(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function F(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class T extends E{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("codex-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}function Se(a){return["cannot steer a compact turn","turn not found","turn already completed","no active turn"].some(t=>a.includes(t))}export{q as CodexAdapter};
@@ -1,8 +1,8 @@
1
- import{createInterface as P}from"node:readline";import{EventEmitter as R}from"node:events";import{mkdirSync as x,readFileSync as T,writeFileSync as M,readdirSync as D,statSync as q}from"node:fs";import{join as f}from"node:path";import{homedir as F}from"node:os";import{GRIX_PATHS as j,log as c}from"../../core/log/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{InternalApiServer as H}from"../../core/mcp/internal-api-server.js";import{resolveCommandPath as C,spawnCommand as $,killProcessGroup as S}from"../../core/runtime/spawn.js";const b=12e4;function U(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function B(h,e,t){if(t<0)return null;let n;try{n=D(h,{withFileTypes:!0})}catch{return null}for(const s of n){const o=f(h,s.name);if(s.isDirectory()){const i=B(o,e,t-1);if(i)return i}else if(s.isFile()&&s.name.endsWith(e))return o}return null}function W(h,e){if(!h)return null;const t=e||f(F(),".cursor","projects");return B(t,`${h}.jsonl`,6)}class E extends R{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class k extends R{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new H,this.internalApi.setInvokeHandler(async(t,n)=>this.callbacks.agentInvoke(t,n)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=C(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),n=$(t,["agent","models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),s=await new Promise(i=>{let r="";n.process.stdout?.on("data",d=>{r+=String(d)}),n.process.once("close",()=>i(r))}),o=[];for(const i of s.split(`
2
- `)){const r=i.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&o.push({id:d[1],displayName:d[2].trim()})}o.length>0&&(this._availableModels=o,c.info("cursor-adapter",`Loaded ${o.length} available models`))}catch(e){c.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())S(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e)}sendPrompt(e){const t=new E(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,o=this.pendingBySession.get(s)??[];return o.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,o),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:o,handle:i}=e,r=o.adapterSessionId,d=this.sessionRuntime.get(o.adapterSessionId)??{},m=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),m.trust!==!1&&u.push("--trust");const v=d.cwd||m.workspace;this.internalApi&&v&&(this.ensureCursorMcpConfig(v,this.internalApi.url),u.push("--approve-mcps")),v&&u.push("--workspace",v);const I=d.modelId||m.model;I&&u.push("--model",I);const w=d.modeId||m.mode;w&&u.push("--mode",w);const A=!!(d.cursorSessionId&&m.use_continue!==!1&&!t);A&&u.push("--continue"),u.push(this.buildPromptText(o));const _={...process.env,...this.config.env??{}},L=C(this.config.command,typeof _.PATH=="string"?_.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let p;try{p=$(L,u,{cwd:v||process.cwd(),env:_,stdio:["ignore","pipe","pipe"]}).process}catch(l){this.finishActive(r,"failed",`spawn failed: ${l instanceof Error?l.message:String(l)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const a={event:s,request:o,handle:i,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,retryCount:n,usedContinue:A,workspace:v||process.cwd(),args:u};this.activeBySession.set(r,a),this.armActiveIdleTimer(r);const y=o.timeoutMs&&o.timeoutMs>0?o.timeoutMs:0;y>0&&(a.timer=setTimeout(()=>{a.done||(S(p,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${y}ms`))},y)),P({input:p.stdout}).on("line",l=>this.handleStdoutLineForActive(a,l)),p.stderr?.on("data",l=>{const g=a.stderr+String(l??"");a.stderr=g.length>k.STDERR_MAX_CHARS?g.slice(-k.STDERR_MAX_CHARS):g}),p.once("error",l=>{this.finishActive(r,"failed",`spawn failed: ${String(l?.message??l)}`)}),p.once("close",l=>{if(!a.done)if((l??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(a)){a.timer&&clearTimeout(a.timer),a.idleTimer&&clearTimeout(a.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,a.retryCount+1);return}const g=a.stderr.trim()||`cursor agent exited with code ${l??-1}`;this.finishActive(r,"failed",g)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){c.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:String(e.content??"")},s=new E(t),o=this.pendingBySession.get(t)??[];o.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,o),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${o.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],o=s.findIndex(i=>i.event.event_id===e);if(o>=0){const[i]=s.splice(o,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled","stopped"),i.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const o=s.findIndex(r=>r.event.event_id===e);if(o<0)continue;const[i]=s.splice(o,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled","stopped"),i.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),o=e.action_id;switch(t){case"set_model":{const i=String(n.model_id??"").trim();if(!i||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:i}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"model_set",session_context:{model_id:i,mode_id:r.modeId??null,modelId:i,modeId:r.modeId??null},model_id:i,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const i=String(n.mode_id??"").trim();if(!i||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:i}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:i,modelId:r.modelId??null,modeId:i},model_id:r.modelId??null,mode_id:i}),{handled:!0,kind:"set_mode"}}case"get_context":{const i=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(o,"ok",{session_context:{model_id:i?.modelId??null,mode_id:i?.modeId??null,cwd:i?.cwd??null},model_id:i?.modelId??null,mode_id:i?.modeId??null,modelId:i?.modelId??null,modeId:i?.modeId??null,cwd:i?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const i=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(o,"ok",i),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const i=s?this.getUsageSnapshot(s):null;return i?this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!0,sampledAt:i.sampledAt,turns:i.turns,tokenUsage:i.total}):this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const i=String(n.verb??"").trim().toLowerCase();if(i==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"restarted"});else if(i==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(o,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_verb",`Unsupported verb: ${i}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!U(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await N(this.config.command||"agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?W(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=q(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){return!e.contextMessages||e.contextMessages.length===0?e.text:`${e.contextMessages.map(n=>`[${n.senderId}] ${n.content}`).join(`
1
+ import{createInterface as P}from"node:readline";import{EventEmitter as R}from"node:events";import{mkdirSync as M,readFileSync as T,writeFileSync as x,readdirSync as q,statSync as j}from"node:fs";import{join as m}from"node:path";import{homedir as $}from"node:os";import{GRIX_PATHS as F,log as c}from"../../core/log/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{InternalApiServer as H}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as U}from"../../default-skills/index.js";import{resolveCommandPath as C,spawnCommand as B,killProcessGroup as S}from"../../core/runtime/spawn.js";const k=12e4;function W(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function E(h,e,t){if(t<0)return null;let i;try{i=q(h,{withFileTypes:!0})}catch{return null}for(const s of i){const o=m(h,s.name);if(s.isDirectory()){const n=E(o,e,t-1);if(n)return n}else if(s.isFile()&&s.name.endsWith(e))return o}return null}function O(h,e){if(!h)return null;const t=e||m($(),".cursor","projects");return E(t,`${h}.jsonl`,6)}class D extends R{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class b extends R{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new H,this.internalApi.setInvokeHandler(async(t,i)=>this.callbacks.agentInvoke(t,i)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=C(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),i=B(t,["agent","models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),s=await new Promise(n=>{let r="";i.process.stdout?.on("data",d=>{r+=String(d)}),i.process.once("close",()=>n(r))}),o=[];for(const n of s.split(`
2
+ `)){const r=n.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&o.push({id:d[1],displayName:d[2].trim()})}o.length>0&&(this._availableModels=o,c.info("cursor-adapter",`Loaded ${o.length} available models`))}catch(e){c.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())S(e.child,"SIGTERM");this.activeBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const i=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...i,cwd:typeof t?.cwd=="string"?t.cwd:i.cwd,modelId:typeof t?.modelId=="string"?t.modelId:i.modelId,modeId:typeof t?.modeId=="string"?t.modeId:i.modeId})}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e)}sendPrompt(e){const t=new D(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const i=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,o=this.pendingBySession.get(s)??[];return o.push({event:i,request:e,handle:t}),this.pendingBySession.set(s,o),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const i=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),i&&this.startJob(i,!1,0)}startJob(e,t,i){const{event:s,request:o,handle:n}=e,r=o.adapterSessionId,d=this.sessionRuntime.get(o.adapterSessionId)??{},p=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),p.trust!==!1&&u.push("--trust");const v=d.cwd||p.workspace;this.internalApi&&v&&(this.ensureWorkspaceMcpAndSkills(v),u.push("--approve-mcps")),v&&u.push("--workspace",v);const I=d.modelId||p.model;I&&u.push("--model",I);const A=d.modeId||p.mode;A&&u.push("--mode",A);const w=!!(d.cursorSessionId&&p.use_continue!==!1&&!t);w&&u.push("--continue"),u.push(this.buildPromptText(o));const _={...process.env,...this.config.env??{}},L=C(this.config.command,typeof _.PATH=="string"?_.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${i}`);let f;try{f=B(L,u,{cwd:v||process.cwd(),env:_,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const l={event:s,request:o,handle:n,child:f,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,retryCount:i,usedContinue:w,workspace:v||process.cwd(),args:u};this.activeBySession.set(r,l),this.armActiveIdleTimer(r);const y=o.timeoutMs&&o.timeoutMs>0?o.timeoutMs:0;y>0&&(l.timer=setTimeout(()=>{l.done||(S(f,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${y}ms`))},y)),P({input:f.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),f.stderr?.on("data",a=>{const g=l.stderr+String(a??"");l.stderr=g.length>b.STDERR_MAX_CHARS?g.slice(-b.STDERR_MAX_CHARS):g}),f.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),f.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),l.idleTimer&&clearTimeout(l.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const g=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",g)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){c.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const i={adapterSessionId:t,text:String(e.content??"")},s=new D(t),o=this.pendingBySession.get(t)??[];o.push({event:e,request:i,handle:s}),this.pendingBySession.set(t,o),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${o.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const i=this.activeBySession.get(t);if(i?.event.event_id===e){this.killChildWithFallback(i.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],o=s.findIndex(n=>n.event.event_id===e);if(o>=0){const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[i,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(i,"canceled","stopped");return}for(const[i,s]of this.pendingBySession.entries()){const o=s.findIndex(r=>r.event.event_id===e);if(o<0)continue;const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(i):this.pendingBySession.set(i,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),i=e.params??{},s=String(i.session_id??"").trim(),o=e.action_id;switch(t){case"set_model":{const n=String(i.model_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"model_set",session_context:{model_id:n,mode_id:r.modeId??null,modelId:n,modeId:r.modeId??null},model_id:n,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const n=String(i.mode_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:n,modelId:r.modelId??null,modeId:n},model_id:r.modelId??null,mode_id:n}),{handled:!0,kind:"set_mode"}}case"get_context":{const n=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(o,"ok",{session_context:{model_id:n?.modelId??null,mode_id:n?.modeId??null,cwd:n?.cwd??null},model_id:n?.modelId??null,mode_id:n?.modeId??null,modelId:n?.modelId??null,modeId:n?.modeId??null,cwd:n?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const n=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(o,"ok",n),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const n=s?this.getUsageSnapshot(s):null;return n?this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!0,sampledAt:n.sampledAt,turns:n.turns,tokenUsage:n.total}):this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const n=String(i.verb??"").trim().toLowerCase();if(n==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"restarted"});else if(n==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(o,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_verb",`Unsupported verb: ${n}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const i of this.activeBySession.values()){const s=i.child?.pid;if(s&&!W(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await N(this.config.command||"agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?O(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const i=j(t);return{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){return!e.contextMessages||e.contextMessages.length===0?e.text:`${e.contextMessages.map(i=>`[${i.senderId}] ${i.content}`).join(`
3
3
  `)}
4
4
 
5
5
  [Current user message]
6
- ${e.text}`}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;this.armActiveIdleTimer(e.request.adapterSessionId);const{event:s}=e;let o;try{o=JSON.parse(n)}catch{this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,{type:"raw_text",text:n});return}this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const i=this.extractAssistantText(o);if(i&&(e.seq+=1,this.callbacks.sendStreamChunk(s.event_id,s.session_id,i,e.seq,!1)),o?.type==="result"){const r=String(o?.session_id??"").trim();if(r){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:r})}const d=o?.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 t=e?.message?.content;return Array.isArray(t)?t.map(s=>s?.type==="text"?String(s?.text??""):"").filter(Boolean).join(""):""}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{t.done||(c.error("cursor-adapter",`Idle timeout (${b/1e3}s) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout after ${b/1e3}s`))},b),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);s&&(s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),s.seq>0&&(s.seq+=1,this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0)),c.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.emit("eventDone",s.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase();return t.includes("continue")&&(t.includes("not found")||t.includes("session not found")||t.includes("no previous session"))}ensureCursorMcpConfig(e,t){try{const n=f(e,".cursor"),s=f(n,"mcp.json");x(n,{recursive:!0});let o={};try{o=JSON.parse(T(s,"utf8"))}catch{o={}}const i={command:process.execPath,args:[f(process.cwd(),"dist","mcp","stdio","server.js"),"--handle-url",t]};(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={}),o.mcpServers.grix=i,M(s,`${JSON.stringify(o,null,2)}
7
- `,"utf8"),this.recordCursorMcpRegistry(e,s,i),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(n){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(n)}`)}}recordCursorMcpRegistry(e,t,n){try{const s=f(j.data,"cursor"),o=f(s,"mcp-registry.json");x(s,{recursive:!0});let i={};try{i=JSON.parse(T(o,"utf8"))}catch{i={}}i[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},M(o,`${JSON.stringify(i,null,2)}
8
- `,"utf8")}catch(s){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{k as CursorAdapter,W as findCursorTranscript};
6
+ ${e.text}`}handleStdoutLineForActive(e,t){if(e.done)return;const i=t.trim();if(!i)return;this.armActiveIdleTimer(e.request.adapterSessionId);const{event:s}=e;let o;try{o=JSON.parse(i)}catch{this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,{type:"raw_text",text:i});return}this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const n=this.extractAssistantText(o);if(n&&(e.seq+=1,this.callbacks.sendStreamChunk(s.event_id,s.session_id,n,e.seq,!1)),o?.type==="result"){const r=String(o?.session_id??"").trim();if(r){const p=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...p,cursorSessionId:r})}const d=o?.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 t=e?.message?.content;return Array.isArray(t)?t.map(s=>s?.type==="text"?String(s?.text??""):"").filter(Boolean).join(""):""}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{t.done||(c.error("cursor-adapter",`Idle timeout (${k/1e3}s) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout after ${k/1e3}s`))},k),t.idleTimer.unref?.())}finishActive(e,t,i){const s=this.activeBySession.get(e);s&&(s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),s.seq>0&&(s.seq+=1,this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0)),c.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${i?` msg=${i}`:""}`),this.callbacks.sendEventResult(s.event.event_id,t,i),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:i}):s.handle.emitDone({status:"failed",error:i}),this.emit("eventDone",s.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase();return t.includes("continue")&&(t.includes("not found")||t.includes("session not found")||t.includes("no previous session"))}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=m($(),".cursor","skills"),i=U(t);i.length>0&&c.info("cursor-adapter",`Synced connector skills to ${t}: [${i.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const i=m(e,".cursor"),s=m(i,"mcp.json");M(i,{recursive:!0});let o={};try{o=JSON.parse(T(s,"utf8"))}catch{o={}}const n={command:process.execPath,args:[m(process.cwd(),"dist","mcp","stdio","server.js"),"--handle-url",t]};(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={}),o.mcpServers.grix=n,x(s,`${JSON.stringify(o,null,2)}
7
+ `,"utf8"),this.recordCursorMcpRegistry(e,s,n),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(i){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(i)}`)}}recordCursorMcpRegistry(e,t,i){try{const s=m(F.data,"cursor"),o=m(s,"mcp-registry.json");M(s,{recursive:!0});let n={};try{n=JSON.parse(T(o,"utf8"))}catch{n={}}n[e]={mcp_path:t,server:i,updated_at:new Date().toISOString()},x(o,`${JSON.stringify(n,null,2)}
8
+ `,"utf8")}catch(s){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{b as CursorAdapter,O as findCursorTranscript};