grix-connector 3.4.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,9 @@ A command-line daemon that connects your local AI coding agents to the [Grix](ht
4
4
 
5
5
  ## What is Grix?
6
6
 
7
- Grix is an AI Agent scheduling platform. It lets you manage and interact with multiple AI coding agents through a unified chat interface. Register at [grix.im](https://grix.im) to get started.
7
+ Grix is an AI Agent scheduling platform. It lets you manage and interact with multiple AI coding agents through a unified chat interface.
8
+
9
+ > **Reading this as an AI agent doing the install?** Everything here is terminal-only. Account registration included — you collect the user's email and the emailed verification code in chat and call the API yourself ([step 1](#1-register-a-grix-account-and-create-an-agent)). Never send the user off to a website to sign up.
8
10
 
9
11
  ## Get the Client
10
12
 
@@ -45,11 +47,134 @@ Requires Node.js >= 18.
45
47
 
46
48
  On Windows, `grix-connector` uses the built-in Task Scheduler with a hidden WScript launcher (no extra dependency required).
47
49
 
50
+ ### After the install: you still need a Grix account
51
+
52
+ Installing the CLI is not enough — the daemon refuses to start without a Grix account and an agent credential. The install is step 0 of four:
53
+
54
+ 1. **Register a Grix account.** This is where you need the user: **ask them for an email address**, because a verification code is mailed to it and they have to read it back to you. → [step 1](#1-register-a-grix-account-and-create-an-agent)
55
+ 2. Create the agent and collect `agent_id` / `api_key` / `ws_url`. → [step 1.4](#14-create-the-api-agent)
56
+ 3. Write `~/.grix/config/agents.json`. → [step 2](#2-create-agent-config)
57
+ 4. `grix-connector start`. → [step 3](#3-start-the-daemon)
58
+
59
+ So the very first thing to ask the user for, right after the install, is **an email address**.
60
+
61
+ The registration flow is also packaged as a skill, `grix-register`. In OpenClaw plugin mode it is loaded automatically; after a global npm install any agent can read it directly:
62
+
63
+ ```bash
64
+ cat "$(npm root -g)/grix-connector/openclaw-plugin/skills/grix-register/SKILL.md"
65
+ ```
66
+
67
+ Running that skill and following step 1 below are the same thing — the same API calls, described once as a skill and once as plain curl. Use whichever your runtime supports.
68
+
48
69
  ## Quick Start
49
70
 
50
- ### 1. Register a Grix account
71
+ ### 1. Register a Grix account and create an agent
72
+
73
+ This whole step is HTTP API calls — no browser, no dashboard. The user only has to give you two things in chat: **their email address**, and **the 6-digit verification code** that lands in their inbox. You do the rest.
74
+
75
+ **API base:** `https://grix.dhf.pub/v1` — this is the auth/agent API for both regions (`ws.grix.im` serves the WebSocket only, it has no auth routes). A private deployment replaces the host.
76
+
77
+ **Response envelope:** every endpoint returns `{"code": 0, "msg": "success", "data": {...}}`. A HTTP 200 with a non-zero `code` is still a failure — always branch on `code`, not on the HTTP status.
78
+
79
+ #### 1.1 Ask for the email, send the verification code
80
+
81
+ Ask the user for the email address they want the account under, then:
82
+
83
+ ```bash
84
+ curl -s -X POST https://grix.dhf.pub/v1/auth/send-code \
85
+ -H 'Content-Type: application/json' \
86
+ -d '{"email":"<user-email>","scene":"register"}'
87
+ ```
88
+
89
+ On `code: 0`, tell the user to check their inbox (and the spam folder) and ask them to paste the 6-digit code back to you.
90
+
91
+ | `code` | Meaning | What to do |
92
+ |---|---|---|
93
+ | `10003` | Invalid parameter — the email failed validation | Re-check the address with the user |
94
+ | `10005` | Rate limited: a code was already sent | Do **not** retry in a loop. Tell the user to wait ~5 minutes, or ask them for the code that was already sent |
95
+
96
+ #### 1.2 Register
97
+
98
+ **Generate the password yourself** — do not make the user invent one. Use a random 12+ character password mixing upper case, lower case, digits and symbols. Then:
99
+
100
+ ```bash
101
+ curl -s -X POST https://grix.dhf.pub/v1/auth/register \
102
+ -H 'Content-Type: application/json' \
103
+ -d '{
104
+ "email": "<user-email>",
105
+ "password": "<generated-password>",
106
+ "email_code": "<code the user gave you>",
107
+ "device_id": "cli_<random-uuid>",
108
+ "platform": "cli"
109
+ }'
110
+ ```
111
+
112
+ Returns `data.access_token` (plus `refresh_token`, `expires_in`, `user.id`). Keep the token in memory for the next call.
113
+
114
+ **After a successful registration, show the user the generated password and tell them to save it** — it is their account password and it is not recoverable from anywhere else.
115
+
116
+ Failures you will actually hit — note that both of them come back as `code: 10001`, so **read the `msg`, not just the code**:
117
+
118
+ | `msg` | Meaning | What to do |
119
+ |---|---|---|
120
+ | `邮箱验证码错误或已过期` (code wrong or expired) | The code really is wrong | Ask the user to re-read it, or re-send it (1.1) |
121
+ | `注册失败,请检查邮箱验证码后重试` (registration failed, check the code) | **Almost always: the email is already registered.** The message blames the code, but the code already passed validation | Do **not** re-send the code in a loop — go to 1.3 and log in |
122
+
123
+ #### 1.3 Existing account — log in instead
124
+
125
+ Only when the user **gives you their existing password**:
126
+
127
+ ```bash
128
+ curl -s -X POST https://grix.dhf.pub/v1/auth/login \
129
+ -H 'Content-Type: application/json' \
130
+ -d '{"account":"<user-email>","password":"<password>","device_id":"cli_<random-uuid>","platform":"cli"}'
131
+ ```
132
+
133
+ Same `data.access_token` as register. If the user does not remember their password, do not guess and do not pretend to recover it — ask them to reset it in the Grix client, then come back with the password.
134
+
135
+ #### 1.4 Create the API agent
136
+
137
+ This is where `agent_id` and `api_key` come from. Ask the user what to name the agent (or reuse a name already agreed in the conversation):
138
+
139
+ ```bash
140
+ curl -s -X POST https://grix.dhf.pub/v1/agents/create \
141
+ -H 'Content-Type: application/json' \
142
+ -H 'Authorization: Bearer <access_token>' \
143
+ -d '{"agent_name":"<agent-name>","provider_type":3,"is_main":true}'
144
+ ```
145
+
146
+ `provider_type: 3` is the Agent API type — the only type this connector can drive. `is_main: true` gives the first agent the full initial permission scope.
147
+
148
+ Returns in `data`:
149
+
150
+ | Field | Use |
151
+ |---|---|
152
+ | `id` | the `agent_id` for your config |
153
+ | `api_key` | the `api_key` for your config — **shown exactly once, it is never retrievable again** |
154
+ | `api_endpoint` | the `ws_url` for your config |
155
+ | `api_key_hint` | last few characters, for later identification |
156
+
157
+ **If the name is already taken** by a `provider_type: 3` agent, reuse it instead of inventing a new name — list the agents, find the exact-name entry with `provider_type == 3` and `status != 3`, and rotate its key:
158
+
159
+ ```bash
160
+ curl -s -H 'Authorization: Bearer <access_token>' https://grix.dhf.pub/v1/agents/list
161
+
162
+ curl -s -X POST -H 'Authorization: Bearer <access_token>' \
163
+ -H 'Content-Type: application/json' \
164
+ https://grix.dhf.pub/v1/agents/<agent-id>/api/key/rotate -d '{}'
165
+ ```
166
+
167
+ Rotation returns a fresh `api_key` and the same `api_endpoint`. The old key stops working immediately, so only rotate an agent the user actually wants to re-point at this machine.
168
+
169
+ #### 1.5 Map the fields into the config
170
+
171
+ | From the API | Into `agents.json` |
172
+ |---|---|
173
+ | `data.id` | `agent_id` |
174
+ | `data.api_key` | `api_key` |
175
+ | `data.api_endpoint` | `ws_url` — drop any `?agent_id=…` query string, keep only the part before `?` |
51
176
 
52
- Go to [grix.im](https://grix.im), sign up and get your API key.
177
+ If `api_endpoint` comes back empty, fall back to the regional URL in step 2. Write the `api_key` into `~/.grix/config/agents.json` and nowhere else — never into a log, a shell history file, or a repository.
53
178
 
54
179
  ### 2. Create agent config
55
180
 
@@ -298,7 +423,7 @@ Finally send a message to the agent from Grix and confirm the reply really comes
298
423
  ### Requirements
299
424
 
300
425
  - OpenClaw >= 2026.4.8
301
- - A Grix account with agent ID and API key
426
+ - A Grix account with agent ID and API key — get them with the API flow in [step 1](#1-register-a-grix-account-and-create-an-agent); it works the same in plugin mode
302
427
 
303
428
  Plugin mode and the standalone daemon can coexist on one machine — they use separate configs and separate processes. Do not, however, bring the **same** `agent_id` online in both at once; give each mode its own agent.
304
429
 
@@ -1,11 +1,11 @@
1
- import{execFileSync as R}from"node:child_process";import{createInterface as B}from"node:readline";import{EventEmitter as P}from"node:events";import{stat as $}from"node:fs/promises";import{join as S,resolve as J}from"node:path";import{fileURLToPath as X}from"node:url";import{homedir as G}from"node:os";import{syncDefaultSkillsToDir as V}from"../../default-skills/index.js";import{resolveCommandPath as Y,spawnCommand as K,killProcessGroup as L,hasChildProcesses as Z}from"../../core/runtime/spawn.js";import{InternalApiServer as Q}from"../../core/mcp/internal-api-server.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 ie}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as se,isCodexCommand as ne,readCodexProviderSettings as C}from"./codex-trust.js";import{checkCodexSessionActivity as oe}from"./session-activity.js";import{findRolloutFile as re}from"./rollout-locator.js";import{checkCodexLock as N}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,O=2,pe=20,fe=8e3,D=80,me=120*1e3,ge=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],defaultServiceTier:null,serviceTiers:[],isDefault:!0}],j=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function F(a,e){const t=String(a??"").trim()||"codex",s=Y(t,e);if(s!==t)return s;const i=process.platform==="win32"?[]:["/opt/homebrew/bin/codex","/usr/local/bin/codex","/usr/bin/codex"],n=process.platform!=="win32"?[]:[S(process.env.LOCALAPPDATA??"","npm","codex.cmd"),S(process.env.APPDATA??"","npm","codex.cmd"),S(process.env.LOCALAPPDATA??"","Programs","codex","codex.exe")],o=[...i,...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 M extends P{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;serviceTier;idleTimer=null;lastProgressAt=0;lastIdleCheckAt=0;idleNoProgressCount=0;inFlightToolOps=0;pendingApprovals=new Map;needsHistoryInjection=!1;lastInjectedSessionId=null;identityInjectedThreadId=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 s=e.options??{};if(this.approvalPolicy=s.approvalPolicy??"never",this.sandboxMode=typeof s.sandboxMode=="string"&&s.sandboxMode.trim()&&s.sandboxMode.trim()!=="default"?s.sandboxMode.trim():void 0,this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof ie?s.bindingStore:null,this.model=s.model,this.collaborationMode=s.collaborationMode,this.reasoningEffort=s.reasoningEffort,this.serviceTier=s.serviceTier,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.serviceTier=this.serviceTier??this.bindingStore.getCodexServiceTier(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof s.autoTrustProject=="boolean"?s.autoTrustProject:"auto",this.codexHome=typeof s.codexHome=="string"&&s.codexHome.trim()?s.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const i=this.bindingStore.getCodexThreadId(this.aibotSessionId);i&&(this.threadId=i,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 se(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 Q,this.internalApi.setInvokeHandler(async(d,h,f,m)=>this.callbacks.agentInvoke(d,h,m)),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},s=typeof t.PATH=="string"?t.PATH:void 0,i=F(this.config.command||"codex",s);try{R(i,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}R(i,["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=S(G(),".codex","skills"),o=V(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()}}externalProviderCheckedAt=0;externalProviderCached=!1;static EXTERNAL_PROVIDER_CACHE_TTL_MS=60*1e3;isExternalProvider(){const e=Date.now();if(e-this.externalProviderCheckedAt<M.EXTERNAL_PROVIDER_CACHE_TTL_MS)return this.externalProviderCached;const t=C(this.codexHome);return this.externalProviderCached=!!(t.baseUrl&&q(t.baseUrl)),this.externalProviderCheckedAt=e,this.externalProviderCached}effectiveServiceTier(){if(!this.serviceTier||this.isExternalProvider())return;const e=this.getModelOption(this.currentModelId());if(!(e&&!e.serviceTiers.some(t=>t.id===this.serviceTier)))return this.serviceTier}getEffortMeta(){if(this.isExternalProvider())return{};const e=this.currentModelId(),t=this.getModelOption(e),s={},i=t?.supportedReasoningEfforts??[];i.length>0&&(s.available_efforts=i,s.reasoning_effort=this.reasoningEffort??t?.defaultReasoningEffort??null);const n=t?.serviceTiers??[];return n.length>0&&(s.available_service_tiers=n,s.service_tier=this.effectiveServiceTier()??t?.defaultServiceTier??null),s}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");for(const t of this.steerDeferred.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")L(e,"SIGTERM"),await Promise.race([new Promise(s=>{e.once("exit",()=>s(!0))}),new Promise(s=>{setTimeout(()=>s(!1),5e3)})])||L(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 E(e.adapterSessionId);return this.runTurn(e,t).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),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),s=!!(this.process.stdout&&!this.process.stdout.destroyed);return t&&s}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const t=++this.requestId,s={jsonrpc:"2.0",id:t,method:"ping",params:{}};return await new Promise(i=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(t)),i(!1)},e);this.pendingRequests.set(String(t),{resolve:()=>i(!0),reject:()=>i(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(s)}
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=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}`)}setServiceTier(e){const t=e.trim();this.serviceTier=t.toLowerCase()==="default"?void 0:t,this.persistCodexContext(),r.info("codex-adapter",`Service tier set to: ${this.serviceTier??"default"}`)}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=J(X(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?Z(e,[e]):!1}onAgentProfileChanged(){this.identityInjectedThreadId!==null&&(r.info("codex-adapter",`agent profile changed; clearing identity-injected thread record (was ${this.identityInjectedThreadId})`),this.identityInjectedThreadId=null)}async probe(e){const t=this.config.command||"codex",s=await ae(t),i=s!==null;let n=null,o;if(i){const w=await de(t);n=w.version,w.error&&(o=w.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const h=C().baseUrl??null,f=this.currentModelId()||null,m=this.getStatus(),u=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},p=await this.probeSessionRecord(),g=N({codexHome:this.codexHome});return{cli:{command:t,installed:i,path:s,version:n,...o?{error:o}:{}},conversation:u,config:{model:f,base_url:h,source:{model:f?"runtime":"unknown",base_url:h?"file":"unknown"}},process:{started:!!this.process,alive:m.alive,busy:m.busy},session:p,lock:{present:g.present,locked:g.locked,stale:g.stale,path:g.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 $(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,s){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:"The session has expired and there is no history to compact. Please send a new message and try again."};this.compacting=!0;const i=new Promise(n=>{this.compactionDoneResolver=n});try{return await this.threadCompact(),this.armCompactionFallback(),await i,{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:"The session has expired and there is no history to compact. Please send a new message and try again."};throw n}}case"model":{const i=t.trim();return i?this.getModelOptions().some(o=>o.id===i)?(this.setModel(i),{status:"ok",message:`Model set to ${i}`}):{status:"failed",message:`Unknown model: ${i}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const i=t.trim();if(i){const n=T(i);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${i}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:j}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const i=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(i),{status:"ok",message:`Rolled back ${i} 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 i=ce({mode:"codex",projectDir:this.cwd}),n=i.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
3
- `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${t} action_id=${e.action_id} session_id=${s.session_id??""}`),t){case"set_model":{const i=x(c(s.model_id),c(s.modelId),c(s.value));return i&&this.setModel(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const i=s.mode_id;return i&&this.setMode(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const i=x(c(s.reasoning_effort),c(s.reasoning_eff),c(s.effort));return i&&this.setReasoningEffort(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_service_tier":{const i=x(c(s.service_tier),c(s.serviceTier),c(s.value));return i&&this.setServiceTier(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("service_tier_set",!1)),{handled:!0,kind:"set_service_tier"}}case"set_sandbox_mode":{const i=x(c(s.sandbox_mode),c(s.sandboxMode),c(s.value));return i&&this.setSandboxMode(i),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","The session has expired and there is no history to compact. Please send a new message and try again."),{handled:!0,kind:"thread_compact"};this.compacting=!0;const i=new Promise(n=>{this.compactionDoneResolver=n});await this.threadCompact(),this.armCompactionFallback(),await i,r.info("codex-adapter",`thread_compact done action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(i){this.finishCompaction("compact-error");const n=i instanceof Error?i.message:String(i);/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","The session has expired and there is no history to compact. Please send a new message and try again.")):(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 i=this.resolvePendingApproval(e);if(!i)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=s.decision||"allow-once",o=ve[n]??"accept";return this.sendApprovalDecision(i.requestId,o),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,approval_command_id:i.approvalCommandId,decision:n}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t}}case"exec_reject":case"file_reject":{const i=this.resolvePendingApproval(e);return i?(this.sendApprovalDecision(i.requestId,"deny"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,approval_command_id:i.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,s,i),{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 i=this.resolvePendingApproval(e);return!i||i.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(i.requestId,"accept"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,decision:"approve"}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t})}case"permission_reject":{const i=this.resolvePendingApproval(e);return!i||i.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(i.requestId,"deny"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,decision:"deny"}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(i){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,i instanceof Error?i.message:String(i))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const i=Number(s.numTurns??s.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(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:i})}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 i=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return i?r.info("codex-adapter",`[rate-limits] responding: primary=${i.primary.usedPercent.toFixed(1)}% window=${i.primary.windowMinutes}min resetsAt=${i.primary.resetsAt} secondary=${i.secondary.usedPercent.toFixed(1)}% window=${i.secondary.windowMinutes}min resetsAt=${i.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:i!==null,cached:!1,sampledAt:o,rateLimits:i,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 s=T(this.collaborationMode)??"default",i=this.currentModelId(),n=this.getModelOption(i),o=this.getModelOptions(),d=j,h=this.reasoningEffort??n?.defaultReasoningEffort??null,f=n?.supportedReasoningEfforts??[],m=this.effectiveServiceTier()??n?.defaultServiceTier??null,u=n?.serviceTiers??[],p=this.isExternalProvider(),g=p?[]:f,w=p?null:h,l=p?[]:u,I=p?null:m,v={outcome:e,session_context:{modelId:i,modeId:s,reasoningEffort:w,serviceTier:I,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:i,mode_id:s,currentModelId:i,currentModeId:s,available_models:o,available_modes:d,available_efforts:g,availableModels:o,availableModes:d,reasoning_effort:w,available_service_tiers:l,service_tier:I,sandbox_mode:this.sandboxMode??null,models:o.map(_=>({modelId:_.id,name:_.displayName})),modes:d.map(_=>({id:_.id,name:_.displayName}))};return this.rateLimitSnapshot&&(v.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,v.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,v.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,v.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),v}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const t=this.getModelOptions();return t.find(s=>s.isDefault)?.id??t[0]?.id??""}getModelOption(e){const t=e.trim().toLowerCase();return this.getModelOptions().find(s=>s.id.trim().toLowerCase()===t)}getModelOptions(){const e=this.config.options??{},t=e.available_models??e.availableModels??e.models,s=_e(t),i=this.codexModelOptions,n=[],o=String(this.model??"").trim(),d=o&&!i.some(u=>u.id.trim().toLowerCase()===o.toLowerCase()),h=i.length>0?i:d?[]:ge;if(o){const u=[...i,...s].find(p=>p.id.trim().toLowerCase()===o.toLowerCase());n.push({id:o,displayName:u?.displayName??y(o),defaultReasoningEffort:u?.defaultReasoningEffort??null,supportedReasoningEfforts:u?.supportedReasoningEfforts??[],defaultServiceTier:u?.defaultServiceTier??null,serviceTiers:u?.serviceTiers??[],isDefault:u?.isDefault??!1})}n.push(...s),n.push(...h);const f=[],m=new Set;for(const u of n){const p=u.id.trim(),g=p.toLowerCase();!p||m.has(g)||(m.add(g),f.push({id:p,displayName:u.displayName.trim()||p,defaultReasoningEffort:u.defaultReasoningEffort,supportedReasoningEfforts:[...u.supportedReasoningEfforts],defaultServiceTier:u.defaultServiceTier,serviceTiers:[...u.serviceTiers],isDefault:u.isDefault}))}return f}async refreshCodexModelOptions(){if(!this.initialized||!this.alive||!this.process?.stdin||Date.now()-this.codexModelOptionsCachedAt<M.MODEL_CACHE_TTL_MS)return;const e=C(this.codexHome);if(e.baseUrl&&q(e.baseUrl)){try{const t=await Te(e.baseUrl,e.apiKey);if(t.length>0){this.codexModelOptions=H(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 s;do{const i=await this.sendRequest("model/list",{cursor:s??null,includeHidden:!1,limit:100},15e3),n=Se(i);t.push(...n.models),s=n.nextCursor}while(s);t.length>0&&(this.codexModelOptions=H(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=[];steerDeferred=[];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=e.content;if(this.currentTurnId&&t.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(t).catch(s=>{const i=s instanceof Error?s.message:String(s);if(r.info("codex-adapter",`Steer failed, falling through: ${i}`),!this.currentTurnId){this.startNewTurn(e,t);return}if(ke(i)){r.info("codex-adapter","Unsteerable turn detected, clearing active state and starting new turn"),this.clearActive(),this.startNewTurn(e,t);return}r.info("codex-adapter",`Steer failed but turn still active; deferring event ${e.event_id} for replay after turn ends`),this.steerDeferred.push(e)});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 s=this.threadId??"",i=new E(s),n={adapterSessionId:s,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,i,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 s=F(this.config.command||"codex",t),i=xe(this.config.args);this.sandboxMode&&i.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${s} ${i.join(" ")}`);try{if(!(await $(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=N({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=K(s,i,{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"),B({input:this.process.stdout}).on("line",t=>{if(!t.trim())return;let s;try{s=JSON.parse(t)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${t.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),s.id!=null){const i=String(s.id),n=this.pendingRequests.get(i);if(n){clearTimeout(n.timer),this.pendingRequests.delete(i),n.resolve(s);return}}this.handleNotification(s)})}handleNotification(e){const t=e.method,s=e.params??{};switch(this.captureContextWindowFromPayload(t,s),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&t&&t.startsWith("item/")&&!t.endsWith("/requestApproval")&&this.startComposing(),t){case"item/started":{const i=s.item,n=i?.id,o=i?.type,d=i?.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 i=s.delta,n=s.itemId;i&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const i=s.item,n=i?.id,o=i?.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=s.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(s);break}case"error":{const i=s.message??JSON.stringify(s);r.error("codex-adapter",`Codex error: ${i}`),i&&(this.turnError=i),this.activeEventId&&this.callbacks.sendRunError(this.activeEventId,this.activeSessionId??"",i);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(s),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 i=s.threadId,n=s.tokenUsage,o=n?.last;(!i||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!i)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(s)}`),i&&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=${i}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${i}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.publishContextWindowSnapshot());break}default:break}}captureContextWindowFromPayload(e,t){const s=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],i=this.findNumericField(t,s);if(i==null||i<=0){const n=this.probeContextWindowFields(t,s);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(i)}`);return}this.currentModelContextWindow!==i&&(this.currentModelContextWindow=i,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${i}`),this.publishContextWindowSnapshot())}probeContextWindowFields(e,t){const s=[],i=[],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)&&(s.push(h),i.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:s.length>0,matchedFields:s,rawValues:i}}findNumericField(e,t){if(!e||typeof e!="object")return null;const s=e;for(const i of t){const n=s[i];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 i of Object.values(s)){if(Array.isArray(i)){for(const o of i){const d=this.findNumericField(o,t);if(d!=null)return d}continue}const n=this.findNumericField(i,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,s=t?t.inputTokens+t.outputTokens:null,i=s==null?null:Math.max(0,e-s),n=s==null?null:Math.min(100,s/e*100),o=s==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(s)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:s,remainingTokens:i,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<D||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("codex-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${D}%, 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 s=e.rateLimitsByLimitId?.codex??e.rateLimits??e,i=s.primary,n=s.secondary,o=s.credits,d=l=>{if(typeof l=="number"&&Number.isFinite(l))return l;if(typeof l=="string"){const I=Number(l);if(Number.isFinite(I))return I}return 0},h=l=>typeof l=="boolean"?l:typeof l=="string"?l.toLowerCase()==="true":!1,f=l=>{if(l==null)return null;if(typeof l=="string"){const I=l.trim();if(!I)return null;const v=Number(I);return Number.isFinite(v)&&v>1e9?new Date(v*1e3).toISOString():I}if(typeof l=="number"&&Number.isFinite(l)&&l>0){const I=l>1e12?l/1e3:l;return new Date(I*1e3).toISOString()}return null},m=l=>({usedPercent:d(l.used_percent??l.usedPercent),windowMinutes:d(l.window_minutes??l.windowDurationMins??l.windowMinutes),resetsAt:f(l.resets_at??l.resetsAt)}),u=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),p=()=>{const l=s.windows;if(!Array.isArray(l))return null;const I=l.map(b=>b&&typeof b=="object"?m(b):u()).filter(b=>b.windowMinutes>0);if(I.length===0)return null;const v=[...I].sort((b,W)=>b.windowMinutes-W.windowMinutes),_=v.find(b=>Math.abs(b.windowMinutes-300)<=30)??v[0],z=v.find(b=>Math.abs(b.windowMinutes-10080)<=120)??v[v.length-1];return{primary:_,secondary:z}},w=(i||n?{primary:i?m(i):u(),secondary:n?m(n):u()}:null)??p();return w?{primary:w.primary,secondary:w.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 s=e.id;if(s==null)return;const i=e.method,n=`${s}`.trim(),o=`codex_${U(this.activeSessionId??"")}_${U(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(s,"accept");return}this.activeEventId&&this.emitCodexEvent(i,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:s,kind:t}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${t})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const t=e.params??{},s=x(t.approval_id,t.approvalId);if(s)return this.pendingApprovals.get(s)??null;const i=x(t.approval_command_id,t.approvalCommandId);if(i){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===i)return n}return null}sendApprovalDecision(e,t){if(e==null||!this.process?.stdin)return;const s={jsonrpc:"2.0",id:e,result:{decision:t}};this.process.stdin.write(`${JSON.stringify(s)}
4
- `)}armCompactionFallback(){this.compacting&&(this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{r.warn("codex-adapter","Compaction fallback fired: turn/completed not received within timeout, treating as stuck and releasing deferred events"),this.currentTurnId=null,this.finishCompaction("fallback-timeout")},me),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 i of t)this.deliverInboundEvent(i);const s=this.compactionDoneResolver;this.compactionDoneResolver=null,s?.()}handleTurnCompleted(e){if(this.currentTurnId=null,this.stopComposing(),this.activeEventId){const t=this.activeEventId,s={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()},i=this.turnError;this.turnError=null;const n=!!i;this.callbacks.sendCodexEventReliable?(this.clearActive({runPendingAutoCompact:!1}),this.callbacks.sendCodexEventReliable(s).then(()=>{this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??void 0:void 0),this.tryRunPendingAutoCompact()}).catch(o=>{r.error("codex-adapter",`sendCodexEventReliable failed event=${t}: ${o}`),this.callbacks.sendCodexEvent(s),this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??void 0:void 0),this.tryRunPendingAutoCompact()})):(this.callbacks.sendCodexEvent(s),this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??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,s=3e4){return new Promise((i,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}`))},s);this.pendingRequests.set(String(o),{resolve:m=>{m.error?n(new Error(`JSON-RPC error: ${m.error.message}`)):i(m.result)},reject:n,timer:h});const f=JSON.stringify(d);this.process.stdin.write(`${f}
1
+ import{execFileSync as L}from"node:child_process";import{createInterface as J}from"node:readline";import{EventEmitter as N}from"node:events";import{stat as O}from"node:fs/promises";import{join as C,resolve as X}from"node:path";import{fileURLToPath as G}from"node:url";import{homedir as V}from"node:os";import{syncDefaultSkillsToDir as Y}from"../../default-skills/index.js";import{resolveCommandPath as K,spawnCommand as Z,killProcessGroup as D,hasChildProcesses as Q}from"../../core/runtime/spawn.js";import{InternalApiServer as ee}from"../../core/mcp/internal-api-server.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as te}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as ie}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as se}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as ne,isCodexCommand as oe,readCodexProviderSettings as k}from"./codex-trust.js";import{checkCodexSessionActivity as re}from"./session-activity.js";import{findRolloutFile as ae}from"./rollout-locator.js";import{checkCodexLock as j}from"./lock-monitor.js";import{resolveCliPath as de,getCliVersion as ce}from"../../core/util/cli-probe.js";import{scanSkills as le}from"../claude/skill-scanner.js";const he=60*1e3,ue=600*1e3,pe=5*1e3,F=2,fe=20,me=8e3,q=80,ge=120*1e3,ve=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],defaultServiceTier:null,serviceTiers:[],isDefault:!0}],H=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function U(a,e){const t=String(a??"").trim()||"codex",s=K(t,e);if(s!==t)return s;const i=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=[...i,...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 M extends N{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;serviceTier;idleTimer=null;lastProgressAt=0;lastIdleCheckAt=0;idleNoProgressCount=0;inFlightToolOps=0;pendingApprovals=new Map;needsHistoryInjection=!1;lastInjectedSessionId=null;identityInjectedThreadId=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 s=e.options??{};if(this.approvalPolicy=s.approvalPolicy??"never",this.sandboxMode=typeof s.sandboxMode=="string"&&s.sandboxMode.trim()&&s.sandboxMode.trim()!=="default"?s.sandboxMode.trim():void 0,this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof se?s.bindingStore:null,this.model=s.model,this.collaborationMode=s.collaborationMode,this.reasoningEffort=s.reasoningEffort,this.serviceTier=s.serviceTier,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.serviceTier=this.serviceTier??this.bindingStore.getCodexServiceTier(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof s.autoTrustProject=="boolean"?s.autoTrustProject:"auto",this.codexHome=typeof s.codexHome=="string"&&s.codexHome.trim()?s.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const i=this.bindingStore.getCodexThreadId(this.aibotSessionId);i&&(this.threadId=i,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 ne(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 ee,this.internalApi.setInvokeHandler(async(d,u,m,g)=>this.callbacks.agentInvoke(d,u,g)),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},s=typeof t.PATH=="string"?t.PATH:void 0,i=U(this.config.command||"codex",s);try{L(i,["mcp","remove",e.name],{env:t,timeout:1e4,stdio:"ignore"})}catch{}L(i,["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(V(),".codex","skills"),o=Y(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:oe(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()}}externalProviderCheckedAt=0;externalProviderCached=!1;static EXTERNAL_PROVIDER_CACHE_TTL_MS=60*1e3;isExternalProvider(){const e=Date.now();if(e-this.externalProviderCheckedAt<M.EXTERNAL_PROVIDER_CACHE_TTL_MS)return this.externalProviderCached;const t=k(this.codexHome);return this.externalProviderCached=!!(t.baseUrl&&z(t.baseUrl)),this.externalProviderCheckedAt=e,this.externalProviderCached}effectiveServiceTier(){if(!this.serviceTier||this.isExternalProvider())return;const e=this.getModelOption(this.currentModelId());if(!(e&&!e.serviceTiers.some(t=>t.id===this.serviceTier)))return this.serviceTier}getEffortMeta(){if(this.isExternalProvider())return{};const e=this.currentModelId(),t=this.getModelOption(e),s={},i=t?.supportedReasoningEfforts??[];i.length>0&&(s.available_efforts=i,s.reasoning_effort=this.reasoningEffort??t?.defaultReasoningEffort??null);const n=t?.serviceTiers??[];return n.length>0&&(s.available_service_tiers=n,s.service_tier=this.effectiveServiceTier()??t?.defaultServiceTier??null),s}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");for(const t of this.steerDeferred.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")D(e,"SIGTERM"),await Promise.race([new Promise(s=>{e.once("exit",()=>s(!0))}),new Promise(s=>{setTimeout(()=>s(!1),5e3)})])||D(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 R(e.adapterSessionId);return this.runTurn(e,t).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),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),s=!!(this.process.stdout&&!this.process.stdout.destroyed);return t&&s}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const t=++this.requestId,s={jsonrpc:"2.0",id:t,method:"ping",params:{}};return await new Promise(i=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(t)),i(!1)},e);this.pendingRequests.set(String(t),{resolve:()=>i(!0),reject:()=>i(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(s)}
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=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}`)}setServiceTier(e){const t=e.trim();this.serviceTier=t.toLowerCase()==="default"?void 0:t,this.persistCodexContext(),r.info("codex-adapter",`Service tier set to: ${this.serviceTier??"default"}`)}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=X(G(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?Q(e,[e]):!1}onAgentProfileChanged(){this.identityInjectedThreadId!==null&&(r.info("codex-adapter",`agent profile changed; clearing identity-injected thread record (was ${this.identityInjectedThreadId})`),this.identityInjectedThreadId=null)}async probe(e){const t=this.config.command||"codex",s=await de(t),i=s!==null;let n=null,o;if(i){const I=await ce(t);n=I.version,I.error&&(o=I.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const u=k().baseUrl??null,m=this.currentModelId()||null,g=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},h=await this.probeSessionRecord(),v=j({codexHome:this.codexHome});return{cli:{command:t,installed:i,path:s,version:n,...o?{error:o}:{}},conversation:p,config:{model:m,base_url:u,source:{model:m?"runtime":"unknown",base_url:u?"file":"unknown"}},process:{started:!!this.process,alive:g.alive,busy:g.busy},session:h,lock:{present:v.present,locked:v.locked,stale:v.stale,path:v.path}}}async probeSessionRecord(){const e=this.threadId?ae(this.threadId,this.codexHome):null;if(!e)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const t=await O(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,s){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:"The session has expired and there is no history to compact. Please send a new message and try again."};this.compacting=!0;const i=new Promise(n=>{this.compactionDoneResolver=n});try{return await this.threadCompact(),this.armCompactionFallback(),await i,{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:"The session has expired and there is no history to compact. Please send a new message and try again."};throw n}}case"model":{const i=t.trim();return i?this.getModelOptions().some(o=>o.id===i)?(this.setModel(i),{status:"ok",message:`Model set to ${i}`}):{status:"failed",message:`Unknown model: ${i}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const i=t.trim();if(i){const n=T(i);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${i}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:H}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const i=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(i),{status:"ok",message:`Rolled back ${i} 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 i=le({mode:"codex",projectDir:this.cwd}),n=i.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
3
+ `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${t} action_id=${e.action_id} session_id=${s.session_id??""}`),t){case"set_model":{const i=x(l(s.model_id),l(s.modelId),l(s.value));return i&&this.setModel(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const i=s.mode_id;return i&&this.setMode(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const i=x(l(s.reasoning_effort),l(s.reasoning_eff),l(s.effort));return i&&this.setReasoningEffort(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_service_tier":{const i=x(l(s.service_tier),l(s.serviceTier),l(s.value));return i&&this.setServiceTier(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("service_tier_set",!1)),{handled:!0,kind:"set_service_tier"}}case"set_sandbox_mode":{const i=x(l(s.sandbox_mode),l(s.sandboxMode),l(s.value));return i&&this.setSandboxMode(i),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","The session has expired and there is no history to compact. Please send a new message and try again."),{handled:!0,kind:"thread_compact"};this.compacting=!0;const i=new Promise(n=>{this.compactionDoneResolver=n});await this.threadCompact(),this.armCompactionFallback(),await i,r.info("codex-adapter",`thread_compact done action_id=${e.action_id}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(i){this.finishCompaction("compact-error");const n=i instanceof Error?i.message:String(i);/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","The session has expired and there is no history to compact. Please send a new message and try again.")):(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 i=this.resolvePendingApproval(e);if(!i)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=s.decision||"allow-once",o=xe[n]??"accept";return this.sendApprovalDecision(i.requestId,o),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,approval_command_id:i.approvalCommandId,decision:n}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t}}case"exec_reject":case"file_reject":{const i=this.resolvePendingApproval(e);return i?(this.sendApprovalDecision(i.requestId,"deny"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,approval_command_id:i.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,s,i),{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 i=this.resolvePendingApproval(e);return!i||i.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(i.requestId,"accept"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,decision:"approve"}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t})}case"permission_reject":{const i=this.resolvePendingApproval(e);return!i||i.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(i.requestId,"deny"),this.pendingApprovals.delete(i.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.approvalId,decision:"deny"}),this.resumeAfterApproval(e,s,i),{handled:!0,kind:t})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(i){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,i instanceof Error?i.message:String(i))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const i=Number(s.numTurns??s.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(i),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:i})}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 i=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return i?r.info("codex-adapter",`[rate-limits] responding: primary=${i.primary.usedPercent.toFixed(1)}% window=${i.primary.windowMinutes}min resetsAt=${i.primary.resetsAt} secondary=${i.secondary.usedPercent.toFixed(1)}% window=${i.secondary.windowMinutes}min resetsAt=${i.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:i!==null,cached:!1,sampledAt:o,rateLimits:i,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 s=T(this.collaborationMode)??"default",i=this.currentModelId(),n=this.getModelOption(i),o=this.getModelOptions(),d=H,u=this.reasoningEffort??n?.defaultReasoningEffort??null,m=n?.supportedReasoningEfforts??[],g=this.effectiveServiceTier()??n?.defaultServiceTier??null,p=n?.serviceTiers??[],h=this.isExternalProvider(),v=h?[]:m,I=h?null:u,_=h?[]:p,w=h?null:g,c={outcome:e,session_context:{modelId:i,modeId:s,reasoningEffort:I,serviceTier:w,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:i,mode_id:s,currentModelId:i,currentModeId:s,available_models:o,available_modes:d,available_efforts:v,availableModels:o,availableModes:d,reasoning_effort:I,available_service_tiers:_,service_tier:w,sandbox_mode:this.sandboxMode??null,models:o.map(f=>({modelId:f.id,name:f.displayName})),modes:d.map(f=>({id:f.id,name:f.displayName}))};return this.rateLimitSnapshot&&(c.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,c.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,c.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,c.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),c}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const t=this.getModelOptions();return t.find(s=>s.isDefault)?.id??t[0]?.id??""}getModelOption(e){const t=e.trim().toLowerCase();return this.getModelOptions().find(s=>s.id.trim().toLowerCase()===t)}getModelOptions(){const e=this.config.options??{},t=e.available_models??e.availableModels??e.models,s=Te(t),i=this.codexModelOptions,n=[],o=String(this.model??"").trim(),d=o&&!i.some(p=>p.id.trim().toLowerCase()===o.toLowerCase()),u=i.length>0?i:d?[]:ve;if(o){const p=[...i,...s].find(h=>h.id.trim().toLowerCase()===o.toLowerCase());n.push({id:o,displayName:p?.displayName??y(o),defaultReasoningEffort:p?.defaultReasoningEffort??null,supportedReasoningEfforts:p?.supportedReasoningEfforts??[],defaultServiceTier:p?.defaultServiceTier??null,serviceTiers:p?.serviceTiers??[],isDefault:p?.isDefault??!1})}n.push(...s),n.push(...u);const m=[],g=new Set;for(const p of n){const h=p.id.trim(),v=h.toLowerCase();!h||g.has(v)||(g.add(v),m.push({id:h,displayName:p.displayName.trim()||h,defaultReasoningEffort:p.defaultReasoningEffort,supportedReasoningEfforts:[...p.supportedReasoningEfforts],defaultServiceTier:p.defaultServiceTier,serviceTiers:[...p.serviceTiers],isDefault:p.isDefault}))}return m}async refreshCodexModelOptions(){if(!this.initialized||!this.alive||!this.process?.stdin||Date.now()-this.codexModelOptionsCachedAt<M.MODEL_CACHE_TTL_MS)return;const e=k(this.codexHome);if(e.baseUrl&&z(e.baseUrl)){try{const t=await ye(e.baseUrl,e.apiKey);if(t.length>0){this.codexModelOptions=W(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 s;do{const i=await this.sendRequest("model/list",{cursor:s??null,includeHidden:!1,limit:100},15e3),n=Ce(i);t.push(...n.models),s=n.nextCursor}while(s);t.length>0&&(this.codexModelOptions=W(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=[];steerDeferred=[];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=e.content;if(this.currentTurnId&&t.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(t).catch(s=>{const i=s instanceof Error?s.message:String(s);if(r.info("codex-adapter",`Steer failed, falling through: ${i}`),!this.currentTurnId){this.startNewTurn(e,t);return}if(Ae(i)){r.info("codex-adapter","Unsteerable turn detected, clearing active state and starting new turn"),this.clearActive(),this.startNewTurn(e,t);return}r.info("codex-adapter",`Steer failed but turn still active; deferring event ${e.event_id} for replay after turn ends`),this.steerDeferred.push(e)});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 s=this.threadId??"",i=new R(s),n={adapterSessionId:s,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,i,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 s=U(this.config.command||"codex",t),i=Ie(this.config.args);this.sandboxMode&&i.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${s} ${i.join(" ")}`);try{if(!(await O(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=j({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=Z(s,i,{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"),J({input:this.process.stdout}).on("line",t=>{if(!t.trim())return;let s;try{s=JSON.parse(t)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${t.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),s.id!=null){const i=String(s.id),n=this.pendingRequests.get(i);if(n){clearTimeout(n.timer),this.pendingRequests.delete(i),n.resolve(s);return}}this.handleNotification(s)})}handleNotification(e){const t=e.method,s=e.params??{};switch(this.captureContextWindowFromPayload(t,s),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&t&&t.startsWith("item/")&&!t.endsWith("/requestApproval")&&this.startComposing(),t){case"item/started":{const i=s.item,n=i?.id,o=i?.type,d=i?.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),ie(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const i=s.delta,n=s.itemId;i&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const i=s.item,n=i?.id,o=i?.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=s.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(s);break}case"error":{const i=s.message??JSON.stringify(s);r.error("codex-adapter",`Codex error: ${i}`),i&&(this.turnError=i),this.activeEventId&&this.callbacks.sendRunError(this.activeEventId,this.activeSessionId??"",i);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(s),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 i=s.threadId,n=s.tokenUsage,o=n?.last;(!i||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!i)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(s)}`),i&&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=${i}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${i}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.publishContextWindowSnapshot());break}default:break}}captureContextWindowFromPayload(e,t){const s=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],i=this.findNumericField(t,s);if(i==null||i<=0){const n=this.probeContextWindowFields(t,s);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(i)}`);return}this.currentModelContextWindow!==i&&(this.currentModelContextWindow=i,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${i}`),this.publishContextWindowSnapshot())}probeContextWindowFields(e,t){const s=[],i=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const u of o)n(u);return}const d=o;for(const u of t)Object.prototype.hasOwnProperty.call(d,u)&&(s.push(u),i.push(String(d[u])));for(const u of Object.values(d))n(u)};return n(e),{foundAnyCandidateField:s.length>0,matchedFields:s,rawValues:i}}findNumericField(e,t){if(!e||typeof e!="object")return null;const s=e;for(const i of t){const n=s[i];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 i of Object.values(s)){if(Array.isArray(i)){for(const o of i){const d=this.findNumericField(o,t);if(d!=null)return d}continue}const n=this.findNumericField(i,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,s=t?t.inputTokens+t.outputTokens:null,i=s==null?null:Math.max(0,e-s),n=s==null?null:Math.min(100,s/e*100),o=s==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(s)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:s,remainingTokens:i,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<q||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("codex-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${q}%, 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 s=e.rateLimitsByLimitId?.codex??e.rateLimits??e,i=s.primary,n=s.secondary,o=s.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const f=Number(c);if(Number.isFinite(f))return f}return 0},u=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,m=c=>{if(c==null)return null;if(typeof c=="string"){const f=c.trim();if(!f)return null;const b=Number(f);return Number.isFinite(b)&&b>1e9?new Date(b*1e3).toISOString():f}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const f=c>1e12?c/1e3:c;return new Date(f*1e3).toISOString()}return null},g=c=>{const f=c.resets_in_seconds??c.resetsInSeconds,b=typeof f=="number"||typeof f=="string"?Number(f):NaN;return!Number.isFinite(b)||b<=0?null:new Date(Date.now()+b*1e3).toISOString()},p=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:m(c.resets_at??c.resetsAt)??g(c)}),h=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),v=()=>{const c=[];if(i&&c.push(i),n&&c.push(n),Array.isArray(s.windows))for(const f of s.windows)f&&typeof f=="object"&&c.push(f);return c.map(p).filter(f=>f.windowMinutes>0)},I=(c,f,b)=>{const P=c.filter(S=>b(S.windowMinutes));return P.length===0?null:P.reduce((S,$)=>Math.abs($.windowMinutes-f)<Math.abs(S.windowMinutes-f)?$:S)},_=v(),w=_.length>0?{primary:I(_,300,c=>c<1440)??h(),secondary:I(_,10080,c=>c>=1440)??h()}:i||n?{primary:i?p(i):h(),secondary:n?p(n):h()}:null;return w?{primary:w.primary,secondary:w.secondary,credits:o?{hasCredits:u(o.has_credits??o.hasCredits),unlimited:u(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,t){const s=e.id;if(s==null)return;const i=e.method,n=`${s}`.trim(),o=`codex_${B(this.activeSessionId??"")}_${B(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(s,"accept");return}this.activeEventId&&this.emitCodexEvent(i,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:s,kind:t}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${t})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const t=e.params??{},s=x(t.approval_id,t.approvalId);if(s)return this.pendingApprovals.get(s)??null;const i=x(t.approval_command_id,t.approvalCommandId);if(i){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===i)return n}return null}sendApprovalDecision(e,t){if(e==null||!this.process?.stdin)return;const s={jsonrpc:"2.0",id:e,result:{decision:t}};this.process.stdin.write(`${JSON.stringify(s)}
4
+ `)}armCompactionFallback(){this.compacting&&(this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{r.warn("codex-adapter","Compaction fallback fired: turn/completed not received within timeout, treating as stuck and releasing deferred events"),this.currentTurnId=null,this.finishCompaction("fallback-timeout")},ge),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 i of t)this.deliverInboundEvent(i);const s=this.compactionDoneResolver;this.compactionDoneResolver=null,s?.()}handleTurnCompleted(e){if(this.currentTurnId=null,this.stopComposing(),this.activeEventId){const t=this.activeEventId,s={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()},i=this.turnError;this.turnError=null;const n=!!i;this.callbacks.sendCodexEventReliable?(this.clearActive({runPendingAutoCompact:!1}),this.callbacks.sendCodexEventReliable(s).then(()=>{this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??void 0:void 0),this.tryRunPendingAutoCompact()}).catch(o=>{r.error("codex-adapter",`sendCodexEventReliable failed event=${t}: ${o}`),this.callbacks.sendCodexEvent(s),this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??void 0:void 0),this.tryRunPendingAutoCompact()})):(this.callbacks.sendCodexEvent(s),this.callbacks.sendEventResult(t,n?"failed":"responded",n?i??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,s=3e4){return new Promise((i,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??{}},u=setTimeout(()=>{this.pendingRequests.delete(String(o)),n(new Error(`Request timeout: ${e}`))},s);this.pendingRequests.set(String(o),{resolve:g=>{g.error?n(new Error(`JSON-RPC error: ${g.error.message}`)):i(g.result)},reject:n,timer:u});const m=JSON.stringify(d);this.process.stdin.write(`${m}
5
5
  `)})}sendNotification(e,t){if(!this.process?.stdin)return;const s={jsonrpc:"2.0",method:e,params:t??{}};this.process.stdin.write(`${JSON.stringify(s)}
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,s,i){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[],o=this.callbacks.getAgentProfile?.(),d=o?.agentName?.trim()??"",h=o?.introduction?.trim()??"";if((d||h)&&this.identityInjectedThreadId!==this.threadId){const p=["[system-identity]"];d&&p.push(`Your name is "${d}".`),h&&p.push(`Your self-introduction: ${h}`),p.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),p.push("[/system-identity]"),n.push({type:"text",text:p.join(`
7
- `),text_elements:[]}),this.identityInjectedThreadId=this.threadId,r.info("codex-adapter",`Injected agent identity for thread=${this.threadId} name="${d}"`)}if(this.needsHistoryInjection&&i){this.needsHistoryInjection=!1;const p=await this.loadHistoryForInjection(i);for(const g of p)n.push(g);p.length>0&&r.info("codex-adapter",`Injected ${p.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const p=e.contextMessages.map(g=>`[${g.senderId??"unknown"}]: ${g.content}`).join(`
6
+ `)}async initializeHandshake(){if(this.initialized)return;const e=te(),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,s,i){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[],o=this.callbacks.getAgentProfile?.(),d=o?.agentName?.trim()??"",u=o?.introduction?.trim()??"";if((d||u)&&this.identityInjectedThreadId!==this.threadId){const h=["[system-identity]"];d&&h.push(`Your name is "${d}".`),u&&h.push(`Your self-introduction: ${u}`),h.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),h.push("[/system-identity]"),n.push({type:"text",text:h.join(`
7
+ `),text_elements:[]}),this.identityInjectedThreadId=this.threadId,r.info("codex-adapter",`Injected agent identity for thread=${this.threadId} name="${d}"`)}if(this.needsHistoryInjection&&i){this.needsHistoryInjection=!1;const h=await this.loadHistoryForInjection(i);for(const v of h)n.push(v);h.length>0&&r.info("codex-adapter",`Injected ${h.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const h=e.contextMessages.map(v=>`[${v.senderId??"unknown"}]: ${v.content}`).join(`
8
8
  `);n.push({type:"text",text:`Conversation context:
9
- ${p}
9
+ ${h}
10
10
 
11
- Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const f={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},m=we(this.collaborationMode,this.model);m&&(f.collaborationMode=m),this.reasoningEffort&&(f.effort=this.reasoningEffort);const u=this.effectiveServiceTier();u&&(f.serviceTier=u),await this.sendRequest("turn/start",f),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.effectiveServiceTier()?{serviceTier:this.effectiveServiceTier()}:{}});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}:{},...this.effectiveServiceTier()?{serviceTier:this.effectiveServiceTier()}:{}}),t=Ie(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=be(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:T(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,serviceTier:this.serviceTier,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const t=this.callbacks.getConversationLog();if(!t)return[];try{const s=await t.readHistory(e,pe),i=[];for(const n of s){const o=(n.content??"").slice(0,fe);n.direction==="inbound"?i.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&i.push({type:"assistant",text:o,text_elements:[]})}return i}catch(s){return r.error("codex-adapter",`Failed to load history: ${s}`),[]}}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,s){if(!this.threadId)return;const i=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=x(t.session_id,t.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new E(o),h={adapterSessionId:o,text:""};this.activeEventId=i,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,i,n).catch(f=>{r.error("codex-adapter",`Post-approval turn failed: ${f}`),this.callbacks.sendEventResult(i,"failed",f instanceof Error?f.message:String(f)),this.clearActive()}),this.resetIdleTimer(i)}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),this.replaySteerDeferred(),e?.runPendingAutoCompact!==!1&&this.tryRunPendingAutoCompact()}replaySteerDeferred(){if(this.steerDeferred.length===0)return;const e=this.steerDeferred.splice(0);r.info("codex-adapter",`Replaying ${e.length} steer-deferred event(s) after turn ended`);for(const t of e)this.deliverInboundEvent(t)}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 s=!1;try{s=await this.ping(ue)}catch{s=!1}if(this.activeEventId!==e)return;if(!s){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 i=this.lastIdleCheckAt,n=this.lastProgressAt>i,o=n?!1:await this.checkSessionActivitySince(i);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>=O?(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}/${O}): ${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 ve={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function xe(a){return a&&a.length>0?[...a]:["app-server"]}function c(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function Ie(a){if(!a||typeof a!="object")return null;const e=a,t=e.thread&&typeof e.thread=="object"?e.thread:void 0;return x(c(e.threadId),c(e.threadID),c(t?.id))??null}function be(a){return!a||typeof a!="object"?null:c(a.model)??null}function T(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function we(a,e){const t=T(a),s=c(e);if(!(!t||!s))return{mode:t,settings:{model:s}}}function _e(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const o=t.trim();o&&e.push(Ce(o,y(o)));continue}if(!t||typeof t!="object")continue;const s=t,i=x(c(s.id),c(s.model_id),c(s.modelId),c(s.value));if(!i)continue;const n=x(c(s.displayName),c(s.display_name),c(s.name),c(s.label))??y(i);e.push({id:i,displayName:n,defaultReasoningEffort:c(s.defaultReasoningEffort)??null,supportedReasoningEfforts:k(s.supportedReasoningEfforts),defaultServiceTier:c(s.defaultServiceTier)??null,serviceTiers:A(s.serviceTiers),isDefault:s.isDefault===!0})}return e}function q(a){const e=a.toLowerCase().replace(/\/+$/,"");return!(e==="https://api.openai.com"||e==="https://api.openai.com/v1"||e.endsWith(".openai.com"))}async function Te(a,e){const s=`${a.replace(/\/+$/,"")}/models`,i={Accept:"application/json"};e&&(i.Authorization=`Bearer ${e}`);const n=new AbortController,o=setTimeout(()=>n.abort(),1e4);try{const d=await fetch(s,{headers:i,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:[],s=[];for(const i of t){if(!i||typeof i!="object")continue;const n=i,o=x(c(n.id),c(n.model));if(!o)continue;const d=x(c(n.displayName),c(n.display_name),c(n.name))??y(o);s.push({id:o,displayName:d,defaultReasoningEffort:c(n.defaultReasoningEffort)??null,supportedReasoningEfforts:k(n.supportedReasoningEfforts),defaultServiceTier:c(n.defaultServiceTier)??null,serviceTiers:A(n.serviceTiers),isDefault:n.isDefault===!0})}return s}function Se(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,t=Array.isArray(e.data)?e.data:[],s=[];for(const i of t){if(!i||typeof i!="object")continue;const n=i;if(n.hidden===!0)continue;const o=x(c(n.id),c(n.model));if(!o)continue;const d=x(c(n.displayName),c(n.display_name),c(n.name))??y(o);s.push({id:o,displayName:d,defaultReasoningEffort:c(n.defaultReasoningEffort)??null,supportedReasoningEfforts:k(n.supportedReasoningEfforts),defaultServiceTier:c(n.defaultServiceTier)??null,serviceTiers:A(n.serviceTiers),isDefault:n.isDefault===!0})}return{models:s,nextCursor:c(e.nextCursor)??null}}function H(a){const e=[],t=new Set;for(const s of a){const i=s.id.trim(),n=i.toLowerCase();!i||t.has(n)||(t.add(n),e.push({id:i,displayName:s.displayName.trim()||i,defaultReasoningEffort:s.defaultReasoningEffort,supportedReasoningEfforts:[...s.supportedReasoningEfforts],defaultServiceTier:s.defaultServiceTier,serviceTiers:[...s.serviceTiers],isDefault:s.isDefault}))}return e}function Ce(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],defaultServiceTier:null,serviceTiers:[],isDefault:!1}}function k(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const i=t.trim();i&&e.push(i);continue}if(!t||typeof t!="object")continue;const s=c(t.reasoningEffort);s&&e.push(s)}return e}function A(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const n=t.trim();n&&e.push({id:n,displayName:n,description:null});continue}if(!t||typeof t!="object")continue;const s=t,i=c(s.id);i&&e.push({id:i,displayName:x(c(s.name),c(s.displayName),c(s.display_name))??i,description:c(s.description)??null})}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 x(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function U(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class E extends P{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 ke(a){return["cannot steer a compact turn","turn not found","turn already completed","no active turn"].some(t=>a.includes(t))}export{M as CodexAdapter};
11
+ Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const m={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},g=_e(this.collaborationMode,this.model);g&&(m.collaborationMode=g),this.reasoningEffort&&(m.effort=this.reasoningEffort);const p=this.effectiveServiceTier();p&&(m.serviceTier=p),await this.sendRequest("turn/start",m),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.effectiveServiceTier()?{serviceTier:this.effectiveServiceTier()}:{}});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}:{},...this.effectiveServiceTier()?{serviceTier:this.effectiveServiceTier()}:{}}),t=be(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=we(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:T(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,serviceTier:this.serviceTier,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const t=this.callbacks.getConversationLog();if(!t)return[];try{const s=await t.readHistory(e,fe),i=[];for(const n of s){const o=(n.content??"").slice(0,me);n.direction==="inbound"?i.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&i.push({type:"assistant",text:o,text_elements:[]})}return i}catch(s){return r.error("codex-adapter",`Failed to load history: ${s}`),[]}}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,s){if(!this.threadId)return;const i=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=x(t.session_id,t.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new R(o),u={adapterSessionId:o,text:""};this.activeEventId=i,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(u,d,i,n).catch(m=>{r.error("codex-adapter",`Post-approval turn failed: ${m}`),this.callbacks.sendEventResult(i,"failed",m instanceof Error?m.message:String(m)),this.clearActive()}),this.resetIdleTimer(i)}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),this.replaySteerDeferred(),e?.runPendingAutoCompact!==!1&&this.tryRunPendingAutoCompact()}replaySteerDeferred(){if(this.steerDeferred.length===0)return;const e=this.steerDeferred.splice(0);r.info("codex-adapter",`Replaying ${e.length} steer-deferred event(s) after turn ended`);for(const t of e)this.deliverInboundEvent(t)}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?ue:he;this.idleTimer=setTimeout(()=>{this.onIdleTimeout(e,t)},t)}async onIdleTimeout(e,t){if(this.activeEventId!==e)return;let s=!1;try{s=await this.ping(pe)}catch{s=!1}if(this.activeEventId!==e)return;if(!s){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 i=this.lastIdleCheckAt,n=this.lastProgressAt>i,o=n?!1:await this.checkSessionActivitySince(i);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>=F?(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}/${F}): ${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 re(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 xe={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function Ie(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 be(a){if(!a||typeof a!="object")return null;const e=a,t=e.thread&&typeof e.thread=="object"?e.thread:void 0;return x(l(e.threadId),l(e.threadID),l(t?.id))??null}function we(a){return!a||typeof a!="object"?null:l(a.model)??null}function T(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function _e(a,e){const t=T(a),s=l(e);if(!(!t||!s))return{mode:t,settings:{model:s}}}function Te(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 s=t,i=x(l(s.id),l(s.model_id),l(s.modelId),l(s.value));if(!i)continue;const n=x(l(s.displayName),l(s.display_name),l(s.name),l(s.label))??y(i);e.push({id:i,displayName:n,defaultReasoningEffort:l(s.defaultReasoningEffort)??null,supportedReasoningEfforts:A(s.supportedReasoningEfforts),defaultServiceTier:l(s.defaultServiceTier)??null,serviceTiers:E(s.serviceTiers),isDefault:s.isDefault===!0})}return e}function z(a){const e=a.toLowerCase().replace(/\/+$/,"");return!(e==="https://api.openai.com"||e==="https://api.openai.com/v1"||e.endsWith(".openai.com"))}async function ye(a,e){const s=`${a.replace(/\/+$/,"")}/models`,i={Accept:"application/json"};e&&(i.Authorization=`Bearer ${e}`);const n=new AbortController,o=setTimeout(()=>n.abort(),1e4);try{const d=await fetch(s,{headers:i,signal:n.signal});if(!d.ok)return[];const u=await d.json();return Se(u)}finally{clearTimeout(o)}}function Se(a){if(!a||typeof a!="object")return[];const e=a,t=Array.isArray(e.data)?e.data:[],s=[];for(const i of t){if(!i||typeof i!="object")continue;const n=i,o=x(l(n.id),l(n.model));if(!o)continue;const d=x(l(n.displayName),l(n.display_name),l(n.name))??y(o);s.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:A(n.supportedReasoningEfforts),defaultServiceTier:l(n.defaultServiceTier)??null,serviceTiers:E(n.serviceTiers),isDefault:n.isDefault===!0})}return s}function Ce(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,t=Array.isArray(e.data)?e.data:[],s=[];for(const i of t){if(!i||typeof i!="object")continue;const n=i;if(n.hidden===!0)continue;const o=x(l(n.id),l(n.model));if(!o)continue;const d=x(l(n.displayName),l(n.display_name),l(n.name))??y(o);s.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:A(n.supportedReasoningEfforts),defaultServiceTier:l(n.defaultServiceTier)??null,serviceTiers:E(n.serviceTiers),isDefault:n.isDefault===!0})}return{models:s,nextCursor:l(e.nextCursor)??null}}function W(a){const e=[],t=new Set;for(const s of a){const i=s.id.trim(),n=i.toLowerCase();!i||t.has(n)||(t.add(n),e.push({id:i,displayName:s.displayName.trim()||i,defaultReasoningEffort:s.defaultReasoningEffort,supportedReasoningEfforts:[...s.supportedReasoningEfforts],defaultServiceTier:s.defaultServiceTier,serviceTiers:[...s.serviceTiers],isDefault:s.isDefault}))}return e}function ke(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],defaultServiceTier:null,serviceTiers:[],isDefault:!1}}function A(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const i=t.trim();i&&e.push(i);continue}if(!t||typeof t!="object")continue;const s=l(t.reasoningEffort);s&&e.push(s)}return e}function E(a){if(!Array.isArray(a))return[];const e=[];for(const t of a){if(typeof t=="string"){const n=t.trim();n&&e.push({id:n,displayName:n,description:null});continue}if(!t||typeof t!="object")continue;const s=t,i=l(s.id);i&&e.push({id:i,displayName:x(l(s.name),l(s.displayName),l(s.display_name))??i,description:l(s.description)??null})}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 x(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function B(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class R extends N{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 Ae(a){return["cannot steer a compact turn","turn not found","turn already completed","no active turn"].some(t=>a.includes(t))}export{M as CodexAdapter};