thinkpool-pair 0.7.420 → 0.7.421

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:readline";import{codexCatastrophicHookCliArgs as i}from"./catastrophic-command-guard.mjs";export const CODEX_APP_SERVER_ENV="TP_CODEX_APP_SERVER";export const MIN_CODEX_APP_SERVER_VERSION=Object.freeze([0,144,1]);const s=(e,t="unknown")=>{const r=e instanceof Error?e:new Error(String(e||"Codex App Server transport failed"));return r.delivery=t,r};export function codexAppServerGate({env:t=process.env,execFile:r=e}={}){const i=String(t?.[CODEX_APP_SERVER_ENV]||"").trim().toLowerCase();if(["0","false","off"].includes(i))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} disables App Server`,version:null};if(i&&!["1","true","on"].includes(i))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} has an invalid value`,version:null};try{const e=r("codex",["--version"],{encoding:"utf8",env:t}),i=(e=>{const t=String(e||"").match(/(?:codex-cli\s+)?(\d+)\.(\d+)\.(\d+)/i);return t?t.slice(1,4).map(Number):null})(e);if(!i)return{enabled:!1,reason:`unrecognized Codex version: ${String(e).trim()}`,version:null};const s=i.join(".");return((e,t)=>{for(let r=0;r<3;r++){if(e[r]>t[r])return!0;if(e[r]<t[r])return!1}return!0})(i,MIN_CODEX_APP_SERVER_VERSION)?{enabled:!0,reason:null,version:s}:{enabled:!1,reason:`Codex ${s} is older than ${MIN_CODEX_APP_SERVER_VERSION.join(".")}`,version:s}}catch(e){return{enabled:!1,reason:`could not read Codex version: ${e?.message||e}`,version:null}}}export function buildCodexAppServerArgs({providerConfig:e,mcpUrl:t,writableDirs:r=[]}={}){const s=[...i(),"app-server","--stdio"];return e&&s.push("-c",e),t&&(s.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(t)}`),s.push("-c","mcp_servers.thinkpool.required=true")),r.length&&s.push("-c",`sandbox_workspace_write.writable_roots=${JSON.stringify(r)}`),s}const o=(e,t=-32e3)=>({code:t,message:String(e||"App Server request failed")}),n=(e,t=[])=>[{type:"text",text:String(e??"")},...t.filter(Boolean).map(e=>({type:"localImage",path:String(e)}))];export class CodexAppServerClient{constructor({cwd:e,env:r,args:i=["app-server","--stdio"],spawnImpl:s=t,onNotification:o,onServerRequest:n,onClose:a,initializeTimeoutMs:d=3e3,threadTimeoutMs:c=5e3,turnStartTimeoutMs:l=5e3,controlTimeoutMs:h=5e3}={}){this.cwd=e,this.env=r,this.args=i,this.spawnImpl=s,this.onNotification=o,this.onServerRequest=n,this.onClose=a,this.initializeTimeoutMs=d,this.threadTimeoutMs=c,this.turnStartTimeoutMs=l,this.controlTimeoutMs=h,this.child=null,this.nextId=1,this.pending=new Map,this.turnWaiters=new Map,this.completedTurns=new Map,this.mcpStartup=new Map,this.mcpWaiters=new Map,this.closedError=null,this.stderrTail=""}get alive(){return!!this.child&&!this.closedError}async start(){if(this.child)return;const e=this.spawnImpl("codex",this.args,{cwd:this.cwd,env:this.env,stdio:["pipe","pipe","pipe"]});this.child=e,r.createInterface({input:e.stdout,crlfDelay:1/0}).on("line",e=>this._onLine(e)),e.stderr?.on?.("data",e=>{this.stderrTail+=String(e),this.stderrTail.length>1200&&(this.stderrTail=this.stderrTail.slice(-1200))}),e.stdin?.on?.("error",e=>this._close(e)),e.on("error",e=>this._close(e)),e.on("close",e=>this._close(new Error(`codex app-server exited ${e??"unknown"}${this.stderrTail?`: ${this.stderrTail.trim().slice(-300)}`:""}`))),await this.request("initialize",{clientInfo:{name:"thinkpool-pair",title:"thinkpool Code",version:"1"},capabilities:{experimentalApi:!0}},this.initializeTimeoutMs),this.notify("initialized")}_write(e){if(!this.child?.stdin||this.closedError)throw this.closedError||new Error("codex app-server is not running");this.child.stdin.write(`${JSON.stringify(e)}\n`)}request(e,t={},r=0){const i=this.nextId++;return new Promise((o,n)=>{let a=null;r>0&&(a=setTimeout(()=>{this.pending.delete(i),n(s(`${e} timed out after ${r}ms`,"unknown"))},r)),this.pending.set(i,{resolve:o,reject:n,timer:a,method:e});try{this._write({id:i,method:e,params:t})}catch(e){this.pending.delete(i),a&&clearTimeout(a),n(s(e,"not_sent"))}})}notify(e,t){this._write(void 0===t?{method:e}:{method:e,params:t})}respond(e,t){this._write({id:e,result:t})}respondError(e,t){this._write({id:e,error:o(t?.message||t)})}_onLine(e){if(!String(e).trim())return;let t;try{t=JSON.parse(e)}catch{return}if(null!=t.id&&("result"in t||"error"in t)&&!t.method){const e=this.pending.get(t.id);if(!e)return;if(this.pending.delete(t.id),e.timer&&clearTimeout(e.timer),t.error){const r=s(t.error.message||`${e.method} failed`,"rejected");r.code=t.error.code,r.data=t.error.data,e.reject(r)}else e.resolve(t.result);return}if(null!=t.id&&t.method)Promise.resolve().then(()=>{if(!this.onServerRequest)throw new Error(`Unsupported Codex App Server request: ${t.method}`);return this.onServerRequest(t.method,t.params||{},t.id)}).then(e=>{try{this.respond(t.id,e??{})}catch{}}).catch(e=>{try{this.respondError(t.id,e)}catch{}});else if(t.method){if("turn/completed"===t.method){const e=t.params?.turn,r=e?.id;if(r){const e=this.turnWaiters.get(r);if(e)this.turnWaiters.delete(r),e.resolve(t.params);else for(this.completedTurns.set(r,t.params);this.completedTurns.size>32;)this.completedTurns.delete(this.completedTurns.keys().next().value)}}if("mcpServer/startupStatus/updated"===t.method){const e=t.params||{},r=String(e.threadId||""),i=String(e.name||"");if(r&&i){const t=`${r}\0${i}`;this.mcpStartup.set(t,e);const s=this.mcpWaiters.get(t);s&&"ready"===e.status?(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.resolve(e)):s&&("failed"===e.status||e.error||e.failureReason)&&(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.reject(new Error(`Codex MCP server ${i} failed to start: ${e.error||e.failureReason||e.status}`)))}}try{this.onNotification?.(t.method,t.params||{})}catch{}}}_close(e){if(!this.closedError){this.closedError=s(e instanceof Error?e:String(e||"codex app-server closed"),"unknown"),this.child=null;for(const{reject:e,timer:t}of this.pending.values())t&&clearTimeout(t),e(this.closedError);this.pending.clear();for(const{reject:e}of this.turnWaiters.values())e(this.closedError);this.turnWaiters.clear(),this.completedTurns.clear();for(const{reject:e,timer:t}of this.mcpWaiters.values())t&&clearTimeout(t),e(this.closedError);this.mcpWaiters.clear(),this.mcpStartup.clear();try{this.onClose?.(this.closedError)}catch{}}}async startThread({threadId:e,cwd:t,model:r,sandbox:i,approvalPolicy:s,config:o}={}){const n={cwd:t,model:r,sandbox:i,approvalPolicy:s,...o?{config:o}:{}},a=e?await this.request("thread/resume",{...n,threadId:e},this.threadTimeoutMs):await this.request("thread/start",n,this.threadTimeoutMs),d=a?.thread?.id||a?.threadId||e;if(!d)throw new Error("Codex App Server did not return a thread id");return d}waitForMcpServer({threadId:e,name:t,timeoutMs:r=this.threadTimeoutMs}={}){const i=String(e||""),s=String(t||"");if(!i||!s)return Promise.reject(new Error("Codex MCP readiness needs a thread id and server name"));const o=`${i}\0${s}`,n=this.mcpStartup.get(o);if("ready"===n?.status)return Promise.resolve(n);if(n&&("failed"===n.status||n.error||n.failureReason))return Promise.reject(new Error(`Codex MCP server ${s} failed to start: ${n.error||n.failureReason||n.status}`));if(this.mcpWaiters.has(o))return this.mcpWaiters.get(o).promise;let a,d,c=null;const l=new Promise((e,t)=>{a=e,d=t,r>0&&(c=setTimeout(()=>{this.mcpWaiters.delete(o),t(new Error(`Codex MCP server ${s} did not become ready within ${r}ms`))},r))});return this.mcpWaiters.set(o,{promise:l,resolve:a,reject:d,timer:c}),l}async startTurn({threadId:e,input:t,images:r,model:i,effort:s,approvalPolicy:o,collaborationMode:a}={}){const d=await this.request("turn/start",{threadId:e,input:n(t,r),...i?{model:i}:{},...s?{effort:s}:{},...o?{approvalPolicy:o}:{},...a?{collaborationMode:a}:{}},this.turnStartTimeoutMs),c=d?.turn?.id||d?.turnId;if(!c)throw new Error("Codex App Server did not return a turn id");return c}waitForTurn(e){if(this.completedTurns.has(e)){const t=this.completedTurns.get(e);return this.completedTurns.delete(e),Promise.resolve(t)}return this.turnWaiters.has(e)?Promise.reject(new Error(`Already waiting for Codex turn ${e}`)):new Promise((t,r)=>this.turnWaiters.set(e,{resolve:t,reject:r}))}async steer({threadId:e,turnId:t,input:r,images:i}={}){return this.request("turn/steer",{threadId:e,expectedTurnId:t,input:n(r,i)},this.controlTimeoutMs)}interrupt({threadId:e,turnId:t}={}){return this.request("turn/interrupt",{threadId:e,turnId:t},this.controlTimeoutMs)}compact({threadId:e}={}){return this.request("thread/compact/start",{threadId:e},this.controlTimeoutMs)}accountUsage(){return this.request("account/usage/read",null,this.controlTimeoutMs)}accountRateLimits(){return this.request("account/rateLimits/read",null,this.controlTimeoutMs)}startReview({threadId:e,target:t}={}){return this.request("review/start",{threadId:e,target:t||{type:"uncommittedChanges"},delivery:"inline"},this.turnStartTimeoutMs)}end(){const e=this.child;if(this._close(new Error("codex app-server ended")),e)try{e.kill("SIGTERM")}catch{}}}export function createCodexAppServer(e){return new CodexAppServerClient(e)}
1
+ import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:readline";import{codexCatastrophicHookCliArgs as i}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as s}from"./codex-host-heal.mjs";export const CODEX_APP_SERVER_ENV="TP_CODEX_APP_SERVER";export const MIN_CODEX_APP_SERVER_VERSION=Object.freeze([0,144,1]);const o=(e,t="unknown")=>{const r=e instanceof Error?e:new Error(String(e||"Codex App Server transport failed"));return r.delivery=t,r};export function codexAppServerGate({env:t=process.env,execFile:r=e}={}){const i=String(t?.[CODEX_APP_SERVER_ENV]||"").trim().toLowerCase();if(["0","false","off"].includes(i))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} disables App Server`,version:null};if(i&&!["1","true","on"].includes(i))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} has an invalid value`,version:null};try{const e=r("codex",["--version"],{encoding:"utf8",env:t}),i=(e=>{const t=String(e||"").match(/(?:codex-cli\s+)?(\d+)\.(\d+)\.(\d+)/i);return t?t.slice(1,4).map(Number):null})(e);if(!i)return{enabled:!1,reason:`unrecognized Codex version: ${String(e).trim()}`,version:null};const s=i.join(".");return((e,t)=>{for(let r=0;r<3;r++){if(e[r]>t[r])return!0;if(e[r]<t[r])return!1}return!0})(i,MIN_CODEX_APP_SERVER_VERSION)?{enabled:!0,reason:null,version:s}:{enabled:!1,reason:`Codex ${s} is older than ${MIN_CODEX_APP_SERVER_VERSION.join(".")}`,version:s}}catch(e){return{enabled:!1,reason:`could not read Codex version: ${e?.message||e}`,version:null}}}export function buildCodexAppServerArgs({providerConfig:e,mcpUrl:t,writableDirs:r=[]}={}){const s=[...i(),"app-server","--stdio"];return e&&s.push("-c",e),t&&(s.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(t)}`),s.push("-c","mcp_servers.thinkpool.required=true")),r.length&&s.push("-c",`sandbox_workspace_write.writable_roots=${JSON.stringify(r)}`),s}const n=(e,t=-32e3)=>({code:t,message:String(e||"App Server request failed")}),a=(e,t=[])=>[{type:"text",text:String(e??"")},...t.filter(Boolean).map(e=>({type:"localImage",path:String(e)}))];export class CodexAppServerClient{constructor({cwd:e,env:r,args:i=["app-server","--stdio"],spawnImpl:s=t,onNotification:o,onServerRequest:n,onClose:a,initializeTimeoutMs:d=3e3,threadTimeoutMs:c=5e3,turnStartTimeoutMs:l=5e3,controlTimeoutMs:h=5e3,codexHost:u={}}={}){this.cwd=e,this.env=r,this.args=i,this.spawnImpl=s,this.onNotification=o,this.onServerRequest=n,this.onClose=a,this.codexHost=u,this.initializeTimeoutMs=d,this.threadTimeoutMs=c,this.turnStartTimeoutMs=l,this.controlTimeoutMs=h,this.child=null,this.nextId=1,this.pending=new Map,this.turnWaiters=new Map,this.completedTurns=new Map,this.mcpStartup=new Map,this.mcpWaiters=new Map,this.closedError=null,this.stderrTail="",this.starting=!1}get alive(){return!!this.child&&!this.closedError}async start(){if(!this.child){this.starting=!0;try{await s(async()=>{this.closedError=null,this.stderrTail="";const e=this.spawnImpl("codex",this.args,{cwd:this.cwd,env:this.env,stdio:["pipe","pipe","pipe"]});this.child=e,r.createInterface({input:e.stdout,crlfDelay:1/0}).on("line",e=>this._onLine(e)),e.stderr?.on?.("data",t=>{this.child===e&&(this.stderrTail+=String(t),this.stderrTail.length>1200&&(this.stderrTail=this.stderrTail.slice(-1200)))}),e.stdin?.on?.("error",t=>this._close(t,e)),e.on("error",t=>this._close(t,e)),e.on("close",t=>this._close(new Error(`codex app-server exited ${t??"unknown"}${this.stderrTail?`: ${this.stderrTail.trim().slice(-300)}`:""}`),e));try{await this.request("initialize",{clientInfo:{name:"thinkpool-pair",title:"thinkpool Code",version:"1"},capabilities:{experimentalApi:!0}},this.initializeTimeoutMs),this.notify("initialized")}catch(t){if(this.child===e){try{e.kill("SIGTERM")}catch{}this._close(t,e)}throw t}},this.codexHost)}finally{this.starting=!1}}}_write(e){if(!this.child?.stdin||this.closedError)throw this.closedError||new Error("codex app-server is not running");this.child.stdin.write(`${JSON.stringify(e)}\n`)}request(e,t={},r=0){const i=this.nextId++;return new Promise((s,n)=>{let a=null;r>0&&(a=setTimeout(()=>{this.pending.delete(i),n(o(`${e} timed out after ${r}ms`,"unknown"))},r)),this.pending.set(i,{resolve:s,reject:n,timer:a,method:e});try{this._write({id:i,method:e,params:t})}catch(e){this.pending.delete(i),a&&clearTimeout(a),n(o(e,"not_sent"))}})}notify(e,t){this._write(void 0===t?{method:e}:{method:e,params:t})}respond(e,t){this._write({id:e,result:t})}respondError(e,t){this._write({id:e,error:n(t?.message||t)})}_onLine(e){if(!String(e).trim())return;let t;try{t=JSON.parse(e)}catch{return}if(null!=t.id&&("result"in t||"error"in t)&&!t.method){const e=this.pending.get(t.id);if(!e)return;if(this.pending.delete(t.id),e.timer&&clearTimeout(e.timer),t.error){const r=o(t.error.message||`${e.method} failed`,"rejected");r.code=t.error.code,r.data=t.error.data,e.reject(r)}else e.resolve(t.result);return}if(null!=t.id&&t.method)Promise.resolve().then(()=>{if(!this.onServerRequest)throw new Error(`Unsupported Codex App Server request: ${t.method}`);return this.onServerRequest(t.method,t.params||{},t.id)}).then(e=>{try{this.respond(t.id,e??{})}catch{}}).catch(e=>{try{this.respondError(t.id,e)}catch{}});else if(t.method){if("turn/completed"===t.method){const e=t.params?.turn,r=e?.id;if(r){const e=this.turnWaiters.get(r);if(e)this.turnWaiters.delete(r),e.resolve(t.params);else for(this.completedTurns.set(r,t.params);this.completedTurns.size>32;)this.completedTurns.delete(this.completedTurns.keys().next().value)}}if("mcpServer/startupStatus/updated"===t.method){const e=t.params||{},r=String(e.threadId||""),i=String(e.name||"");if(r&&i){const t=`${r}\0${i}`;this.mcpStartup.set(t,e);const s=this.mcpWaiters.get(t);s&&"ready"===e.status?(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.resolve(e)):s&&("failed"===e.status||e.error||e.failureReason)&&(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.reject(new Error(`Codex MCP server ${i} failed to start: ${e.error||e.failureReason||e.status}`)))}}try{this.onNotification?.(t.method,t.params||{})}catch{}}}_close(e,t=null){if(!(t&&this.child&&t!==this.child||this.closedError)){this.closedError=o(e instanceof Error?e:String(e||"codex app-server closed"),"unknown"),this.child=null;for(const{reject:e,timer:t}of this.pending.values())t&&clearTimeout(t),e(this.closedError);this.pending.clear();for(const{reject:e}of this.turnWaiters.values())e(this.closedError);this.turnWaiters.clear(),this.completedTurns.clear();for(const{reject:e,timer:t}of this.mcpWaiters.values())t&&clearTimeout(t),e(this.closedError);if(this.mcpWaiters.clear(),this.mcpStartup.clear(),!this.starting)try{this.onClose?.(this.closedError)}catch{}}}async startThread({threadId:e,cwd:t,model:r,sandbox:i,approvalPolicy:s,config:o}={}){const n={cwd:t,model:r,sandbox:i,approvalPolicy:s,...o?{config:o}:{}},a=e?await this.request("thread/resume",{...n,threadId:e},this.threadTimeoutMs):await this.request("thread/start",n,this.threadTimeoutMs),d=a?.thread?.id||a?.threadId||e;if(!d)throw new Error("Codex App Server did not return a thread id");return d}waitForMcpServer({threadId:e,name:t,timeoutMs:r=this.threadTimeoutMs}={}){const i=String(e||""),s=String(t||"");if(!i||!s)return Promise.reject(new Error("Codex MCP readiness needs a thread id and server name"));const o=`${i}\0${s}`,n=this.mcpStartup.get(o);if("ready"===n?.status)return Promise.resolve(n);if(n&&("failed"===n.status||n.error||n.failureReason))return Promise.reject(new Error(`Codex MCP server ${s} failed to start: ${n.error||n.failureReason||n.status}`));if(this.mcpWaiters.has(o))return this.mcpWaiters.get(o).promise;let a,d,c=null;const l=new Promise((e,t)=>{a=e,d=t,r>0&&(c=setTimeout(()=>{this.mcpWaiters.delete(o),t(new Error(`Codex MCP server ${s} did not become ready within ${r}ms`))},r))});return this.mcpWaiters.set(o,{promise:l,resolve:a,reject:d,timer:c}),l}async startTurn({threadId:e,input:t,images:r,model:i,effort:s,approvalPolicy:o,collaborationMode:n}={}){const d=await this.request("turn/start",{threadId:e,input:a(t,r),...i?{model:i}:{},...s?{effort:s}:{},...o?{approvalPolicy:o}:{},...n?{collaborationMode:n}:{}},this.turnStartTimeoutMs),c=d?.turn?.id||d?.turnId;if(!c)throw new Error("Codex App Server did not return a turn id");return c}waitForTurn(e){if(this.completedTurns.has(e)){const t=this.completedTurns.get(e);return this.completedTurns.delete(e),Promise.resolve(t)}return this.turnWaiters.has(e)?Promise.reject(new Error(`Already waiting for Codex turn ${e}`)):new Promise((t,r)=>this.turnWaiters.set(e,{resolve:t,reject:r}))}async steer({threadId:e,turnId:t,input:r,images:i}={}){return this.request("turn/steer",{threadId:e,expectedTurnId:t,input:a(r,i)},this.controlTimeoutMs)}interrupt({threadId:e,turnId:t}={}){return this.request("turn/interrupt",{threadId:e,turnId:t},this.controlTimeoutMs)}compact({threadId:e}={}){return this.request("thread/compact/start",{threadId:e},this.controlTimeoutMs)}accountUsage(){return this.request("account/usage/read",null,this.controlTimeoutMs)}accountRateLimits(){return this.request("account/rateLimits/read",null,this.controlTimeoutMs)}startReview({threadId:e,target:t}={}){return this.request("review/start",{threadId:e,target:t||{type:"uncommittedChanges"},delivery:"inline"},this.turnStartTimeoutMs)}end(){const e=this.child;if(this._close(new Error("codex app-server ended")),e)try{e.kill("SIGTERM")}catch{}}}export function createCodexAppServer(e){return new CodexAppServerClient(e)}
@@ -0,0 +1 @@
1
+ import t from"node:fs";import o from"node:os";import e from"node:path";export const CODEX_HOST_RELINK_MESSAGE="host link missing; relinked from app bundle; retrying";let n=0;export function codexHostCandidatePaths({home:t=o.homedir(),hostLinkPath:n,bundleHostPath:s}={}){return{hostLinkPath:n||e.join(t,"node_modules",".bin","codex-code-mode-host"),bundleHostPath:s||"/Applications/ChatGPT.app/Contents/Resources/codex-code-mode-host"}}function s(t,o){try{return o.lstatSync(t),!0}catch{return!1}}export function codexHostRepairCommand(t={}){const o=codexHostCandidatePaths(t);return`ln -sfn ${o.bundleHostPath} ${o.hostLinkPath}`}export function missingCodexHostError(t={},o){const e=codexHostCandidatePaths(t),n=new Error(["Codex failed to start and the ChatGPT.app host shim is unavailable.",`Host link: ${e.hostLinkPath}`,`App bundle host: ${e.bundleHostPath}`,`Repair with: ${codexHostRepairCommand(e)}`,...o?.message?[`Original failure: ${o.message}`]:[]].join("\n"));return n.code="TP_CODEX_HOST_MISSING",n.cause=o,n}export function diagnoseMissingCodexHostAfterFailure({fsImpl:o=t,...e}={},n){const r=codexHostCandidatePaths(e);return s(r.hostLinkPath,o)||s(r.bundleHostPath,o)?null:missingCodexHostError(r,n)}export function healCodexHostLinkAfterFailure({fsImpl:o=t,log:r=t=>process.stderr.write(`${t}\n`),...a}={},i){const d=codexHostCandidatePaths(a);if(s(d.hostLinkPath,o))return{healed:!1,...d};if(!s(d.bundleHostPath,o))throw missingCodexHostError(d,i);o.mkdirSync(e.dirname(d.hostLinkPath),{recursive:!0});const h=`${d.hostLinkPath}.thinkpool-${process.pid}-${n++}`;try{o.symlinkSync(d.bundleHostPath,h),o.renameSync(h,d.hostLinkPath)}finally{try{o.unlinkSync(h)}catch{}}return r?.(CODEX_HOST_RELINK_MESSAGE),{healed:!0,...d}}export async function retryCodexSpawnAfterHostHeal(t,o={}){try{return await t()}catch(e){if("function"==typeof o.shouldHeal&&!o.shouldHeal(e))throw e;if(!healCodexHostLinkAfterFailure(o,e).healed)throw e;return t()}}export function prepareCodexHostLinkBeforeRawSpawn({fsImpl:o=t,...e}={}){const n=codexHostCandidatePaths(e);return s(n.hostLinkPath,o)||!s(n.bundleHostPath,o)?{healed:!1,...n}:healCodexHostLinkAfterFailure({fsImpl:o,...e})}
package/codex-session.mjs CHANGED
@@ -1 +1 @@
1
- import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:fs";import n from"node:os";import o from"node:path";import a from"node:readline";import{buildCodexAppServerArgs as s,codexAppServerGate as i,createCodexAppServer as l}from"./codex-app-server.mjs";import{CodexEventMapper as u}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as d}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as c,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as b}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as w}from"./catastrophic-command-guard.mjs";import{codexNativeReasoningEffort as k,codexReasoningEfforts as _,normalizeCodexReasoningEffort as S,normalizeReasoningEffortList as C}from"./reasoning-effort.mjs";const I=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const M=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=k(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:a,timeoutMs:s=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:d=r,spawnImpl:c=t}={}){const p=String(e||"").trim();if(!p)throw new Error("codex one-shot requires a prompt");const m=!l,f=l||d.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let h=null;try{i&&(h=o.join(f,"output-schema.json"),d.writeFileSync(h,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...a?["-m",String(a)]:[],...h?["--output-schema",h]:[],p];return await new Promise((t,r)=>{let n,o,a="",i="",l=!1;const u=(e,n)=>{l||(l=!0,o&&clearTimeout(o),e?r(e):t(n))};try{n=c("codex",e,{cwd:f,env:process.env,stdio:["ignore","pipe","pipe"]})}catch(e){return void u(e)}n.stdout.on("data",e=>{a+=e.toString(),a.length>1e6&&(a=a.slice(-1e6))}),n.stderr.on("data",e=>{i+=e.toString(),i.length>2e3&&(i=i.slice(-2e3))}),n.on("error",e=>u(e)),n.on("close",e=>{if(l)return;const t=a.split(/\r?\n/).filter(Boolean).flatMap(e=>{try{const t=JSON.parse(e);return"item.completed"===t?.type&&"agent_message"===t?.item?.type&&"string"==typeof t.item.text?[t.item.text.trim()]:[]}catch{return[]}}).filter(Boolean).at(-1);0===e&&t?u(null,t):u(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}u(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(s)||22e3))})}finally{if(m)try{d.rmSync(f,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:C(e.supported_reasoning_levels)}))}catch{return[]}}const T=new Map;function E(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,a=T.get(n);if(a)return a;const s=o.join(t,"sessions");try{const t=`${e}.jsonl`,a=r.readdirSync(s,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!a)return null;const i=o.join(a.parentPath||a.path||s,a.name);return T.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:a=r}={}){if(!e)return!1;const s=E(e,{home:t,fsImpl:a});if(!s)return!0;try{return Number(a.statSync(s).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:a=r,tailBytes:s=4194304}={}){const i=E(e,{home:t,fsImpl:a});if(!i)return null;let l;try{const e=Number(a.statSync(i).size)||0,t=Math.min(e,Math.max(65536,s)),r=Buffer.alloc(t);l=a.openSync(i,"r"),a.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},a=r.total_token_usage||{},s=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(s)||s<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(s),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(a.input_tokens)||0),cached_input_tokens:Math.max(0,Number(a.cached_input_tokens)||0),output_tokens:Math.max(0,Number(a.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{a.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return I.has(e)?e:"workspace-write"}export function codexPublishAccess({cwd:t,env:r=process.env,tmpdir:a=n.tmpdir(),execFile:s=e}={}){const i=[];try{const e=String(s("git",["rev-parse","--git-common-dir"],{cwd:t,encoding:"utf8"})||"").trim();if(e){const r=o.resolve(t,e),n=o.resolve(t);r===n||r.startsWith(n+o.sep)||i.push(r)}}catch{}return{writableDirs:i,env:{...r,NPM_CONFIG_CACHE:o.join(a,"thinkpool-npm-cache")}}}export const CODEX_ROOM_CASCADE_REMINDER=["thinkpool ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above. A person-authored request for a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. Never claim a Cascade or visible lane started without a successful thinkpool opener result containing its room-visible terminal ref. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals and can never substitute for spawn_terminal or open_main_terminal. If the required opener is unavailable or fails, say that nothing started and report the blocker. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate."].join(" ");export function buildCodexPrompt({text:e,terminalRolePrompt:t,rolePrompt:r,roomContext:n,firstTurn:o=!1,promptIndex:a=0,forceFullReminder:s=!1,turnCause:i=null}){const l="function"==typeof n?n():n,u=f({promptIndex:a,forceFull:s,turnCause:i}),d=[o?c:"",u?t:"",p({text:e,promptIndex:a,forceFull:s,turnCause:i}),u?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return d.length?`<thinkpool-context>\n${d.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:a="untrusted",providerConfig:s,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:d}){const c=normalizeCodexSandbox(n),p=[...w(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===c?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${c}"`]]:["exec","--json","--sandbox",c,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${a}"`);const m=k(r);if(m&&p.push("-c",`model_reasoning_effort="${m}"`),s&&p.push("-c",s),!e)for(const e of l)p.push("--add-dir",e);i&&(p.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(i)}`),p.push("-c","mcp_servers.thinkpool.required=true"));let f=!1;for(const e of u)o.isAbsolute(String(e||""))&&(p.push("--image",String(e)),f=!0);return f&&p.push("--"),e&&p.push(e),p.push(String(d??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const F=/\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i;export function codexApprovalCard(e,t={},r=null){const n=String(t.approvalId||t.itemId||`${t.turnId||"turn"}:${e}`);if("item/tool/requestUserInput"===e){const e=Array.isArray(t.questions)?t.questions:[];return{id:n,toolName:"AskUserQuestion",input:{questions:e},risk:"ask",questions:e,asker:"Codex",answerFormat:"codex",...Number.isFinite(t.autoResolutionMs)&&t.autoResolutionMs>0?{autoResolutionMs:t.autoResolutionMs}:{}}}if("item/commandExecution/requestApproval"===e){const e=t.command||r?.command||"";return{id:n,toolName:"Bash",input:{command:e,cwd:t.cwd||r?.cwd||"",...t.reason?{reason:t.reason}:{}},risk:F.test(e)?"high":"medium"}}if("item/fileChange/requestApproval"===e){const e=Array.isArray(r?.changes)?r.changes:[],o=e[0]||{};return{id:n,toolName:e.length>1?"MultiEdit":"add"===o.kind?"Write":"Edit",input:e.length>1?{edits:e.map(e=>({file_path:e.path||"",path:e.path||"",kind:e.kind||"update"})),...t.reason?{reason:t.reason}:{}}:{file_path:o.path||t.grantRoot||"",path:o.path||t.grantRoot||"",kind:o.kind||"update",...t.reason?{reason:t.reason}:{}},risk:"medium"}}return null}export function codexApprovalResponse(e){return{decision:"always"===e?"acceptForSession":"allow"===e?"accept":"decline"}}export function codexUserInputResponse(e,t=[]){return h(e,t)}function N(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:c,models:p=[],effort:h="high",resume:w,env:C,sandbox:I,mode:T="default",onEvent:E,providerConfig:F,terminalRolePrompt:O,rolePrompt:A,roomContext:R,mcpServers:P,prepareCwd:D=null,requestPermission:U,appServerGate:j=i,appServerFactory:$=l,mcpHttpFactory:L=d,spawnImpl:q=t,admitStart:G=null,stallOptions:X={}}){let z=CODEX_MODE_CONFIG[T]?T:"default",B=codexConfigForMode(z);I=normalizeCodexSandbox(I||B.sandbox);const W=codexPublishAccess({cwd:e,env:{...process.env,...C||{}}}),H=W.env,J=w||null,K=!J||codexThreadCanResume(J,{home:H.CODEX_HOME||o.join(n.homedir(),".codex")});let V=K?J:null,Y=V?1:0,Q=0,Z=!0;const ee=m(R);let te=null,re=!1,ne=!1,oe=!1;const ae="function"==typeof X.now?X.now:Date.now,se="function"==typeof X.setInterval?X.setInterval:setInterval,ie="function"==typeof X.clearInterval?X.clearInterval:clearInterval,le=Number.isFinite(X.stallMs)?Math.max(1,Number(X.stallMs)):Math.max(3e4,parseInt(H.TP_STALL_MS,10)||9e4),ue=Number.isFinite(X.forceStopMs)?Math.max(le,Number(X.forceStopMs)):Math.max(3*le,parseInt(H.TP_FORCE_STOP_MS,10)||3e5),de=Number.isFinite(X.longReasoningForceStopMs)?Math.max(ue,Number(X.longReasoningForceStopMs)):Math.max(ue,parseInt(H.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),ce=Number.isFinite(X.intervalMs)?Math.max(1,Number(X.intervalMs)):5e3;let pe=ae(),me=!1,fe=!1,he=!1,xe=!1,ge=!0,ye=0,ve=0,be=null,we=c||null,ke=S(h,{models:p,model:we}),_e=V?readCodexThreadUsage(V):null,Se=null;const Ce=j({env:H});let Ie=!Ce.enabled,Me=null,Te=!1;const Ee=new WeakSet;let Fe=null,Ne=null,Oe=!1;const Ae=new Set,Re=new Map,Pe=new Set,De=new Map;let Ue=Promise.resolve(),je=Promise.resolve();const $e=[],Le=y(e=>{try{E?.(e)}catch{}}),qe=()=>{pe=ae(),me=!1},Ge=e=>{qe(),Le(e)};Ge.cancel=()=>Le.cancel();const Xe=new u({onEvent:Ge,model:c||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(_e=t),t||_e}}),ze=()=>{const e=[...De.values()];De.clear();for(const t of e)t.text&&Ge({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},Be=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(ze(),oe=!1),Xe.push(e)},We=e=>{ze(),oe=!1,Ge(e)},He=({retry:e=!1,recoveryAttempt:t=0}={})=>{oe=!0,pe=ae(),me=!1,he=!1,xe=!1,ge=!0,ye=Math.max(0,Number(t)||0),e||(fe=!1)},Je=e=>Ge({kind:"note",text:e}),Ke=(e=Fe)=>{const t=String(e||"");if(t)for(Ae.add(t);Ae.size>64;)Ae.delete(Ae.values().next().value)},Ve=(e={})=>{const t=String(e.turnId||"");return!(!t||ne||re||Ae.has(t)||t!==String(Fe||""))};async function Ye(){const e=P?.thinkpool;return e?(Se||(Se=L({sdkServer:e})),Se):null}function Qe(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&V&&String(t.threadId)!==String(V))return;(oe||Oe)&&qe();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(_e=e,Oe){const{used:t,max:r}=e.context;Ge({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:we}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(ne||re)return void Ke(e);if(Ne){const t=Ne;return Ne=null,void t.resolve(e)}return Fe||(Fe=e),void qe()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||Ve(t)){if(qe(),"turn/plan/updated"===e){const e=(Array.isArray(t.plan)?t.plan:[]).map(({step:e,status:t})=>({content:e||"",activeForm:e||"",status:"inProgress"===t?"in_progress":"completed"===t?"completed":"pending"})).filter(e=>e.content);return void Ge({kind:"assistant",blocks:[{type:"tool_use",id:`codex-plan:${t.turnId||"active"}`,name:"TodoWrite",input:{todos:e}}],parentToolUseId:null})}if("item/agentMessage/delta"===e){if(!t.delta)return;const e=t.itemId||`turn:${t.turnId||Fe||"active"}`,r=De.get(e)||{cid:`codex-stream:${V||"thread"}:${e}`,text:""};return r.text+=String(t.delta),De.set(e,r),void Ge({kind:"assistant_stream",cid:r.cid,text:r.text,itemId:e,streaming:!0})}if("item/started"===e||"item/completed"===e){const r=t.item;if(!r)return;if("item/started"===e&&(Re.set(r.id,r),r.id&&M(r)&&Pe.add(r.id)),"item/completed"===e&&(r.id&&Pe.delete(r.id),Re.set(r.id,r),"agentMessage"===r.type&&De.has(r.id))){const e=De.get(r.id);return De.delete(r.id),void Ge({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void Be({type:"item/started"===e?"item.started":"item.completed",item:N(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";Be({type:"error",message:e})}}}async function Ze(e,t={}){if(!Ve(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,Re.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";ve++;try{n=await(U?.(r))}catch{}finally{ve=Math.max(0,ve-1),qe()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function et(){if(Ie)return!1;if(!1===Me?.alive)return Te=!1,Ie=!0,!1;if(Me&&Te)return!0;let t;try{t=await Ye()}catch(e){Je(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Me){const r=$({cwd:e,env:H,args:s({providerConfig:F,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(z,I,W.writableDirs)}),onNotification:Qe,onServerRequest:Ze,onClose:e=>{ne||Ie||Ee.has(r)||(Te=!1,Ie=!0,Je(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Me=r,await Me.start(),qe()}return V=await Me.startThread({threadId:Y>0?V:null,cwd:e,model:we,sandbox:I,approvalPolicy:B.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),qe(),t?.url&&(await Me.waitForMcpServer({threadId:V,name:"thinkpool"}),qe()),Te=!0,Be({type:"thread.started",thread_id:V}),!0}catch{Ie=!0;try{Me?.end()}catch{}return Me=null,Te=!1,!1}}function tt({force:e=!1}={}){const t=Me,r=Fe;if(t&&e){Ke(r),Ie=!0,Te=!1;try{t.end()}catch{}Me===t&&(Me=null)}else if(t&&r)Ke(r),Promise.resolve(t.interrupt({threadId:V,turnId:r})).catch(()=>{Ie=!0,Te=!1;try{t.end()}catch{}Me===t&&(Me=null)});else if(t){Ie=!0,Te=!1;try{t.end()}catch{}Me===t&&(Me=null)}if(te){const e=te;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(te===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function rt(e,t={}){if(ne||re||xe)return!0;if(!await et())return!1;if(ne||re||xe)return!0;Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),oe=!0;try{if(ge=!1,Fe=await Me.startTurn({threadId:V,input:e,images:Array.isArray(t.images)?t.images:[],model:we,effort:k(ke),approvalPolicy:B.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(we,ke)}),qe(),re||ne){Ke(Fe);try{await Me.interrupt({threadId:V,turnId:Fe})}catch{}return!0}const r=await Me.waitForTurn(Fe);Ke(r?.turn?.id||Fe);const n=r?.turn?.status;re?We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(he||xe)||("interrupted"===n?We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(he=!1,Be({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(he=!1,Be({type:"turn.completed"})))}catch(e){Ie=!0,Te=!1,Ke(Fe);try{Me?.end()}catch{}Me=null,ne||he||xe||Be({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{ze(),Fe=null,oe=!1,Pe.clear(),Re.clear(),De.clear()}return!0}async function nt(e){if(ne||re||xe)return!0;if(!await et())return!1;if(ne||re||xe)return!0;Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),oe=!0;try{ge=!1;const t=await Me.startReview({threadId:V,target:x(e)});if(Fe=t?.turn?.id||t?.turnId,!Fe)throw new Error("Codex review/start returned no turn id");if(qe(),re||ne){Ke(Fe);try{await Me.interrupt({threadId:V,turnId:Fe})}catch{}return!0}const r=await Me.waitForTurn(Fe);Ke(r?.turn?.id||Fe);const n=r?.turn?.status;re?We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(he||xe)||("interrupted"===n?We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(he=!1,Be({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(he=!1,Be({type:"turn.completed"})))}catch(e){Ie=!0,Te=!1,Ke(Fe);try{Me?.end()}catch{}Me=null,ne||he||xe||Be({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{ze(),Fe=null,oe=!1,Pe.clear(),Re.clear(),De.clear()}return!0}async function ot(t,n={}){if(ne||re||xe)return;let o;try{o=await Ye()}catch(e){Je(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Je(`thinkpool peer handoff failed: ${e?.message||e}`)}const s=buildCodexExecArgs({sessionId:0===Y?null:V,model:we,effort:ke,sandbox:I,approvalPolicy:B.approvalPolicy,providerConfig:F,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(z,I,W.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});return Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),new Promise(t=>{let n;try{n=r.openSync("/dev/null","r")}catch{}const o={cwd:e,env:H,stdio:["ignore","pipe","pipe"]};if(null!=n&&(o.stdio=[n,"pipe","pipe"]),oe=!0,ge=!1,te=q("codex",s,o),null!=n)try{r.closeSync(n)}catch{}const i=[];a.createInterface({input:te.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}qe(),i.push(t.type),"item.started"===t.type&&t.item?.id&&M(t.item)&&Pe.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Pe.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(V=V||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(u=!0,he=!1),Be(t)});let l="",u=!1,d=!1;te.stderr.on("data",e=>{l+=e.toString(),l.length>800&&(l=l.slice(-800))}),te.on("error",e=>{d||(d=!0,te=null,Pe.clear(),he||xe||We({kind:"error",message:`codex failed to start: ${e.message}`,recoverable:!0}),t({preTurnInitFailure:!1}))}),te.on("close",e=>{if(d)return;d=!0,te=null,oe=!1,Pe.clear();const r=`codex exec exited ${e}${l?": "+l.trim().slice(-300):""}`;0===e||null==e||u||!codexExecFailedBeforeTurn({stderr:l,eventTypes:i})||re||xe?((!he&&!xe||u||re)&&(re&&!u?We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}):re||u||0===e||null==e||We({kind:"error",message:r,recoverable:!0})),t({preTurnInitFailure:!1})):t({preTurnInitFailure:!0,message:r})})})}function at(){if(ne)return;const e=$e.shift();e&&(je=je.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);He({recoveryAttempt:t}),t>0&&Le({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>ee({force:n}),firstTurn:0===Y,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),a=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let s=!1,i=!1;for(;!ne&&!re&&!xe;){if(!(a?await nt(e.text):await rt(o,e.options)))if(a)he=!1,We({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{he&&(he=!1,s=!0,He({retry:!0,recoveryAttempt:t}),Le({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await ot(o,e.options);if(r?.preTurnInitFailure){if(!(i||ne||re||xe)){i=!0,He({retry:!0,recoveryAttempt:t}),Le({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}We({kind:"error",message:r.message,recoverable:!0})}}if(!he||s||xe||ne||re)break;he=!1,s=!0,He({retry:!0,recoveryAttempt:t}),Le({kind:"note",text:"retrying the stalled turn on a fresh connection"})}Y++,at()}))}return J&&!K&&queueMicrotask(()=>Je("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(H.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Ce.enabled||queueMicrotask(()=>Je(`Codex App Server disabled: ${Ce.reason}`)),be=se(()=>{const e=ae()-pe,t=v({turnActive:oe,awaitingUser:ve,quietMs:e,stallMs:le,forceStopMs:["max","ultra"].includes(ke)?de:ue,stalledSent:me,stallRetried:fe});if("none"===t)return;if("retry"===t&&!ge)return xe=!0,he=!1,ye>0?(Le({kind:"error",recoverable:!0,message:"The safe recovery continuation also stopped responding. The original prompt was not replayed again. Switch to another model or provider, then continue from the current workspace state."}),Le({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Pe.size>0?Le({kind:"error",recoverable:!0,message:"Codex stopped responding while a tool was still running. The transport was stopped, but no automatic continuation was started because that could overlap an uncertain side effect. Inspect the current state before continuing manually."}):($e.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:Q++,forceFullReminder:!0}),Le({kind:"error",recoverable:!0,message:"Codex stopped responding after the turn began. The transport was stopped. A new continuation turn will inspect the current state; the original prompt was not replayed."})),We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void tt({force:!0});const r=b(t,e);if(r&&Le(r),"status"!==t){if("retry"===t)return fe=!0,he=!0,pe=ae(),me=!1,void tt();xe=!0,he=!1,$e.length=0,We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),tt({force:!0})}else me=!0},ce),be.unref?.(),{get sessionId(){return V},get usageSnapshot(){return _e},get turnActive(){return oe||Oe},get canSteer(){return!(!Me||!1===Me.alive||!Te||Ie)},get started(){return oe||Oe||!!te||!(!Me||!1===Me.alive)},sendTurn(t,r={}){if(ne)return!1;if(!oe&&0===$e.length&&!Te&&!te){const e="function"==typeof G?G():{ok:!0};if(!1===e?.ok)return Ge({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=Q++,o=Z;if(Z=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!oe&&0===Y&&D)try{e=D()||e}catch{}if(oe&&Me&&Fe){qe();const e=r?.turnCause||null,a=f({promptIndex:n,forceFull:o,turnCause:e}),s=buildCodexPrompt({text:t,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>ee({force:a}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Fe;return Ue=Ue.then(()=>Me.steer({threadId:V,turnId:i,input:s,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?($e.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),oe||1!==$e.length||at()):Ge({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return $e.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),oe||Oe||(re=!1,He({recoveryAttempt:0})),1!==$e.length||Oe||at(),!0},async park(){if(ne||oe||Oe||$e.length)return!1;const e=je;try{await e}catch{}try{await Ue}catch{}if(ne||oe||Oe||$e.length||te||Fe)return!1;const t=Me;if(t){Ee.add(t),Me=null,Te=!1,Re.clear(),De.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=je,t=oe&&!Fe&&!te;re=!0,he=!1,xe=!1,$e.length=0,Ke(Fe);let r=Promise.resolve();if(Me&&Fe&&(r=Me.interrupt({threadId:V,turnId:Fe}).catch(()=>{})),te){try{te.kill("SIGTERM")}catch{}setTimeout(()=>{try{te?.kill("SIGKILL")}catch{}},1500)}else t&&We({kind:"result",subtype:"aborted",sessionId:V,model:we,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(ne=!0,oe=!1,be&&ie(be),Ge.cancel(),$e.length=0,Ke(Fe),te)try{te.kill()}catch{}try{Me?.end()}catch{}Se?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||oe)return!1;we=String(e),Xe.setModel(we);const t=S(ke,{models:p,model:we});return t!==ke&&(ke=t,Ge({kind:"effort",level:ke})),!0},setMode:e=>!(oe||!CODEX_MODE_CONFIG[e]||(z=e,B=codexConfigForMode(z),I=B.sandbox,V=null,Y=0,_e=null,Te=!1,0)),setEffort:e=>!(oe||!_({models:p,model:we}).includes(e)||(ke=e,Ge({kind:"effort",level:ke}),0)),clearContext:()=>!oe&&(V=null,Y=0,_e=null,Te=!1,!0),async compactContext(){if(oe||Oe)return!1;re=!1,Oe=!0;let e=null,t=!1;try{if(!await et()||oe)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Ne=null,r(new Error("Codex did not start the compaction turn"))},5e3),Ne={resolve:t,reject:r}});await Me.compact({threadId:V}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Me.waitForTurn(n);Ke(o?.turn?.id||n);const a=o?.turn?.status;if("completed"===a)return!0;throw new Error(o?.turn?.error?.message||`compaction ${a||"failed"}`)}catch(e){return t||!["rejected","not_sent"].includes(e?.delivery)?(Je(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Je(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Ne=null,Oe=!1,$e.length&&at()}},async accountUsage(){if(!await et())return null;const[e,t]=await Promise.all([Me.accountUsage().catch(()=>null),Me.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
1
+ import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:fs";import n from"node:os";import o from"node:path";import s from"node:readline";import{buildCodexAppServerArgs as a,codexAppServerGate as i,createCodexAppServer as l}from"./codex-app-server.mjs";import{CodexEventMapper as u}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as c}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as d,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as w}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as b}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as k}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as _,codexReasoningEfforts as S,normalizeCodexReasoningEffort as C,normalizeReasoningEffortList as I}from"./reasoning-effort.mjs";const M=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const T=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=_(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:s,timeoutMs:a=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:c=r,spawnImpl:d=t,codexHost:p={}}={}){const m=String(e||"").trim();if(!m)throw new Error("codex one-shot requires a prompt");const f=!l,h=l||c.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let x=null;try{i&&(x=o.join(h,"output-schema.json"),c.writeFileSync(x,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...x?["--output-schema",x]:[],m];return await k(()=>new Promise((t,r)=>{let n,o,s="",i="",l=!1;const u=(e,n)=>{l||(l=!0,o&&clearTimeout(o),e?r(e):t(n))};try{n=d("codex",e,{cwd:h,env:process.env,stdio:["ignore","pipe","pipe"]})}catch(e){return void u(e)}n.stdout.on("data",e=>{s+=e.toString(),s.length>1e6&&(s=s.slice(-1e6))}),n.stderr.on("data",e=>{i+=e.toString(),i.length>2e3&&(i=i.slice(-2e3))}),n.on("error",e=>u(e)),n.on("close",e=>{if(l)return;const t=s.split(/\r?\n/).filter(Boolean).flatMap(e=>{try{const t=JSON.parse(e);return"item.completed"===t?.type&&"agent_message"===t?.item?.type&&"string"==typeof t.item.text?[t.item.text.trim()]:[]}catch{return[]}}).filter(Boolean).at(-1);0===e&&t?u(null,t):u(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}u(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(a)||22e3))}),p)}finally{if(f)try{c.rmSync(h,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:I(e.supported_reasoning_levels)}))}catch{return[]}}const E=new Map;function F(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=E.get(n);if(s)return s;const a=o.join(t,"sessions");try{const t=`${e}.jsonl`,s=r.readdirSync(a,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!s)return null;const i=o.join(s.parentPath||s.path||a,s.name);return E.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r}={}){if(!e)return!1;const a=F(e,{home:t,fsImpl:s});if(!a)return!0;try{return Number(s.statSync(a).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r,tailBytes:a=4194304}={}){const i=F(e,{home:t,fsImpl:s});if(!i)return null;let l;try{const e=Number(s.statSync(i).size)||0,t=Math.min(e,Math.max(65536,a)),r=Buffer.alloc(t);l=s.openSync(i,"r"),s.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},s=r.total_token_usage||{},a=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(a)||a<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(a),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(s.input_tokens)||0),cached_input_tokens:Math.max(0,Number(s.cached_input_tokens)||0),output_tokens:Math.max(0,Number(s.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{s.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return M.has(e)?e:"workspace-write"}export function codexPublishAccess({cwd:t,env:r=process.env,tmpdir:s=n.tmpdir(),execFile:a=e}={}){const i=[];try{const e=String(a("git",["rev-parse","--git-common-dir"],{cwd:t,encoding:"utf8"})||"").trim();if(e){const r=o.resolve(t,e),n=o.resolve(t);r===n||r.startsWith(n+o.sep)||i.push(r)}}catch{}return{writableDirs:i,env:{...r,NPM_CONFIG_CACHE:o.join(s,"thinkpool-npm-cache")}}}export const CODEX_ROOM_CASCADE_REMINDER=["thinkpool ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above. A person-authored request for a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. Never claim a Cascade or visible lane started without a successful thinkpool opener result containing its room-visible terminal ref. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals and can never substitute for spawn_terminal or open_main_terminal. If the required opener is unavailable or fails, say that nothing started and report the blocker. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate."].join(" ");export function buildCodexPrompt({text:e,terminalRolePrompt:t,rolePrompt:r,roomContext:n,firstTurn:o=!1,promptIndex:s=0,forceFullReminder:a=!1,turnCause:i=null}){const l="function"==typeof n?n():n,u=f({promptIndex:s,forceFull:a,turnCause:i}),c=[o?d:"",u?t:"",p({text:e,promptIndex:s,forceFull:a,turnCause:i}),u?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return c.length?`<thinkpool-context>\n${c.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:s="untrusted",providerConfig:a,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:c}){const d=normalizeCodexSandbox(n),p=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===d?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${d}"`]]:["exec","--json","--sandbox",d,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${s}"`);const m=_(r);if(m&&p.push("-c",`model_reasoning_effort="${m}"`),a&&p.push("-c",a),!e)for(const e of l)p.push("--add-dir",e);i&&(p.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(i)}`),p.push("-c","mcp_servers.thinkpool.required=true"));let f=!1;for(const e of u)o.isAbsolute(String(e||""))&&(p.push("--image",String(e)),f=!0);return f&&p.push("--"),e&&p.push(e),p.push(String(c??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const N=/\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i;export function codexApprovalCard(e,t={},r=null){const n=String(t.approvalId||t.itemId||`${t.turnId||"turn"}:${e}`);if("item/tool/requestUserInput"===e){const e=Array.isArray(t.questions)?t.questions:[];return{id:n,toolName:"AskUserQuestion",input:{questions:e},risk:"ask",questions:e,asker:"Codex",answerFormat:"codex",...Number.isFinite(t.autoResolutionMs)&&t.autoResolutionMs>0?{autoResolutionMs:t.autoResolutionMs}:{}}}if("item/commandExecution/requestApproval"===e){const e=t.command||r?.command||"";return{id:n,toolName:"Bash",input:{command:e,cwd:t.cwd||r?.cwd||"",...t.reason?{reason:t.reason}:{}},risk:N.test(e)?"high":"medium"}}if("item/fileChange/requestApproval"===e){const e=Array.isArray(r?.changes)?r.changes:[],o=e[0]||{};return{id:n,toolName:e.length>1?"MultiEdit":"add"===o.kind?"Write":"Edit",input:e.length>1?{edits:e.map(e=>({file_path:e.path||"",path:e.path||"",kind:e.kind||"update"})),...t.reason?{reason:t.reason}:{}}:{file_path:o.path||t.grantRoot||"",path:o.path||t.grantRoot||"",kind:o.kind||"update",...t.reason?{reason:t.reason}:{}},risk:"medium"}}return null}export function codexApprovalResponse(e){return{decision:"always"===e?"acceptForSession":"allow"===e?"accept":"decline"}}export function codexUserInputResponse(e,t=[]){return h(e,t)}function R(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:d,models:p=[],effort:h="high",resume:b,env:I,sandbox:M,mode:E="default",onEvent:F,providerConfig:N,terminalRolePrompt:O,rolePrompt:A,roomContext:P,mcpServers:D,prepareCwd:U=null,requestPermission:j,appServerGate:$=i,appServerFactory:L=l,mcpHttpFactory:q=c,spawnImpl:G=t,admitStart:X=null,stallOptions:H={},codexHost:z={}}){let B=CODEX_MODE_CONFIG[E]?E:"default",W=codexConfigForMode(B);M=normalizeCodexSandbox(M||W.sandbox);const J=codexPublishAccess({cwd:e,env:{...process.env,...I||{}}}),K=J.env,V=b||null,Y=!V||codexThreadCanResume(V,{home:K.CODEX_HOME||o.join(n.homedir(),".codex")});let Q=Y?V:null,Z=Q?1:0,ee=0,te=!0;const re=m(P);let ne=null,oe=!1,se=!1,ae=!1;const ie="function"==typeof H.now?H.now:Date.now,le="function"==typeof H.setInterval?H.setInterval:setInterval,ue="function"==typeof H.clearInterval?H.clearInterval:clearInterval,ce=Number.isFinite(H.stallMs)?Math.max(1,Number(H.stallMs)):Math.max(3e4,parseInt(K.TP_STALL_MS,10)||9e4),de=Number.isFinite(H.forceStopMs)?Math.max(ce,Number(H.forceStopMs)):Math.max(3*ce,parseInt(K.TP_FORCE_STOP_MS,10)||3e5),pe=Number.isFinite(H.longReasoningForceStopMs)?Math.max(de,Number(H.longReasoningForceStopMs)):Math.max(de,parseInt(K.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),me=Number.isFinite(H.intervalMs)?Math.max(1,Number(H.intervalMs)):5e3;let fe=ie(),he=!1,xe=!1,ge=!1,ye=!1,ve=!0,we=0,be=0,ke=null,_e=d||null,Se=C(h,{models:p,model:_e}),Ce=Q?readCodexThreadUsage(Q):null,Ie=null;const Me=$({env:K});let Te=!Me.enabled,Ee=null,Fe=!1;const Ne=new WeakSet;let Re=null,Oe=null,Ae=!1;const Pe=new Set,De=new Map,Ue=new Set,je=new Map;let $e=Promise.resolve(),Le=Promise.resolve();const qe=[],Ge=y(e=>{try{F?.(e)}catch{}}),Xe=()=>{fe=ie(),he=!1},He=e=>{Xe(),Ge(e)};He.cancel=()=>Ge.cancel();const ze=new u({onEvent:He,model:d||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Ce=t),t||Ce}}),Be=()=>{const e=[...je.values()];je.clear();for(const t of e)t.text&&He({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},We=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Be(),ae=!1),ze.push(e)},Je=e=>{Be(),ae=!1,He(e)},Ke=({retry:e=!1,recoveryAttempt:t=0}={})=>{ae=!0,fe=ie(),he=!1,ge=!1,ye=!1,ve=!0,we=Math.max(0,Number(t)||0),e||(xe=!1)},Ve=e=>He({kind:"note",text:e}),Ye=(e=Re)=>{const t=String(e||"");if(t)for(Pe.add(t);Pe.size>64;)Pe.delete(Pe.values().next().value)},Qe=(e={})=>{const t=String(e.turnId||"");return!(!t||se||oe||Pe.has(t)||t!==String(Re||""))};async function Ze(){const e=D?.thinkpool;return e?(Ie||(Ie=q({sdkServer:e})),Ie):null}function et(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&Q&&String(t.threadId)!==String(Q))return;(ae||Ae)&&Xe();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ce=e,Ae){const{used:t,max:r}=e.context;He({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:_e}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(se||oe)return void Ye(e);if(Oe){const t=Oe;return Oe=null,void t.resolve(e)}return Re||(Re=e),void Xe()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||Qe(t)){if(Xe(),"turn/plan/updated"===e){const e=(Array.isArray(t.plan)?t.plan:[]).map(({step:e,status:t})=>({content:e||"",activeForm:e||"",status:"inProgress"===t?"in_progress":"completed"===t?"completed":"pending"})).filter(e=>e.content);return void He({kind:"assistant",blocks:[{type:"tool_use",id:`codex-plan:${t.turnId||"active"}`,name:"TodoWrite",input:{todos:e}}],parentToolUseId:null})}if("item/agentMessage/delta"===e){if(!t.delta)return;const e=t.itemId||`turn:${t.turnId||Re||"active"}`,r=je.get(e)||{cid:`codex-stream:${Q||"thread"}:${e}`,text:""};return r.text+=String(t.delta),je.set(e,r),void He({kind:"assistant_stream",cid:r.cid,text:r.text,itemId:e,streaming:!0})}if("item/started"===e||"item/completed"===e){const r=t.item;if(!r)return;if("item/started"===e&&(De.set(r.id,r),r.id&&T(r)&&Ue.add(r.id)),"item/completed"===e&&(r.id&&Ue.delete(r.id),De.set(r.id,r),"agentMessage"===r.type&&je.has(r.id))){const e=je.get(r.id);return je.delete(r.id),void He({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void We({type:"item/started"===e?"item.started":"item.completed",item:R(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";We({type:"error",message:e})}}}async function tt(e,t={}){if(!Qe(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,De.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";be++;try{n=await(j?.(r))}catch{}finally{be=Math.max(0,be-1),Xe()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function rt(){if(Te)return!1;if(!1===Ee?.alive)return Fe=!1,Te=!0,!1;if(Ee&&Fe)return!0;let t;try{t=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ee){const r=L({cwd:e,env:K,args:a({providerConfig:N,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs)}),onNotification:et,onServerRequest:tt,codexHost:{...z,log:Ve},onClose:e=>{se||Te||Ne.has(r)||(Fe=!1,Te=!0,Ve(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ee=r,await Ee.start(),Xe()}return Q=await Ee.startThread({threadId:Z>0?Q:null,cwd:e,model:_e,sandbox:M,approvalPolicy:W.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Xe(),t?.url&&(await Ee.waitForMcpServer({threadId:Q,name:"thinkpool"}),Xe()),Fe=!0,We({type:"thread.started",thread_id:Q}),!0}catch{Te=!0;try{Ee?.end()}catch{}return Ee=null,Fe=!1,!1}}function nt({force:e=!1}={}){const t=Ee,r=Re;if(t&&e){Ye(r),Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}else if(t&&r)Ye(r),Promise.resolve(t.interrupt({threadId:Q,turnId:r})).catch(()=>{Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)});else if(t){Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}if(ne){const e=ne;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(ne===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function ot(e,t={}){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{if(ve=!1,Re=await Ee.startTurn({threadId:Q,input:e,images:Array.isArray(t.images)?t.images:[],model:_e,effort:_(Se),approvalPolicy:W.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(_e,Se)}),Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function st(e){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{ve=!1;const t=await Ee.startReview({threadId:Q,target:x(e)});if(Re=t?.turn?.id||t?.turnId,!Re)throw new Error("Codex review/start returned no turn id");if(Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function at(t,n={}){if(se||oe||ye)return;let o;try{o=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Ve(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===Z?null:Q,model:_e,effort:Se,sandbox:M,approvalPolicy:W.approvalPolicy,providerConfig:N,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:K,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),ae=!0,ve=!1;try{ne=G("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return ae=!1,e.codexExecFailure={spawnFailure:!0,hostRetrySafe:!0,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},void n(e)}if(null!=o)try{r.closeSync(o)}catch{}const l=ne,u=[];s.createInterface({input:l.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}Xe(),u.push(t.type),"item.started"===t.type&&t.item?.id&&T(t.item)&&Ue.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Ue.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(Q=Q||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(d=!0,ge=!1),We(t)});let c="",d=!1,p=!1;l.stderr.on("data",e=>{c+=e.toString(),c.length>800&&(c=c.slice(-800))}),l.on("error",e=>{p||(p=!0,ne===l&&(ne=null),ae=!1,Ue.clear(),e.codexExecFailure={spawnFailure:!0,hostRetrySafe:0===u.length,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},n(e))}),l.on("close",e=>{if(p)return;p=!0,ne===l&&(ne=null),ae=!1,Ue.clear();const r=`codex exec exited ${e}${c?": "+c.trim().slice(-300):""}`,o=0!==e&&null!=e&&!d&&codexExecFailedBeforeTurn({stderr:c,eventTypes:u});if(!d&&0!==e&&null!=e){const e=new Error(r);return e.codexExecFailure={preTurnInitFailure:o,hostRetrySafe:0===u.length,message:r,sawTerminalResult:!1},void n(e)}oe&&!d&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await k(i,{...z,log:Ve,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe})}catch(e){ne=null,ae=!1,Ue.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)Je({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!oe&&!ye)return{preTurnInitFailure:!0,message:t.message};(!ge&&!ye||oe)&&Je(oe?{kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}:{kind:"error",message:t.message||e?.message||"codex failed to start",recoverable:!0})}return{preTurnInitFailure:!1}}}function it(){if(se)return;const e=qe.shift();e&&(Le=Le.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);Ke({recoveryAttempt:t}),t>0&&Ge({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:n}),firstTurn:0===Z,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!se&&!oe&&!ye;){if(!(s?await st(e.text):await ot(o,e.options)))if(s)ge=!1,Je({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{ge&&(ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await at(o,e.options);if(r?.preTurnInitFailure){if(!(i||se||oe||ye)){i=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Je({kind:"error",message:r.message,recoverable:!0})}}if(!ge||a||ye||se||oe)break;ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"})}Z++,it()}))}return V&&!Y&&queueMicrotask(()=>Ve("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(K.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Me.enabled||queueMicrotask(()=>Ve(`Codex App Server disabled: ${Me.reason}`)),ke=le(()=>{const e=ie()-fe,t=v({turnActive:ae,awaitingUser:be,quietMs:e,stallMs:ce,forceStopMs:["max","ultra"].includes(Se)?pe:de,stalledSent:he,stallRetried:xe});if("none"===t)return;if("retry"===t&&!ve)return ye=!0,ge=!1,we>0?(Ge({kind:"error",recoverable:!0,message:"The safe recovery continuation also stopped responding. The original prompt was not replayed again. Switch to another model or provider, then continue from the current workspace state."}),Ge({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Ue.size>0?Ge({kind:"error",recoverable:!0,message:"Codex stopped responding while a tool was still running. The transport was stopped, but no automatic continuation was started because that could overlap an uncertain side effect. Inspect the current state before continuing manually."}):(qe.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:ee++,forceFullReminder:!0}),Ge({kind:"error",recoverable:!0,message:"Codex stopped responding after the turn began. The transport was stopped. A new continuation turn will inspect the current state; the original prompt was not replayed."})),Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void nt({force:!0});const r=w(t,e);if(r&&Ge(r),"status"!==t){if("retry"===t)return xe=!0,ge=!0,fe=ie(),he=!1,void nt();ye=!0,ge=!1,qe.length=0,Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),nt({force:!0})}else he=!0},me),ke.unref?.(),{get sessionId(){return Q},get usageSnapshot(){return Ce},get turnActive(){return ae||Ae},get canSteer(){return!(!Ee||!1===Ee.alive||!Fe||Te)},get started(){return ae||Ae||!!ne||!(!Ee||!1===Ee.alive)},sendTurn(t,r={}){if(se)return!1;if(!ae&&0===qe.length&&!Fe&&!ne){const e="function"==typeof X?X():{ok:!0};if(!1===e?.ok)return He({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=ee++,o=te;if(te=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!ae&&0===Z&&U)try{e=U()||e}catch{}if(ae&&Ee&&Re){Xe();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Re;return $e=$e.then(()=>Ee.steer({threadId:Q,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||1!==qe.length||it()):He({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||Ae||(oe=!1,Ke({recoveryAttempt:0})),1!==qe.length||Ae||it(),!0},async park(){if(se||ae||Ae||qe.length)return!1;const e=Le;try{await e}catch{}try{await $e}catch{}if(se||ae||Ae||qe.length||ne||Re)return!1;const t=Ee;if(t){Ne.add(t),Ee=null,Fe=!1,De.clear(),je.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Le,t=ae&&!Re&&!ne;oe=!0,ge=!1,ye=!1,qe.length=0,Ye(Re);let r=Promise.resolve();if(Ee&&Re&&(r=Ee.interrupt({threadId:Q,turnId:Re}).catch(()=>{})),ne){try{ne.kill("SIGTERM")}catch{}setTimeout(()=>{try{ne?.kill("SIGKILL")}catch{}},1500)}else t&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(se=!0,ae=!1,ke&&ue(ke),He.cancel(),qe.length=0,Ye(Re),ne)try{ne.kill()}catch{}try{Ee?.end()}catch{}Ie?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||ae)return!1;_e=String(e),ze.setModel(_e);const t=C(Se,{models:p,model:_e});return t!==Se&&(Se=t,He({kind:"effort",level:Se})),!0},setMode:e=>!(ae||!CODEX_MODE_CONFIG[e]||(B=e,W=codexConfigForMode(B),M=W.sandbox,Q=null,Z=0,Ce=null,Fe=!1,0)),setEffort:e=>!(ae||!S({models:p,model:_e}).includes(e)||(Se=e,He({kind:"effort",level:Se}),0)),clearContext:()=>!ae&&(Q=null,Z=0,Ce=null,Fe=!1,!0),async compactContext(){if(ae||Ae)return!1;oe=!1,Ae=!0;let e=null,t=!1;try{if(!await rt()||ae)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Oe=null,r(new Error("Codex did not start the compaction turn"))},5e3),Oe={resolve:t,reject:r}});await Ee.compact({threadId:Q}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ee.waitForTurn(n);Ye(o?.turn?.id||n);const s=o?.turn?.status;if("completed"===s)return!0;throw new Error(o?.turn?.error?.message||`compaction ${s||"failed"}`)}catch(e){return t||!["rejected","not_sent"].includes(e?.delivery)?(Ve(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Ve(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Oe=null,Ae=!1,qe.length&&it()}},async accountUsage(){if(!await rt())return null;const[e,t]=await Promise.all([Ee.accountUsage().catch(()=>null),Ee.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.420",
3
+ "version": "0.7.421",
4
4
  "description": "Connect Claude Code, Codex, or Hermes on your computer to a thinkpool Code room.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  "terminal-name.mjs",
30
30
  "claude-command-catalog.mjs",
31
31
  "codex-session.mjs",
32
+ "codex-host-heal.mjs",
32
33
  "question-response.mjs",
33
34
  "thinkpool-room-prompt.mjs",
34
35
  "thinkpool-prompt-contracts.mjs",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": 1,
3
3
  "package": "thinkpool-pair",
4
- "version": "0.7.420",
4
+ "version": "0.7.421",
5
5
  "kind": "minified",
6
- "minifiedModules": 141,
7
- "sourceModuleBytes": 1861854,
8
- "artifactModuleBytes": 849570,
6
+ "minifiedModules": 142,
7
+ "sourceModuleBytes": 1868938,
8
+ "artifactModuleBytes": 853555,
9
9
  "files": {
10
- "package.json": "e7d31b086d9da77a674d9be393de4fc8328e7d895173ecf0ad417851510ef389",
10
+ "package.json": "6a81a0a027680c52f891c2a493fbd55ee816f605f58a78d75db3e8be9d4987cb",
11
11
  "LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
12
- "bridge.mjs": "e5a1ccea71b516549814936808830ec70a5efefd9bbf0190ba8db23504428ebe",
12
+ "bridge.mjs": "2cd1640b61ca6cd2f6e21122d650f9342450debe4861bf9ce024f9096271e9b6",
13
13
  "bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
14
14
  "command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
15
15
  "abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
@@ -28,13 +28,14 @@
28
28
  "claude-session.mjs": "62af55078e681c94cff4847d7741049630211679c5dd46fee1b60c4811daf852",
29
29
  "terminal-name.mjs": "f39bb8d1e148ab9a74cbb96438d31e524d6597b8035f00d7a906c28c306c0cf8",
30
30
  "claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
31
- "codex-session.mjs": "38c84d43680066b1a5f724575a9bade87bd14a8020bad030ec9990f75fdaa3be",
31
+ "codex-session.mjs": "296f68c90de2ec9631246c82c8f2736c2f7c4ffc9b79ba5f30577375b5e90d66",
32
+ "codex-host-heal.mjs": "14ea191ed2072203be2edd40a41272f176aafc044753919f65b1071ce48f060f",
32
33
  "question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
33
34
  "thinkpool-room-prompt.mjs": "b8cc82ccf4262f6431c2047bbff31ac29c47a0d6c68a64640a2c24d94df4a746",
34
35
  "thinkpool-prompt-contracts.mjs": "051602d7d2fe9b34df92957bd3922c5eeebedbcc60c892adf415243887d8c62f",
35
36
  "thinkpool-capabilities.json": "e56254ae9ae0c3091d122514047b1d3895e023f1a8d37f839c5d3c89326dc8fe",
36
37
  "room-file-delivery.mjs": "a33300b7f12152e8d016771aabbd4616896dec4bf7a3762e059d271e107a1b6a",
37
- "codex-app-server.mjs": "d50e30ba6cb2a383c90d89186bc7a13f4caf900c977dd501572468986009470e",
38
+ "codex-app-server.mjs": "f032cc1f4e1ecd25d165901c0eaf7037da01f0913f08badc43711c9ec79e5467",
38
39
  "codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
39
40
  "codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
40
41
  "lane-worktree.mjs": "9dd70a4f089f8867ad5d6a77e912734dbb396c327c0231b7fae883f7603d5ea2",