thinkpool-pair 0.7.430 → 0.7.432

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";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)}
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=[],serviceTier:s}={}){const o=[...i(),"app-server","--stdio"];return e&&o.push("-c",e),s&&(o.push("-c",`service_tier=${JSON.stringify(s)}`),"fast"===s&&o.push("-c","features.fast_mode=true")),t&&(o.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(t)}`),o.push("-c","mcp_servers.thinkpool.required=true")),r.length&&o.push("-c",`sandbox_workspace_write.writable_roots=${JSON.stringify(r)}`),o}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,serviceTier:i,sandbox:s,approvalPolicy:o,config:n}={}){const a={cwd:t,model:r,...i?{serviceTier:i}:{},sandbox:s,approvalPolicy:o,...n?{config:n}:{}},d=e?await this.request("thread/resume",{...a,threadId:e},this.threadTimeoutMs):await this.request("thread/start",a,this.threadTimeoutMs),c=d?.thread?.id||d?.threadId||e;if(!c)throw new Error("Codex App Server did not return a thread id");return c}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,serviceTier:s,effort:o,approvalPolicy:n,collaborationMode:d}={}){const c=await this.request("turn/start",{threadId:e,input:a(t,r),...i?{model:i}:{},...s?{serviceTier:s}:{},...o?{effort:o}:{},...n?{approvalPolicy:n}:{},...d?{collaborationMode:d}:{}},this.turnStartTimeoutMs),l=c?.turn?.id||c?.turnId;if(!l)throw new Error("Codex App Server did not return a turn id");return l}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)}
@@ -1 +1 @@
1
- export const CODEX_COMMAND_CATALOG=Object.freeze([]);export const CODEX_COMMAND_MATRIX=Object.freeze([Object.freeze({name:"/compact",surface:!0,runtime:"native",api:"thread/compact/start",reason:"Native App Server compaction; bounded-recap reset remains the exec fallback."}),Object.freeze({name:"/model",surface:!0,runtime:"bridge",api:"model/list",reason:"Shared picker owns the model argument."}),Object.freeze({name:"/clear",surface:!0,runtime:"bridge",api:null,reason:"Shared reset clears visible lane history and starts a fresh native thread."}),Object.freeze({name:"/usage",surface:!0,runtime:"bridge+native",api:"account/usage/read + account/rateLimits/read",reason:"Reports lane usage plus redacted provider totals and limits."}),Object.freeze({name:"/credits",surface:!0,runtime:"native",api:"account/rateLimits/read",reason:"Reports balance/availability only; never account identity."}),Object.freeze({name:"/review",surface:!0,runtime:"native",api:"review/start",reason:"Ordinary lanes only; Flow and reviewer structural restrictions win."})]);export const CODEX_COMMAND_EXCLUSIONS=Object.freeze(["profile-global configuration, skills, plugins, marketplaces, and experimental features","updates, diagnostics/feedback upload, and external-agent import","credentials, login/logout, identity, and account mutation","thread archive/delete/fork/name/goal/rollback and hidden-agent controls","MCP OAuth, filesystem, shell, and interactive command-exec protocol methods","detached review and review from Flow/reviewer lanes, because visible structural restrictions win"]);export function isCodexCompactCommand(e){return/^\s*\/compact\s*$/i.test(String(e||""))}export function codexReviewTarget(e){const t=String(e||"").replace(/^\s*\/review\b/i,"").trim();if(!t)return{type:"uncommittedChanges"};const r=t.match(/^base\s*:\s*(\S+)$/i);if(r)return{type:"baseBranch",branch:r[1]};const a=t.match(/^commit\s*:\s*([a-f0-9]{7,64})$/i);return a?{type:"commit",sha:a[1]}:{type:"custom",instructions:t}}const e=e=>Number.isFinite(Number(e))?`${Math.round(Number(e))}%`:null,t=e=>Number.isFinite(Number(e))?new Date(1e3*Number(e)).toISOString().replace("T"," ").slice(0,16)+"Z":null;export function codexLimitReportLine(r){const a=r?.rateLimits||null;if(!a)return"Codex provider limits unavailable";const n=[];return null!=a.primary?.usedPercent&&n.push(`primary ${e(a.primary.usedPercent)}${t(a.primary.resetsAt)?` · resets ${t(a.primary.resetsAt)}`:""}`),null!=a.secondary?.usedPercent&&n.push(`secondary ${e(a.secondary.usedPercent)}${t(a.secondary.resetsAt)?` · resets ${t(a.secondary.resetsAt)}`:""}`),n.length?`Codex limits · ${n.join(" · ")}`:"Codex provider limits unavailable"}export function codexCreditsReportLine(e){const t=e?.rateLimits?.credits;return t?t.unlimited?"Codex credits · unlimited":null!=t.balance?`Codex credits · balance ${String(t.balance)}`:"Codex credits · "+(t.hasCredits?"available":"none available"):"Codex credits unavailable for this account"}export function codexAccountUsageLine(e){const t=e?.summary?.lifetimeTokens;return Number.isFinite(Number(t))?`Codex account usage · ${Number(t).toLocaleString("en-US")} lifetime tokens`:null}
1
+ export const CODEX_COMMAND_CATALOG=Object.freeze([]);export const CODEX_COMMAND_MATRIX=Object.freeze([Object.freeze({name:"/compact",surface:!0,runtime:"native",api:"thread/compact/start",reason:"Native App Server compaction; bounded-recap reset remains the exec fallback."}),Object.freeze({name:"/model",surface:!0,runtime:"bridge",api:"model/list",reason:"Shared picker owns the model argument."}),Object.freeze({name:"/clear",surface:!0,runtime:"bridge",api:null,reason:"Shared reset clears visible lane history and starts a fresh native thread."}),Object.freeze({name:"/usage",surface:!0,runtime:"bridge+native",api:"account/usage/read + account/rateLimits/read",reason:"Reports lane usage plus redacted provider totals and limits."}),Object.freeze({name:"/credits",surface:!0,runtime:"native",api:"account/rateLimits/read",reason:"Reports balance/availability only; never account identity."}),Object.freeze({name:"/fast",surface:!0,runtime:"bridge+native",api:"thread/start + turn/start serviceTier",reason:"Shared speed control owns the per-lane service tier without changing global Codex config."}),Object.freeze({name:"/review",surface:!0,runtime:"native",api:"review/start",reason:"Ordinary lanes only; Flow and reviewer structural restrictions win."})]);export const CODEX_COMMAND_EXCLUSIONS=Object.freeze(["profile-global configuration, skills, plugins, marketplaces, and experimental features","updates, diagnostics/feedback upload, and external-agent import","credentials, login/logout, identity, and account mutation","thread archive/delete/fork/name/goal/rollback and hidden-agent controls","MCP OAuth, filesystem, shell, and interactive command-exec protocol methods","detached review and review from Flow/reviewer lanes, because visible structural restrictions win"]);export function isCodexCompactCommand(e){return/^\s*\/compact\s*$/i.test(String(e||""))}export function codexReviewTarget(e){const t=String(e||"").replace(/^\s*\/review\b/i,"").trim();if(!t)return{type:"uncommittedChanges"};const r=t.match(/^base\s*:\s*(\S+)$/i);if(r)return{type:"baseBranch",branch:r[1]};const a=t.match(/^commit\s*:\s*([a-f0-9]{7,64})$/i);return a?{type:"commit",sha:a[1]}:{type:"custom",instructions:t}}const e=e=>Number.isFinite(Number(e))?`${Math.round(Number(e))}%`:null,t=e=>Number.isFinite(Number(e))?new Date(1e3*Number(e)).toISOString().replace("T"," ").slice(0,16)+"Z":null;export function codexLimitReportLine(r){const a=r?.rateLimits||null;if(!a)return"Codex provider limits unavailable";const n=[];return null!=a.primary?.usedPercent&&n.push(`primary ${e(a.primary.usedPercent)}${t(a.primary.resetsAt)?` · resets ${t(a.primary.resetsAt)}`:""}`),null!=a.secondary?.usedPercent&&n.push(`secondary ${e(a.secondary.usedPercent)}${t(a.secondary.resetsAt)?` · resets ${t(a.secondary.resetsAt)}`:""}`),n.length?`Codex limits · ${n.join(" · ")}`:"Codex provider limits unavailable"}export function codexCreditsReportLine(e){const t=e?.rateLimits?.credits;return t?t.unlimited?"Codex credits · unlimited":null!=t.balance?`Codex credits · balance ${String(t.balance)}`:"Codex credits · "+(t.hasCredits?"available":"none available"):"Codex credits unavailable for this account"}export function codexAccountUsageLine(e){const t=e?.summary?.lifetimeTokens;return Number.isFinite(Number(t))?`Codex account usage · ${Number(t).toLocaleString("en-US")} lifetime tokens`:null}
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 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 I,normalizeReasoningEffortList as C}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:C(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:C,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,...C||{}}}),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=I(h,{models:p,model:_e}),Ie=Q?readCodexThreadUsage(Q):null,Ce=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&&(Ie=t),t||Ie}}),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?(Ce||(Ce=q({sdkServer:e})),Ce):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(Ie=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&&!0!==e?.codexExecFailure?.preTurnInitFailure})}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 Ie},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{}Ce?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||ae)return!1;_e=String(e),ze.setModel(_e);const t=I(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,Ie=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,Ie=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}}}}
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 S}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as _,codexReasoningEfforts as C,normalizeCodexReasoningEffort as k,normalizeReasoningEffortList as M}from"./reasoning-effort.mjs";const T=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 I=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 S(()=>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 normalizeCodexServiceTier(e){const t=String(e||"").trim().toLowerCase();return"priority"===t?"fast":"fast"===t||"default"===t?t:null}export function readCodexDefaultServiceTier({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*service_tier\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return normalizeCodexServiceTier(r?.[1])}catch{return null}}const E=e=>[...new Set((Array.isArray(e)?e:[]).map(e=>normalizeCodexServiceTier("string"==typeof e?e:e?.id)).filter(Boolean))];export function codexModelSupportsFastMode({models:e=[],model:t}={}){const r=(Array.isArray(e)?e:[]).find(e=>e?.value===t);return E(r?.serviceTiers).includes("fast")}const F=e=>"fast"===normalizeCodexServiceTier(e)?"priority":normalizeCodexServiceTier(e);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:M(e.supported_reasoning_levels),serviceTiers:E(e.service_tiers||e.additional_speed_tiers)}))}catch{return[]}}const N=new Map;function O(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=N.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 N.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=O(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=O(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 T.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,serviceTier:r,effort:n,sandbox:s,approvalPolicy:a="untrusted",providerConfig:i,mcpUrl:l,writableDirs:u=[],images:c=[],prompt:d}){const p=normalizeCodexSandbox(s),m=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===p?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${p}"`]]:["exec","--json","--sandbox",p,"--skip-git-repo-check"]];t&&m.push("-m",t);const f=normalizeCodexServiceTier(r);f&&(m.push("-c",`service_tier=${JSON.stringify(f)}`),"fast"===f&&m.push("-c","features.fast_mode=true")),m.push("-c",`approval_policy="${a}"`);const h=_(n);if(h&&m.push("-c",`model_reasoning_effort="${h}"`),i&&m.push("-c",i),!e)for(const e of u)m.push("--add-dir",e);l&&(m.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(l)}`),m.push("-c","mcp_servers.thinkpool.required=true"));let x=!1;for(const e of c)o.isAbsolute(String(e||""))&&(m.push("--image",String(e)),x=!0);return x&&m.push("--"),e&&m.push(e),m.push(String(d??"")),m}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 R=/\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:R.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 A(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=[],serviceTier:h=null,effort:b="high",resume:M,env:T,sandbox:E,mode:N="default",onEvent:O,providerConfig:R,terminalRolePrompt:P,rolePrompt:D,roomContext:U,mcpServers:j,prepareCwd:$=null,requestPermission:L,appServerGate:q=i,appServerFactory:z=l,mcpHttpFactory:G=c,spawnImpl:X=t,admitStart:H=null,stallOptions:B={},codexHost:W={}}){let J=CODEX_MODE_CONFIG[N]?N:"default",K=codexConfigForMode(J);E=normalizeCodexSandbox(E||K.sandbox);const V=codexPublishAccess({cwd:e,env:{...process.env,...T||{}}}),Y=V.env,Q=M||null,Z=!Q||codexThreadCanResume(Q,{home:Y.CODEX_HOME||o.join(n.homedir(),".codex")});let ee=Z?Q:null,te=ee?1:0,re=0,ne=!0;const oe=m(U);let se=null,ae=!1,ie=!1,le=!1;const ue="function"==typeof B.now?B.now:Date.now,ce="function"==typeof B.setInterval?B.setInterval:setInterval,de="function"==typeof B.clearInterval?B.clearInterval:clearInterval,pe=Number.isFinite(B.stallMs)?Math.max(1,Number(B.stallMs)):Math.max(3e4,parseInt(Y.TP_STALL_MS,10)||9e4),me=Number.isFinite(B.forceStopMs)?Math.max(pe,Number(B.forceStopMs)):Math.max(3*pe,parseInt(Y.TP_FORCE_STOP_MS,10)||3e5),fe=Number.isFinite(B.longReasoningForceStopMs)?Math.max(me,Number(B.longReasoningForceStopMs)):Math.max(me,parseInt(Y.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),he=Number.isFinite(B.intervalMs)?Math.max(1,Number(B.intervalMs)):5e3;let xe=ue(),ge=!1,ye=!1,ve=!1,we=!1,be=!0,Se=0,_e=0,Ce=null,ke=d||null,Me=normalizeCodexServiceTier(h);"fast"!==Me||codexModelSupportsFastMode({models:p,model:ke})||(Me="default");let Te=k(b,{models:p,model:ke}),Ie=ee?readCodexThreadUsage(ee):null,Ee=null;const Fe=q({env:Y});let Ne=!Fe.enabled,Oe=null,Re=!1;const Ae=new WeakSet;let Pe=null,De=null,Ue=!1;const je=new Set,$e=new Map,Le=new Set,qe=new Map;let ze=Promise.resolve(),Ge=Promise.resolve();const Xe=[],He=y(e=>{try{O?.(e)}catch{}}),Be=()=>{xe=ue(),ge=!1},We=e=>{Be(),He(e)};We.cancel=()=>He.cancel();const Je=new u({onEvent:We,model:d||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Ie=t),t||Ie}}),Ke=()=>{const e=[...qe.values()];qe.clear();for(const t of e)t.text&&We({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},Ve=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Ke(),le=!1),Je.push(e)},Ye=e=>{Ke(),le=!1,We(e)},Qe=({retry:e=!1,recoveryAttempt:t=0}={})=>{le=!0,xe=ue(),ge=!1,ve=!1,we=!1,be=!0,Se=Math.max(0,Number(t)||0),e||(ye=!1)},Ze=e=>We({kind:"note",text:e}),et=(e=Pe)=>{const t=String(e||"");if(t)for(je.add(t);je.size>64;)je.delete(je.values().next().value)},tt=(e={})=>{const t=String(e.turnId||"");return!(!t||ie||ae||je.has(t)||t!==String(Pe||""))};async function rt(){const e=j?.thinkpool;return e?(Ee||(Ee=G({sdkServer:e})),Ee):null}function nt(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&ee&&String(t.threadId)!==String(ee))return;(le||Ue)&&Be();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ie=e,Ue){const{used:t,max:r}=e.context;We({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:ke}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(ie||ae)return void et(e);if(De){const t=De;return De=null,void t.resolve(e)}return Pe||(Pe=e),void Be()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||tt(t)){if(Be(),"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 We({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||Pe||"active"}`,r=qe.get(e)||{cid:`codex-stream:${ee||"thread"}:${e}`,text:""};return r.text+=String(t.delta),qe.set(e,r),void We({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&&($e.set(r.id,r),r.id&&I(r)&&Le.add(r.id)),"item/completed"===e&&(r.id&&Le.delete(r.id),$e.set(r.id,r),"agentMessage"===r.type&&qe.has(r.id))){const e=qe.get(r.id);return qe.delete(r.id),void We({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void Ve({type:"item/started"===e?"item.started":"item.completed",item:A(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";Ve({type:"error",message:e})}}}async function ot(e,t={}){if(!tt(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,$e.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";_e++;try{n=await(L?.(r))}catch{}finally{_e=Math.max(0,_e-1),Be()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function st(){if(Ne)return!1;if(!1===Oe?.alive)return Re=!1,Ne=!0,!1;if(Oe&&Re)return!0;let t;try{t=await rt()}catch(e){Ze(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Oe){const r=z({cwd:e,env:Y,args:a({providerConfig:R,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(J,E,V.writableDirs),serviceTier:Me}),onNotification:nt,onServerRequest:ot,codexHost:{...W,log:Ze},onClose:e=>{ie||Ne||Ae.has(r)||(Re=!1,Ne=!0,Ze(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Oe=r,await Oe.start(),Be()}return ee=await Oe.startThread({threadId:te>0?ee:null,cwd:e,model:ke,serviceTier:F(Me),sandbox:E,approvalPolicy:K.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Be(),t?.url&&(await Oe.waitForMcpServer({threadId:ee,name:"thinkpool"}),Be()),Re=!0,Ve({type:"thread.started",thread_id:ee}),!0}catch{Ne=!0;try{Oe?.end()}catch{}return Oe=null,Re=!1,!1}}function at({force:e=!1}={}){const t=Oe,r=Pe;if(t&&e){et(r),Ne=!0,Re=!1;try{t.end()}catch{}Oe===t&&(Oe=null)}else if(t&&r)et(r),Promise.resolve(t.interrupt({threadId:ee,turnId:r})).catch(()=>{Ne=!0,Re=!1;try{t.end()}catch{}Oe===t&&(Oe=null)});else if(t){Ne=!0,Re=!1;try{t.end()}catch{}Oe===t&&(Oe=null)}if(se){const e=se;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(se===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function it(e,t={}){if(ie||ae||we)return!0;if(!await st())return!1;if(ie||ae||we)return!0;Je.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{if(be=!1,Pe=await Oe.startTurn({threadId:ee,input:e,images:Array.isArray(t.images)?t.images:[],model:ke,serviceTier:F(Me),effort:_(Te),approvalPolicy:K.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(ke,Te)}),Be(),ae||ie){et(Pe);try{await Oe.interrupt({threadId:ee,turnId:Pe})}catch{}return!0}const r=await Oe.waitForTurn(Pe);et(r?.turn?.id||Pe);const n=r?.turn?.status;ae?Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ve||we)||("interrupted"===n?Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ve=!1,Ve({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(ve=!1,Ve({type:"turn.completed"})))}catch(e){Ne=!0,Re=!1,et(Pe);try{Oe?.end()}catch{}Oe=null,ie||ve||we||Ve({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Ke(),Pe=null,le=!1,Le.clear(),$e.clear(),qe.clear()}return!0}async function lt(e){if(ie||ae||we)return!0;if(!await st())return!1;if(ie||ae||we)return!0;Je.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{be=!1;const t=await Oe.startReview({threadId:ee,target:x(e)});if(Pe=t?.turn?.id||t?.turnId,!Pe)throw new Error("Codex review/start returned no turn id");if(Be(),ae||ie){et(Pe);try{await Oe.interrupt({threadId:ee,turnId:Pe})}catch{}return!0}const r=await Oe.waitForTurn(Pe);et(r?.turn?.id||Pe);const n=r?.turn?.status;ae?Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ve||we)||("interrupted"===n?Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ve=!1,Ve({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(ve=!1,Ve({type:"turn.completed"})))}catch(e){Ne=!0,Re=!1,et(Pe);try{Oe?.end()}catch{}Oe=null,ie||ve||we||Ve({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Ke(),Pe=null,le=!1,Le.clear(),$e.clear(),qe.clear()}return!0}async function ut(t,n={}){if(ie||ae||we)return;let o;try{o=await rt()}catch(e){Ze(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Ze(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===te?null:ee,model:ke,serviceTier:Me,effort:Te,sandbox:E,approvalPolicy:K.approvalPolicy,providerConfig:R,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(J,E,V.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});Je.setUsageBaseline(ee?readCodexThreadUsage(ee):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:Y,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),le=!0,be=!1;try{se=X("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return le=!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=se,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}Be(),u.push(t.type),"item.started"===t.type&&t.item?.id&&I(t.item)&&Le.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Le.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(ee=ee||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(d=!0,ve=!1),Ve(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,se===l&&(se=null),le=!1,Le.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,se===l&&(se=null),le=!1,Le.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)}ae&&!d&&Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await S(i,{...W,log:Ze,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe&&!0!==e?.codexExecFailure?.preTurnInitFailure})}catch(e){se=null,le=!1,Le.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)Ye({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!ae&&!we)return{preTurnInitFailure:!0,message:t.message};(!ve&&!we||ae)&&Ye(ae?{kind:"result",subtype:"aborted",sessionId:ee,model:ke,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 ct(){if(ie)return;const e=Xe.shift();e&&(Ge=Ge.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);Qe({recoveryAttempt:t}),t>0&&He({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:P,rolePrompt:D,roomContext:()=>oe({force:n}),firstTurn:0===te,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!ie&&!ae&&!we;){if(!(s?await lt(e.text):await it(o,e.options)))if(s)ve=!1,Ye({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{ve&&(ve=!1,a=!0,Qe({retry:!0,recoveryAttempt:t}),He({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await ut(o,e.options);if(r?.preTurnInitFailure){if(!(i||ie||ae||we)){i=!0,Qe({retry:!0,recoveryAttempt:t}),He({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Ye({kind:"error",message:r.message,recoverable:!0})}}if(!ve||a||we||ie||ae)break;ve=!1,a=!0,Qe({retry:!0,recoveryAttempt:t}),He({kind:"note",text:"retrying the stalled turn on a fresh connection"})}te++,ct()}))}return Q&&!Z&&queueMicrotask(()=>Ze("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(Y.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Fe.enabled||queueMicrotask(()=>Ze(`Codex App Server disabled: ${Fe.reason}`)),Ce=ce(()=>{const e=ue()-xe,t=v({turnActive:le,awaitingUser:_e,quietMs:e,stallMs:pe,forceStopMs:["max","ultra"].includes(Te)?fe:me,stalledSent:ge,stallRetried:ye});if("none"===t)return;if("retry"===t&&!be)return we=!0,ve=!1,Se>0?(He({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."}),He({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Le.size>0?He({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."}):(Xe.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:re++,forceFullReminder:!0}),He({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."})),Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void at({force:!0});const r=w(t,e);if(r&&He(r),"status"!==t){if("retry"===t)return ye=!0,ve=!0,xe=ue(),ge=!1,void at();we=!0,ve=!1,Xe.length=0,Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),at({force:!0})}else ge=!0},he),Ce.unref?.(),{get sessionId(){return ee},get usageSnapshot(){return Ie},get turnActive(){return le||Ue},get canSteer(){return!(!Oe||!1===Oe.alive||!Re||Ne)},get started(){return le||Ue||!!se||!(!Oe||!1===Oe.alive)},sendTurn(t,r={}){if(ie)return!1;if(!le&&0===Xe.length&&!Re&&!se){const e="function"==typeof H?H():{ok:!0};if(!1===e?.ok)return We({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=re++,o=ne;if(ne=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!le&&0===te&&$)try{e=$()||e}catch{}if(le&&Oe&&Pe){Be();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:P,rolePrompt:D,roomContext:()=>oe({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Pe;return ze=ze.then(()=>Oe.steer({threadId:ee,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(Xe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),le||1!==Xe.length||ct()):We({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return Xe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),le||Ue||(ae=!1,Qe({recoveryAttempt:0})),1!==Xe.length||Ue||ct(),!0},async park(){if(ie||le||Ue||Xe.length)return!1;const e=Ge;try{await e}catch{}try{await ze}catch{}if(ie||le||Ue||Xe.length||se||Pe)return!1;const t=Oe;if(t){Ae.add(t),Oe=null,Re=!1,$e.clear(),qe.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Ge,t=le&&!Pe&&!se;ae=!0,ve=!1,we=!1,Xe.length=0,et(Pe);let r=Promise.resolve();if(Oe&&Pe&&(r=Oe.interrupt({threadId:ee,turnId:Pe}).catch(()=>{})),se){try{se.kill("SIGTERM")}catch{}setTimeout(()=>{try{se?.kill("SIGKILL")}catch{}},1500)}else t&&Ye({kind:"result",subtype:"aborted",sessionId:ee,model:ke,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(ie=!0,le=!1,Ce&&de(Ce),We.cancel(),Xe.length=0,et(Pe),se)try{se.kill()}catch{}try{Oe?.end()}catch{}Ee?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||le)return!1;ke=String(e),Je.setModel(ke);const t=k(Te,{models:p,model:ke});return t!==Te&&(Te=t,We({kind:"effort",level:Te})),"fast"!==Me||codexModelSupportsFastMode({models:p,model:ke})||(Me="default"),!0},setServiceTier(e){if(le||Ue||Xe.length)return!1;const t=normalizeCodexServiceTier(e);if(!t)return!1;if("fast"===t&&!codexModelSupportsFastMode({models:p,model:ke}))return!1;if(t===Me)return!0;Me=t;const r=Oe;if(r){Ae.add(r),Oe=null,Re=!1,$e.clear(),qe.clear();try{r.end()}catch{}}return!0},setMode:e=>!(le||!CODEX_MODE_CONFIG[e]||(J=e,K=codexConfigForMode(J),E=K.sandbox,ee=null,te=0,Ie=null,Re=!1,0)),setEffort:e=>!(le||!C({models:p,model:ke}).includes(e)||(Te=e,We({kind:"effort",level:Te}),0)),get serviceTier(){return Me},get supportsFastMode(){return codexModelSupportsFastMode({models:p,model:ke})},clearContext:()=>!le&&(ee=null,te=0,Ie=null,Re=!1,!0),async compactContext(){if(le||Ue)return!1;ae=!1,Ue=!0;let e=null,t=!1;try{if(!await st()||le)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{De=null,r(new Error("Codex did not start the compaction turn"))},5e3),De={resolve:t,reject:r}});await Oe.compact({threadId:ee}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Oe.waitForTurn(n);et(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)?(Ze(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Ze(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),De=null,Ue=!1,Xe.length&&ct()}},async accountUsage(){if(!await st())return null;const[e,t]=await Promise.all([Oe.accountUsage().catch(()=>null),Oe.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
@@ -1 +1 @@
1
- const e=(e,t,n,i,r=["claude","codex","hermes","opencode"])=>Object.freeze({name:e,description:t,route:n,...i?{inputHint:i}:{},runtimes:Object.freeze(r)});export const CODE_ROOM_COMMANDS=Object.freeze([e("/handoff","save a concise checkpoint or handoff","handoff","optional end goal"),e("/side","ask or investigate without interrupting this terminal","side","task"),e("/help","list commands available in this lane","control"),e("/status","runtime, model, permissions, and busy state","control"),e("/usage","session usage and provider limits","control"),e("/context","current context-window usage","control"),e("/diff","Git changes and branch status","control"),e("/find","find relevant files in this repository","control","query"),e("/compact","compact context","runtime"),e("/clear","clear context · confirms","clear"),e("/model","pick model — opens selector","model"),e("/mode","set permission mode","mode","normal | auto-accept | plan | bypass"),e("/effort","set reasoning effort","effort","none | minimal | low | medium | high | xhigh | max | ultra"),e("/queue","run a prompt after the active turn","queue","prompt"),e("/steer","guide the active turn","steer","prompt",["codex","hermes"]),e("/credits","provider credit balance","credits",null,["codex","hermes"]),e("/review","review uncommitted changes","runtime",null,["codex"])]);const t=Object.freeze({claude:Object.freeze({accept:!0,hidden:Object.freeze(new Set(["/flow"]))}),codex:Object.freeze({accept:!1,hidden:Object.freeze(new Set(["/flow"]))}),hermes:Object.freeze({accept:!0,hidden:Object.freeze(new Set(["/flow","/reasoning"]))})}),n=e=>{const t=(e=>{const t=`/${String(("string"==typeof e?e:e?.name)||"").trim().replace(/^\/+/,"")}`;return/^\/[A-Za-z0-9][A-Za-z0-9:_-]*$/.test(t)?t:""})(e);if(!t)return null;if("string"==typeof e)return{name:t,description:"runtime command",route:"runtime"};const n=String(e?.description||"").trim()||"runtime command",i=String(e?.inputHint||e?.input_hint||e?.input?.hint||"").trim();return{name:t,description:n,route:String(e?.route||"").trim()||"runtime",...i?{inputHint:i}:{}}};export function commandCatalogForRuntime(e,i=[]){const r=(e=>"thinkpool"===e?"hermes":e)(e),o=t[r]||t.codex,c=new Map;for(const e of CODE_ROOM_COMMANDS)e.runtimes.includes(r)&&c.set(e.name,{name:e.name,description:e.description,route:e.route,...e.inputHint?{inputHint:e.inputHint}:{}});for(const e of Array.isArray(i)?i:[]){const t=n(e);if(!t||!o.accept||o.hidden.has(t.name))continue;const i=c.get(t.name),s="hermes"===r&&["/help","/status","/context","/credits"].includes(t.name);c.set(t.name,i?{...t,...i,description:"runtime command"===t.description?i.description:t.description,...t.inputHint?{inputHint:t.inputHint}:{},...s?{route:"runtime"}:{}}:t)}return[...c.values()]}export const commandNeedsInput=e=>!!String(e?.inputHint||"").trim();export const commandRoute=e=>String(e?.route||"runtime");export function commandHelpLine(e){return(Array.isArray(e)?e:[]).map(e=>`${e.name}${e.inputHint?` <${e.inputHint}>`:""}`).join(" ")}export function normalizePermissionMode(e){const t=String(e||"").trim().toLowerCase().replace(/[\s_-]+/g,"");return["normal","default","manual"].includes(t)?"default":["auto","autoaccept","autoacceptedits","acceptedits"].includes(t)?"acceptEdits":"plan"===t?"plan":["bypass","bypasspermissions","fullauto"].includes(t)?"bypassPermissions":null}export function reconcileCommandCatalog({current:e,incoming:t,onChanged:n,onLifecycle:i}={}){const r=Array.isArray(t)&&t.length>0,o=r&&JSON.stringify(e||[])===JSON.stringify(t);return r&&!o&&n?.(t),i?.({duplicate:o,hasIncoming:r}),o}
1
+ const e=(e,t,n,i,r=["claude","codex","hermes","opencode"])=>Object.freeze({name:e,description:t,route:n,...i?{inputHint:i}:{},runtimes:Object.freeze(r)});export const CODE_ROOM_COMMANDS=Object.freeze([e("/handoff","save a concise checkpoint or handoff","handoff","optional end goal"),e("/side","ask or investigate without interrupting this terminal","side","task"),e("/help","list commands available in this lane","control"),e("/status","runtime, model, permissions, and busy state","control"),e("/usage","session usage and provider limits","control"),e("/context","current context-window usage","control"),e("/diff","Git changes and branch status","control"),e("/find","find relevant files in this repository","control","query"),e("/compact","compact context","runtime"),e("/clear","clear context · confirms","clear"),e("/model","pick model — opens selector","model"),e("/mode","set permission mode","mode","normal | auto-accept | plan | bypass"),e("/effort","set reasoning effort","effort","none | minimal | low | medium | high | xhigh | max | ultra"),e("/fast","set Codex speed","control","on | off | status",["codex"]),e("/queue","run a prompt after the active turn","queue","prompt"),e("/steer","guide the active turn","steer","prompt",["codex","hermes"]),e("/credits","provider credit balance","credits",null,["codex","hermes"]),e("/review","review uncommitted changes","runtime",null,["codex"])]);const t=Object.freeze({claude:Object.freeze({accept:!0,hidden:Object.freeze(new Set(["/flow"]))}),codex:Object.freeze({accept:!1,hidden:Object.freeze(new Set(["/flow"]))}),hermes:Object.freeze({accept:!0,hidden:Object.freeze(new Set(["/flow","/reasoning"]))})}),n=e=>{const t=(e=>{const t=`/${String(("string"==typeof e?e:e?.name)||"").trim().replace(/^\/+/,"")}`;return/^\/[A-Za-z0-9][A-Za-z0-9:_-]*$/.test(t)?t:""})(e);if(!t)return null;if("string"==typeof e)return{name:t,description:"runtime command",route:"runtime"};const n=String(e?.description||"").trim()||"runtime command",i=String(e?.inputHint||e?.input_hint||e?.input?.hint||"").trim();return{name:t,description:n,route:String(e?.route||"").trim()||"runtime",...i?{inputHint:i}:{}}};export function commandCatalogForRuntime(e,i=[]){const r=(e=>"thinkpool"===e?"hermes":e)(e),o=t[r]||t.codex,c=new Map;for(const e of CODE_ROOM_COMMANDS)e.runtimes.includes(r)&&c.set(e.name,{name:e.name,description:e.description,route:e.route,...e.inputHint?{inputHint:e.inputHint}:{}});for(const e of Array.isArray(i)?i:[]){const t=n(e);if(!t||!o.accept||o.hidden.has(t.name))continue;const i=c.get(t.name),s="hermes"===r&&["/help","/status","/context","/credits"].includes(t.name);c.set(t.name,i?{...t,...i,description:"runtime command"===t.description?i.description:t.description,...t.inputHint?{inputHint:t.inputHint}:{},...s?{route:"runtime"}:{}}:t)}return[...c.values()]}export const commandNeedsInput=e=>!!String(e?.inputHint||"").trim();export const commandRoute=e=>String(e?.route||"runtime");export function commandHelpLine(e){return(Array.isArray(e)?e:[]).map(e=>`${e.name}${e.inputHint?` <${e.inputHint}>`:""}`).join(" ")}export function normalizePermissionMode(e){const t=String(e||"").trim().toLowerCase().replace(/[\s_-]+/g,"");return["normal","default","manual"].includes(t)?"default":["auto","autoaccept","autoacceptedits","acceptedits"].includes(t)?"acceptEdits":"plan"===t?"plan":["bypass","bypasspermissions","fullauto"].includes(t)?"bypassPermissions":null}export function reconcileCommandCatalog({current:e,incoming:t,onChanged:n,onLifecycle:i}={}){const r=Array.isArray(t)&&t.length>0,o=r&&JSON.stringify(e||[])===JSON.stringify(t);return r&&!o&&n?.(t),i?.({duplicate:o,hasIncoming:r}),o}
@@ -1 +1 @@
1
- import{createHash as e}from"node:crypto";import{BLOCKED_REASON as t,projectLaneLifecycle as r}from"./lane-lifecycle.mjs";import{normalizeCascadeMetadata as n}from"./worker-completion.mjs";export const PEEK={defaultLines:20,maxLines:200,perTurnCap:10,rosterPerTurnCap:1,lineCap:200,rosterPreview:100};export const readTerminalBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0}={},n=PEEK)=>{const o=Math.max(0,Number(t)||0),i=Math.max(0,Number(r)||0);return e?o>=n.perTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`Cross-terminal transcript read limit reached for this turn (${n.perTurnCap}). Stop polling and continue with the worker results already collected; a fresh accepted room turn resets the allowance.`}:{ok:!0,targetedCount:o+1,rosterCount:i}:i>=n.rosterPerTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`The full terminal roster was already read this turn (${n.rosterPerTurnCap}/${n.rosterPerTurnCap}). Use the current ROOM NOW snapshot or read one relevant terminal by ref or name.`}:{ok:!0,targetedCount:o,rosterCount:i+1}};export const readTerminalTurnBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0,budgetTurnRev:n=null,currentTurnRev:o=0}={},i=PEEK)=>{const s=Math.max(0,Number(o)||0),a=(null==n?null:Number.isFinite(Number(n))?Number(n):null)===s;return{...readTerminalBudgetDecision({targeted:e,targetedCount:a?t:0,rosterCount:a?r:0},i),budgetTurnRev:s}};export const LANE_STATUS={stuckAfterMs:9e4,repeatedFailureCount:2,failureWindow:12};const o=e=>!!e&&("error"===e.kind||"tool_result"===e.kind&&e.isError||"result"===e.kind&&e.subtype&&"success"!==e.subtype);export const laneBusyOf=(e={})=>"boolean"==typeof e?.busy?e.busy:"boolean"==typeof e?._busyAnn?e._busyAnn:e?.session?.turnActive??!1;export const settleLaneBusy=e=>!(!e||!1===e._busyAnn||(e._busyAnn=!1,0));export const settleLaneControl=(e,t=Date.now())=>!(!e||!0!==e._busyAnn||!settleLaneBusy(e)||(e._settledTurnRev=Number(e._turnRev)||null,e._turnSettledAt=Number(t)||Date.now(),e._turnStart=null,0));export const laneStatusOf=(e={},n=Date.now(),i=LANE_STATUS)=>{const s=Array.isArray(e?.log)?e.log:[],a=s.slice(-i.failureWindow);!e?.lastEvent||e.lastEvent===s[s.length-1]||e.lastEvent.cid&&e.lastEvent.cid===s[s.length-1]?.cid||a.push(e.lastEvent);let l=null;for(let e=a.length-1;e>=0;e--)if(Number.isFinite(a[e]?.ts)){l=a[e].ts;break}const c=Number.isFinite(e?.lastActionAt)?e.lastActionAt:l??null,d=null==c?null:Math.max(0,n-c),p=laneBusyOf(e),u=e?.pending instanceof Map?e.pending.size:Math.max(0,Number(e?.pendingCount)||0),m=e?.pending instanceof Map&&[...e.pending.values()].some(e=>"ask"===e?.payload?.risk)?t.NEEDS_INPUT:t.NEEDS_DECISION,h=((e,t=LANE_STATUS)=>{let r=0,n=0;for(let i=(Array.isArray(e)?e.length:0)-1;i>=0&&n<t.failureWindow;i--){const t=e[i];if(t&&"object"==typeof t){if(n++,"result"===t.kind&&(!t.subtype||"success"===t.subtype)||"you"===t.kind)break;o(t)&&r++}}return r})(a,i);let f=null,g=null;return u>0?(f="BLOCKED",g="waiting for input"):e?.stalled||p&&!e?.compacting&&null!=d&&d>=i.stuckAfterMs?(f="STUCK",g="no events while busy"):h>=i.repeatedFailureCount&&(f="BLOCKED",g="repeated errors"),{busy:!!p,lastActionAt:c,lastActionAgeMs:d,statusFlag:f,statusReason:g,lifecycle:r({events:a,busy:!!p,pendingCount:u,pendingReason:m,stalled:"STUCK"===f,failed:h>=i.repeatedFailureCount&&!p,rawStatus:f||(p?"busy":"idle")})}};export const formatLaneAge=e=>{if(!Number.isFinite(e))return"no activity";const t=Math.max(0,Math.floor(e/1e3));if(t<60)return`${t}s ago`;const r=Math.floor(t/60);if(r<60)return`${r}m ago`;const n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`};const i=e=>`${e.lifecycle?.state||"Blocked"}${e.lifecycle?.reason?` (${e.lifecycle.reason})`:""} · ${formatLaneAge(e.lastActionAgeMs)}`,s=(e,t)=>{const r=String(null==e?"":e).replace(/\s+/g," ").trim();return r.length>t?r.slice(0,t-1)+"…":r};export const digestLogEvent=(e,t=PEEK.lineCap)=>{if(!e||"object"!=typeof e)return"";switch(e.kind){case"you":return s(`${e.by||"someone"}: ${e.text||""}`,t);case"assistant":{const r=[];for(const t of Array.isArray(e.blocks)?e.blocks:[])"text"===t?.type&&String(t.text||"").trim()?r.push(String(t.text).trim()):"tool_use"===t?.type&&t.name&&r.push(`→ ${t.name}`);return r.length?s(`assistant: ${r.join(" ")}`,t):""}case"tool_result":return e.isError?s("⎿ error",t):"";case"result":return e.subtype&&"success"!==e.subtype?s(`turn ended — ${e.subtype}`,t):"";case"error":return s(`error: ${e.message||""}`,t);default:return""}};export const cleanPtyTail=(e,t)=>String(e||"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[@-_][0-?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").split(/\r?\n/).map(e=>e.replace(/\s+$/,"")).filter(e=>e.trim()).slice(-Math.max(1,t));export const siblingRef=e=>String(e).slice(0,8);export const siblingsOf=({selfId:e,sessions:t,terms:r,names:n={},now:o=Date.now()}={})=>{const i=e=>n&&n[e]||"",s=[];for(const[r,n]of t||new Map)r!==e&&s.push({id:r,ref:siblingRef(r),name:i(r),kind:"agent",cmd:n?.cmd||"claude",log:Array.isArray(n?.log)?n.log:[],...laneStatusOf(n,o)});for(const[t,n]of r||new Map)t!==e&&s.push({id:t,ref:siblingRef(t),name:i(t),kind:"shell",cmd:n?.cmd||"shell",buf:n?.buf||"",...laneStatusOf(n,o)});return s};export const isOrdinalRef=e=>/^(terminal\s*)?\d+$/i.test(e)||/^t\d+$/i.test(e);export const resolveSibling=(e,t)=>{const r=String(t||"").toLowerCase().trim();if(!r)return null;const n=Array.isArray(e)?e:[];return n.find(e=>String(e.id).toLowerCase()===r)||n.find(e=>String(e.name||"").toLowerCase()===r)||n.find(e=>String(e.ref).toLowerCase()===r)||(isOrdinalRef(r)?null:n.find(e=>r.length>=4&&String(e.id).toLowerCase().startsWith(r))||n.find(e=>e.name&&String(e.name).toLowerCase().includes(r))||n.find(e=>String(e.cmd).toLowerCase().includes(r))||null)};const a=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const formatPeek=({selfId:e,sessions:t,terms:r,names:n={},terminal:o,lines:l,now:c=Date.now()}={})=>{const d=Math.min(PEEK.maxLines,Math.max(1,Number(l)||PEEK.defaultLines)),p=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:c});if(!p.length)return"No other terminals are open in this room right now.";if(!o)return`Other terminals in this room (call read_terminal with a ref or name to read one):\n${p.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,PEEK.rosterPreview)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${a(e)} · ${e.kind} · ${e.cmd} · ${i(e)} — ${s(t,PEEK.rosterPreview)}`}).join("\n")}`;const u=resolveSibling(p,o);if(!u)return`No terminal matches "${o}". Call read_terminal with no argument to list the open terminals (refer to one by its name or ref).${isOrdinalRef(String(o||"").toLowerCase().trim())?" On-screen numbers like \"Terminal N\" shift when a terminal closes, so they can't identify a lane — use a terminal's name or ref instead.":""}`;let m;return m="agent"===u.kind?u.log.map(e=>digestLogEvent(e,PEEK.lineCap)).filter(Boolean).slice(-d).join("\n")||"(no activity yet)":cleanPtyTail(u.buf,d).join("\n")||"(no output yet)",`Terminal ${a(u)} (${u.kind} · ${u.cmd}) · ${i(u)} — last ${d} lines:\n${m}`};export const nativeClaudeProviderAccessFailed=e=>{let t="";try{t="string"==typeof e?e:JSON.stringify(e||"")}catch{t=String(e||"")}return/organization has disabled (?:claude(?: subscription access)?|subscription access)|(?:claude )?subscription access (?:has been |is )?disabled|invalid anthropic (?:api )?key|invalid x-api-key|not logged in to claude|(?:run|use) \/login[^\n]{0,80}claude|no valid anthropic (?:api )?key/i.test(t)};const l=Object.freeze(["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"]);export const preferredHermesClaudeReviewModel=(e=[])=>{const t=new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));for(const e of l)if(t.has(e))return e;for(const e of t)if(/^nous:anthropic\/claude-sonnet-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-haiku-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-opus-/i.test(e))return e;return null};export const nativeClaudeFallbackHint=({parentRuntime:e,parentModels:t,targetRuntime:r,targetLog:n}={})=>{if("claude"!==r||!nativeClaudeProviderAccessFailed(n))return"";const o="thinkpool provider status: native Claude access failed at the account/provider level. Do not retry Sonnet, Opus, Haiku, or another model alias on the same native Claude provider during this task.",i="hermes"===e?preferredHermesClaudeReviewModel(t):null;return i?`\n\n${o} For a visible manual review fallback, spawn exactly one worker with runtime="hermes", model=${JSON.stringify(i)}, mode="default", and sliceType="review". This Hermes lane is permission-gated, not structurally read-only: instruct it not to edit, deny/stop any write request, and do not treat its review as a Flow safety proof. Never use Hermes delegate_task.`:`\n\n${o} Use a different available runtime/provider once, or report the blocker.`};export const ROOMNOW={laneCap:8,previewLen:70,wtCap:10};export const formatRoomNow=({selfId:e,sessions:t,terms:r,names:n={},worktrees:o=[],now:l=Date.now()}={},c=ROOMNOW)=>{const d=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:l}),p=[];if(d.length){const e=d.slice(0,c.laneCap),t=e.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,c.previewLen)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${a(e)} · ${e.kind} · ${i(e)} — ${s(t,c.previewLen)}`});d.length>e.length&&t.push(`- … +${d.length-e.length} more (read_terminal lists all)`),p.push(`Other lanes in this room right now (this is the roster; use targeted read_terminal only for needed detail):\n${t.join("\n")}`)}const u=Array.isArray(o)?o:[],m=u.slice(0,c.wtCap);if(m.length){const e=m.map(e=>`- ${s(e,c.previewLen+30)}`);u.length>m.length&&e.push(`- … +${u.length-m.length} more (\`git worktree list\` shows all)`),p.push(`Active git worktrees on this host (code edits go in your OWN worktree — never one another lane is on):\n${e.join("\n")}`)}return p.length?`ROOM NOW (live snapshot, this turn):\n${p.join("\n")}`:null};export const CROSSPOST={maxHop:1,perTurnCap:3};export const crossPostDecision=({hop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSPOST)=>{const o={...CROSSPOST,...n||{}};return r?{ok:!1,reason:"Cross-terminal posting is disabled in this room."}:e>=o.maxHop?{ok:!1,reason:"Cross-terminal hop limit reached — an agent reached via cross-post cannot itself post onward; a person must direct the next cross-terminal action."}:t>=o.perTurnCap?{ok:!1,reason:`Cross-terminal post limit reached for this turn (${o.perTurnCap}).`}:{ok:!0}};export const crossPostNeedsCard=({mode:e="default",spawnedByMe:t=!1}={})=>!("bypassPermissions"===e||!0===t);export const SPAWN={maxHop:1,perWindowCap:6,windowMs:6e5,freeMaxSpawned:3,plusMaxSpawned:6,machineMax:12};export const maxSpawnedFor=(e,t=SPAWN)=>{const r={...SPAWN,...t||{}};return"plus"===e?r.plusMaxSpawned:r.freeMaxSpawned};export const isActiveDispatchedWorker=e=>!(!e?.spawnedBy||"conductor"===e.flowRole||e.flowTaskKey&&e.flowDone||!0===e.workerSettled);export const countActiveDispatchedWorkers=(e=[])=>[...e].filter(isActiveDispatchedWorker).length;export const isSettledDispatchedWorker=e=>!!e?.spawnedBy&&!String(e.spawnedBy).startsWith("flow:")&&!0===e.workerSettled;export const countMachineRuntimeLanes=(e=[])=>[...e].filter(e=>!isSettledDispatchedWorker(e)).length;export const machineCapacityDecision=({totalLive:e=0,disabled:t=!1}={},r=SPAWN)=>{const n={...SPAWN,...r||{}};return t?{ok:!1,reason:"Spawning terminals is disabled in this room."}:e>=n.machineMax?{ok:!1,reason:`Room is at the machine cap (${n.machineMax} live lanes) — close one before opening another.`}:{ok:!0}};export const activeWorkerCapacityDecision=({spawnedLive:e=0,totalLive:t=0,plan:r="free",disabled:n=!1}={},o=SPAWN)=>{const i={...SPAWN,...o||{}},s=machineCapacityDecision({totalLive:t,disabled:n},i);if(!s.ok)return s;const a=maxSpawnedFor(r,i);return e>=a?{ok:!1,reason:`Dispatch ceiling reached (${a} lane${1===a?"":"s"} on the ${r} plan)${"plus"!==r?` — Plus raises it to ${i.plusMaxSpawned}.`:"."} Close a dispatched lane to open another.`}:{ok:!0}};export const recentSpawnCount=(e,t,r=SPAWN.windowMs)=>(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>t-r).length;export const spawnDecision=({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN)=>{const l={...SPAWN,...a||{}};if(s)return{ok:!1,reason:"Spawning terminals is disabled in this room."};if(e>=l.maxHop)return{ok:!1,reason:"Spawn limit reached — a terminal reached via cross-terminal cannot itself open further terminals; a person must open the next lane."};const c=activeWorkerCapacityDecision({spawnedLive:n,totalLive:o,plan:i},l);if(!c.ok)return c;if(recentSpawnCount(t,r,l.windowMs)>=l.perWindowCap){const e=Math.round(l.windowMs/6e4);return{ok:!1,reason:`Spawn burst limit reached (${l.perWindowCap} in ${e} min) — this replenishes as the window slides forward, or instantly when a person types into this lane.`}}return{ok:!0}};const c=Object.freeze(["cascade","mode","model","name","provider","runtime","sliceType","task"]),d=new Set(["default","acceptEdits","bypassPermissions","plan","review"]),p=new Set(["claude","codex","hermes"]),u=new Set(["scaffold","feature","fix","review"]),m=/((?<![a-z0-9])sk-[a-z0-9_-]{8,}|(?<![a-z0-9])gsk_[a-z0-9_-]{8,}|(?<![a-z0-9])AIza[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,})/gi,h=/(?:\/home\/|\/users\/|\/private\/|\/tmp\/|[a-z]:\\|\\\\)/gi,f=e=>Array.isArray(e)?e.map(f):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,f(e[t])])):e,g=(e,t)=>null==e?null:String(e).slice(0,t),y=(e,t)=>g(e,t)?.replace(m,"[credential redacted]").replace(h,"[host path redacted]")||"";export function normalizeDispatchArgs(e={},t={}){if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!c.includes(e)))throw new TypeError("Invalid dispatch arguments");const r=e.runtime||t.runtime||"claude",o=e.mode||("plan"===t.mode?"default":t.mode)||"default";if(!p.has(r)||!d.has(o)||e.sliceType&&!u.has(e.sliceType))throw new TypeError("Invalid dispatch arguments");const i=null==e.cascade?null:n(e.cascade);if(null!=e.cascade&&!i)throw new TypeError("Invalid Cascade metadata");return Object.freeze({cascade:i?Object.freeze(i):null,name:g(e.name,80),task:g(e.task,12e3),model:g(e.model,160),sliceType:g(e.sliceType,24),runtime:r,provider:g(e.provider,160),mode:o})}export function dispatchCapSnapshot({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN){const l={...SPAWN,...a||{}},c="plus"===i?"plus":"free",d=maxSpawnedFor(c,l);return Object.freeze({killSwitch:!0===s,hop:Object.freeze({used:Math.max(0,Number(e)||0),limit:l.maxHop}),width:Object.freeze({requested:1,live:Math.max(0,Number(n)||0),limit:d}),machine:Object.freeze({live:Math.max(0,Number(o)||0),limit:l.machineMax}),plan:Object.freeze({name:c,price:"plus"===c?"€5/mo":"€0",used:Math.max(0,Number(n)||0),limit:d}),burst:Object.freeze({used:recentSpawnCount(t,r,l.windowMs),limit:l.perWindowCap,windowMinutes:Math.round(l.windowMs/6e4)})})}export function buildDispatchPreview({args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){if(!(r&&n&&o&&i))throw new TypeError("Dispatch authority is incomplete");const l=normalizeDispatchArgs(t,{runtime:s.runtime,mode:s.mode}),c=dispatchCapSnapshot(s,a),d=(p={authority:Object.freeze({initiatorTerminalId:String(r),roomCode:String(n).toUpperCase(),bridgeAuthorityId:String(o),localAuthorityId:String(i)}),exactArgs:l,caps:c},e("sha256").update(JSON.stringify(f(p))).digest("hex"));var p;const u=l.provider||("codex"===l.runtime?"OpenAI / Codex login":"hermes"===l.runtime?"Hermes ACP profile":"Anthropic / host default"),m=l.model||s.resolvedModel||"runtime default",h=y(l.name||l.task?.split(/\r?\n/)[0]||"Open one worker lane",120);return Object.freeze({version:1,fingerprint:d,purpose:h,packages:Object.freeze([{title:y(l.name||"Worker package",80),task:y(l.task||"Open idle for a later hand-off.",600)}]),runtime:Object.freeze({name:l.runtime,provider:y(s.providerName||u,120),model:y(m,160),band:"Start: now–1 min · completion: task-dependent",execution:"Runs locally on the bridge machine; no cloud worker is created.",accountAndSpend:`Uses the host’s ${y(s.providerName||u,100)} account/BYOK. Provider usage is the spend source; thinkpool has no credit meter.`}),permissions:Object.freeze({mode:l.mode,consequence:"bypassPermissions"===l.mode?"Unattended local access within this worker’s sandbox and tool policy.":"review"===l.mode?"Read-only inspection of the pinned parent snapshot; writes and approval escapes are unavailable.":"plan"===l.mode?"Planning only; execution remains gated.":"Protected local actions keep their normal permission gates."}),worktree:"Automatically creates one visible lateral worker lane and an isolated git worktree on this machine.",caps:c})}export function authorizeDispatchApproval({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},decision:l="allow",consumed:c=!1,permissionId:d=null,consumedPermissionId:p=null,limits:u=SPAWN}={}){return"allow"!==l?{ok:!1,code:"dispatch_canceled"}:c?{ok:!1,code:d&&d===p?"idempotent_replay":"approval_replayed"}:w({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:u})}export function authorizeDirectDispatch({preview:e,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){return w({preview:e,approvedFingerprint:e?.fingerprint,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s,limits:a})}function w({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},limits:l=SPAWN}={}){let c;try{c=buildDispatchPreview({args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:l})}catch{return{ok:!1,code:"invalid_dispatch"}}if(!e||e.fingerprint!==t)return{ok:!1,code:"approval_mismatch"};if(c.fingerprint!==e.fingerprint)return{ok:!1,code:"stale_dispatch"};const d=spawnDecision(a,l);return d.ok?{ok:!0,code:"approved",preview:c}:{ok:!1,code:"cap_denied",reason:d.reason}}export function verifyDurableDispatchAuthority({item:e,events:t=[],expected:r={}}={}){const n=e=>({ok:!1,code:e}),o=String(r.fingerprint||"");if(!e||e.id!==r.controlItemId)return n("control_item_mismatch");if(e.session_code!==String(r.roomCode||"").toUpperCase())return n("wrong_room");if("dispatch"!==e.action_kind||"approval"!==e.item_kind)return n("wrong_action");if("resolved"!==e.status||"approved"!==e.resolution_code||!e.resolved_by)return n("not_approved");if("either_member"!==e.resolution_policy||null!=e.required_actor_id)return n("wrong_resolution_policy");if(e.bridge_authority_id!==r.bridgeAuthorityId)return n("wrong_bridge_authority");if(e.local_authority_id!==r.localAuthorityId)return n("wrong_local_authority");if(!/^[0-9a-f]{64}$/.test(o))return n("invalid_fingerprint");if(!String(e.request_context?.action||"").includes(`fp:${o}`))return n("request_fingerprint_mismatch");if(e.resolution_payload?.dispatch_fingerprint!==o)return n("resolution_fingerprint_mismatch");const i=(Array.isArray(t)?t:[]).filter(t=>t?.item_id===e.id);if(1!==i.filter(t=>"resolved"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.resolution_payload?.dispatch_fingerprint===o&&"approved"===t.payload?.resolution_code).length)return n("resolution_event_mismatch");const s=i.filter(t=>"delivery_attempted"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.data?.dispatch_fingerprint===o);return 1!==s.length?n("delivery_attempt_mismatch"):i.some(e=>"delivered"===e.event_type||"delivery_failed"===e.event_type)?n("delivery_already_final"):{ok:!0,code:"durable_dispatch_authorized",fingerprint:o,humanActorId:e.resolved_by,attemptKey:s[0].correlation_key}}export const CLOSABLE_HINT_MAX=6;export const closeLaneDecision=({callerId:e,spawnedBy:t,busy:r}={})=>e&&t===e?r?{ok:!1,reason:"busy"}:{ok:!0}:{ok:!1,reason:"not-owner"};export const closableLanes=(e,t=[])=>(Array.isArray(t)?t:[]).filter(t=>t&&closeLaneDecision({callerId:e,spawnedBy:t.spawnedBy,busy:t.busy}).ok);export const formatClosableHint=(e,t=[],r=6)=>{const n=closableLanes(e,t);if(!n.length)return"";const o=n.slice(0,r),i=o.map(e=>`${e.ref}${e.name?` ("${e.name}")`:""}`).join(", "),s=n.length>o.length?` (+${n.length-o.length} more)`:"";return`\n\nYou have ${n.length} finished lane${1===n.length?"":"s"} preserved for review. A person can free a slot by closing one with the X in its Ensemble row: ${i}${s}`};export const CROSSROOM={peekPerTurnCap:5};export const PEER_READ_MCP_TOOLS=Object.freeze(["read_terminal","list_sessions","read_session"]);export const PEER_POST_MCP_TOOLS=Object.freeze(["post_to_terminal","post_to_session"]);export const PEER_MCP_TOOLS=Object.freeze([...PEER_READ_MCP_TOOLS,...PEER_POST_MCP_TOOLS]);export const formatPairRoster=(e,{thisRoom:t,error:r}={})=>{if(r)return String(r);const n=new Set,o=(Array.isArray(e)?e:[]).filter(e=>!(!e||!e.code||e.code===t||n.has(e.code)||(n.add(e.code),0)));return o.length?`Your other thinkpool Code sessions (call read_session with a room code to look inside one):\n${o.map(e=>`- ${e.code}${e.name?` · "${e.name}"`:""}${e.host?` · on ${e.host}`:""}${!1===e.online?" · partner bridge offline":""}`).join("\n")}`:"No other sessions of yours are open right now. (Cross-session reach is limited to rooms this account is running here, plus your partner's rooms in the same pair.)"};export const CROSSROOM_BUS={busTimeoutMs:4e3};export const pairKeyFor=(e,t)=>{const r=String(null==e?"":e).toLowerCase().trim(),n=String(null==t?"":t).toLowerCase().trim();return r&&n&&r!==n?r<n?`${r}.${n}`:`${n}.${r}`:null};export const pairTopic=e=>e?`tppair:${e}`:null;export const CROSSROOM_POST={maxRoomHop:1,crossRoomPostCap:1};export const crossRoomPostDecision=({roomHop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSROOM_POST)=>{const o={...CROSSROOM_POST,...n||{}};return r?{ok:!1,reason:"Cross-room posting is turned off on this bridge."}:e>=o.maxRoomHop?{ok:!1,reason:"Cross-room hop limit reached — a room reached via a cross-room post cannot itself post into another room; a person must direct the next cross-room action."}:t>=o.crossRoomPostCap?{ok:!1,reason:`Cross-room post limit reached for this turn (${o.crossRoomPostCap}).`}:{ok:!0}};
1
+ import{createHash as e}from"node:crypto";import{BLOCKED_REASON as t,projectLaneLifecycle as r}from"./lane-lifecycle.mjs";import{normalizeCascadeMetadata as n}from"./worker-completion.mjs";export const PEEK={defaultLines:20,maxLines:200,perTurnCap:10,rosterPerTurnCap:1,lineCap:200,finalAssistantCap:24e3,rosterPreview:100};export const readTerminalBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0}={},n=PEEK)=>{const o=Math.max(0,Number(t)||0),i=Math.max(0,Number(r)||0);return e?o>=n.perTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`Cross-terminal transcript read limit reached for this turn (${n.perTurnCap}). Stop polling and continue with the worker results already collected; a fresh accepted room turn resets the allowance.`}:{ok:!0,targetedCount:o+1,rosterCount:i}:i>=n.rosterPerTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`The full terminal roster was already read this turn (${n.rosterPerTurnCap}/${n.rosterPerTurnCap}). Use the current ROOM NOW snapshot or read one relevant terminal by ref or name.`}:{ok:!0,targetedCount:o,rosterCount:i+1}};export const readTerminalTurnBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0,budgetTurnRev:n=null,currentTurnRev:o=0}={},i=PEEK)=>{const s=Math.max(0,Number(o)||0),a=(null==n?null:Number.isFinite(Number(n))?Number(n):null)===s;return{...readTerminalBudgetDecision({targeted:e,targetedCount:a?t:0,rosterCount:a?r:0},i),budgetTurnRev:s}};export const LANE_STATUS={stuckAfterMs:9e4,repeatedFailureCount:2,failureWindow:12};const o=e=>!!e&&("error"===e.kind||"tool_result"===e.kind&&e.isError||"result"===e.kind&&e.subtype&&"success"!==e.subtype);export const laneBusyOf=(e={})=>"boolean"==typeof e?.busy?e.busy:"boolean"==typeof e?._busyAnn?e._busyAnn:e?.session?.turnActive??!1;export const settleLaneBusy=e=>!(!e||!1===e._busyAnn||(e._busyAnn=!1,0));export const settleLaneControl=(e,t=Date.now())=>!(!e||!0!==e._busyAnn||!settleLaneBusy(e)||(e._settledTurnRev=Number(e._turnRev)||null,e._turnSettledAt=Number(t)||Date.now(),e._turnStart=null,0));export const laneStatusOf=(e={},n=Date.now(),i=LANE_STATUS)=>{const s=Array.isArray(e?.log)?e.log:[],a=s.slice(-i.failureWindow);!e?.lastEvent||e.lastEvent===s[s.length-1]||e.lastEvent.cid&&e.lastEvent.cid===s[s.length-1]?.cid||a.push(e.lastEvent);let l=null;for(let e=a.length-1;e>=0;e--)if(Number.isFinite(a[e]?.ts)){l=a[e].ts;break}const c=Number.isFinite(e?.lastActionAt)?e.lastActionAt:l??null,d=null==c?null:Math.max(0,n-c),p=laneBusyOf(e),u=e?.pending instanceof Map?e.pending.size:Math.max(0,Number(e?.pendingCount)||0),m=e?.pending instanceof Map&&[...e.pending.values()].some(e=>"ask"===e?.payload?.risk)?t.NEEDS_INPUT:t.NEEDS_DECISION,h=((e,t=LANE_STATUS)=>{let r=0,n=0;for(let i=(Array.isArray(e)?e.length:0)-1;i>=0&&n<t.failureWindow;i--){const t=e[i];if(t&&"object"==typeof t){if(n++,"result"===t.kind&&(!t.subtype||"success"===t.subtype)||"you"===t.kind)break;o(t)&&r++}}return r})(a,i);let f=null,g=null;return u>0?(f="BLOCKED",g="waiting for input"):e?.stalled||p&&!e?.compacting&&null!=d&&d>=i.stuckAfterMs?(f="STUCK",g="no events while busy"):h>=i.repeatedFailureCount&&(f="BLOCKED",g="repeated errors"),{busy:!!p,lastActionAt:c,lastActionAgeMs:d,statusFlag:f,statusReason:g,lifecycle:r({events:a,busy:!!p,pendingCount:u,pendingReason:m,stalled:"STUCK"===f,failed:h>=i.repeatedFailureCount&&!p,rawStatus:f||(p?"busy":"idle")})}};export const formatLaneAge=e=>{if(!Number.isFinite(e))return"no activity";const t=Math.max(0,Math.floor(e/1e3));if(t<60)return`${t}s ago`;const r=Math.floor(t/60);if(r<60)return`${r}m ago`;const n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`};const i=e=>`${e.lifecycle?.state||"Blocked"}${e.lifecycle?.reason?` (${e.lifecycle.reason})`:""} · ${formatLaneAge(e.lastActionAgeMs)}`,s=(e,t)=>{const r=String(null==e?"":e).replace(/\s+/g," ").trim();return r.length>t?r.slice(0,t-1)+"…":r};export const digestLogEvent=(e,t=PEEK.lineCap)=>{if(!e||"object"!=typeof e)return"";switch(e.kind){case"you":return s(`${e.by||"someone"}: ${e.text||""}`,t);case"assistant":{const r=[];for(const t of Array.isArray(e.blocks)?e.blocks:[])"text"===t?.type&&String(t.text||"").trim()?r.push(String(t.text).trim()):"tool_use"===t?.type&&t.name&&r.push(`→ ${t.name}`);return r.length?s(`assistant: ${r.join(" ")}`,t):""}case"tool_result":return e.isError?s("⎿ error",t):"";case"result":return e.subtype&&"success"!==e.subtype?s(`turn ended — ${e.subtype}`,t):"";case"error":return s(`error: ${e.message||""}`,t);default:return""}};const a=e=>"assistant"===e?.kind&&(Array.isArray(e.blocks)?e.blocks:[]).some(e=>"text"===e?.type&&String(e.text||"").trim());export const cleanPtyTail=(e,t)=>String(e||"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[@-_][0-?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").split(/\r?\n/).map(e=>e.replace(/\s+$/,"")).filter(e=>e.trim()).slice(-Math.max(1,t));export const siblingRef=e=>String(e).slice(0,8);export const siblingsOf=({selfId:e,sessions:t,terms:r,names:n={},now:o=Date.now()}={})=>{const i=e=>n&&n[e]||"",s=[];for(const[r,n]of t||new Map)r!==e&&s.push({id:r,ref:siblingRef(r),name:i(r),kind:"agent",cmd:n?.cmd||"claude",log:Array.isArray(n?.log)?n.log:[],...laneStatusOf(n,o)});for(const[t,n]of r||new Map)t!==e&&s.push({id:t,ref:siblingRef(t),name:i(t),kind:"shell",cmd:n?.cmd||"shell",buf:n?.buf||"",...laneStatusOf(n,o)});return s};export const isOrdinalRef=e=>/^(terminal\s*)?\d+$/i.test(e)||/^t\d+$/i.test(e);export const resolveSibling=(e,t)=>{const r=String(t||"").toLowerCase().trim();if(!r)return null;const n=Array.isArray(e)?e:[];return n.find(e=>String(e.id).toLowerCase()===r)||n.find(e=>String(e.name||"").toLowerCase()===r)||n.find(e=>String(e.ref).toLowerCase()===r)||(isOrdinalRef(r)?null:n.find(e=>r.length>=4&&String(e.id).toLowerCase().startsWith(r))||n.find(e=>e.name&&String(e.name).toLowerCase().includes(r))||n.find(e=>String(e.cmd).toLowerCase().includes(r))||null)};const l=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const formatPeek=({selfId:e,sessions:t,terms:r,names:n={},terminal:o,lines:c,now:d=Date.now()}={})=>{const p=Math.min(PEEK.maxLines,Math.max(1,Number(c)||PEEK.defaultLines)),u=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:d});if(!u.length)return"No other terminals are open in this room right now.";if(!o)return`Other terminals in this room (call read_terminal with a ref or name to read one):\n${u.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,PEEK.rosterPreview)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${e.cmd} · ${i(e)} — ${s(t,PEEK.rosterPreview)}`}).join("\n")}`;const m=resolveSibling(u,o);if(!m)return`No terminal matches "${o}". Call read_terminal with no argument to list the open terminals (refer to one by its name or ref).${isOrdinalRef(String(o||"").toLowerCase().trim())?" On-screen numbers like \"Terminal N\" shift when a terminal closes, so they can't identify a lane — use a terminal's name or ref instead.":""}`;let h;if("agent"===m.kind){let e=-1;if("Done"===m.lifecycle?.state)for(let t=m.log.length-1;t>=0;t--)if(a(m.log[t])){e=t;break}h=m.log.map((t,r)=>digestLogEvent(t,r===e?PEEK.finalAssistantCap:PEEK.lineCap)).filter(Boolean).slice(-p).join("\n")||"(no activity yet)"}else h=cleanPtyTail(m.buf,p).join("\n")||"(no output yet)";return`Terminal ${l(m)} (${m.kind} · ${m.cmd}) · ${i(m)} — last ${p} lines:\n${h}`};export const nativeClaudeProviderAccessFailed=e=>{let t="";try{t="string"==typeof e?e:JSON.stringify(e||"")}catch{t=String(e||"")}return/organization has disabled (?:claude(?: subscription access)?|subscription access)|(?:claude )?subscription access (?:has been |is )?disabled|invalid anthropic (?:api )?key|invalid x-api-key|not logged in to claude|(?:run|use) \/login[^\n]{0,80}claude|no valid anthropic (?:api )?key/i.test(t)};const c=Object.freeze(["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"]);export const preferredHermesClaudeReviewModel=(e=[])=>{const t=new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));for(const e of c)if(t.has(e))return e;for(const e of t)if(/^nous:anthropic\/claude-sonnet-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-haiku-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-opus-/i.test(e))return e;return null};export const nativeClaudeFallbackHint=({parentRuntime:e,parentModels:t,targetRuntime:r,targetLog:n}={})=>{if("claude"!==r||!nativeClaudeProviderAccessFailed(n))return"";const o="thinkpool provider status: native Claude access failed at the account/provider level. Do not retry Sonnet, Opus, Haiku, or another model alias on the same native Claude provider during this task.",i="hermes"===e?preferredHermesClaudeReviewModel(t):null;return i?`\n\n${o} For a visible manual review fallback, spawn exactly one worker with runtime="hermes", model=${JSON.stringify(i)}, mode="default", and sliceType="review". This Hermes lane is permission-gated, not structurally read-only: instruct it not to edit, deny/stop any write request, and do not treat its review as a Flow safety proof. Never use Hermes delegate_task.`:`\n\n${o} Use a different available runtime/provider once, or report the blocker.`};export const ROOMNOW={laneCap:8,previewLen:70,wtCap:10};export const formatRoomNow=({selfId:e,sessions:t,terms:r,names:n={},worktrees:o=[],now:a=Date.now()}={},c=ROOMNOW)=>{const d=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:a}),p=[];if(d.length){const e=d.slice(0,c.laneCap),t=e.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,c.previewLen)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${i(e)} — ${s(t,c.previewLen)}`});d.length>e.length&&t.push(`- … +${d.length-e.length} more (read_terminal lists all)`),p.push(`Other lanes in this room right now (this is the roster; use targeted read_terminal only for needed detail):\n${t.join("\n")}`)}const u=Array.isArray(o)?o:[],m=u.slice(0,c.wtCap);if(m.length){const e=m.map(e=>`- ${s(e,c.previewLen+30)}`);u.length>m.length&&e.push(`- … +${u.length-m.length} more (\`git worktree list\` shows all)`),p.push(`Active git worktrees on this host (code edits go in your OWN worktree — never one another lane is on):\n${e.join("\n")}`)}return p.length?`ROOM NOW (live snapshot, this turn):\n${p.join("\n")}`:null};export const CROSSPOST={maxHop:1,perTurnCap:3};export const crossPostDecision=({hop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSPOST)=>{const o={...CROSSPOST,...n||{}};return r?{ok:!1,reason:"Cross-terminal posting is disabled in this room."}:e>=o.maxHop?{ok:!1,reason:"Cross-terminal hop limit reached — an agent reached via cross-post cannot itself post onward; a person must direct the next cross-terminal action."}:t>=o.perTurnCap?{ok:!1,reason:`Cross-terminal post limit reached for this turn (${o.perTurnCap}).`}:{ok:!0}};export const crossPostNeedsCard=({mode:e="default",spawnedByMe:t=!1}={})=>!("bypassPermissions"===e||!0===t);export const SPAWN={maxHop:1,perWindowCap:6,windowMs:6e5,freeMaxSpawned:3,plusMaxSpawned:6,machineMax:12};export const maxSpawnedFor=(e,t=SPAWN)=>{const r={...SPAWN,...t||{}};return"plus"===e?r.plusMaxSpawned:r.freeMaxSpawned};export const isActiveDispatchedWorker=e=>!(!e?.spawnedBy||"conductor"===e.flowRole||e.flowTaskKey&&e.flowDone||!0===e.workerSettled);export const countActiveDispatchedWorkers=(e=[])=>[...e].filter(isActiveDispatchedWorker).length;export const isSettledDispatchedWorker=e=>!!e?.spawnedBy&&!String(e.spawnedBy).startsWith("flow:")&&!0===e.workerSettled;export const countMachineRuntimeLanes=(e=[])=>[...e].filter(e=>!isSettledDispatchedWorker(e)).length;export const machineCapacityDecision=({totalLive:e=0,disabled:t=!1}={},r=SPAWN)=>{const n={...SPAWN,...r||{}};return t?{ok:!1,reason:"Spawning terminals is disabled in this room."}:e>=n.machineMax?{ok:!1,reason:`Room is at the machine cap (${n.machineMax} live lanes) — close one before opening another.`}:{ok:!0}};export const activeWorkerCapacityDecision=({spawnedLive:e=0,totalLive:t=0,plan:r="free",disabled:n=!1}={},o=SPAWN)=>{const i={...SPAWN,...o||{}},s=machineCapacityDecision({totalLive:t,disabled:n},i);if(!s.ok)return s;const a=maxSpawnedFor(r,i);return e>=a?{ok:!1,reason:`Dispatch ceiling reached (${a} lane${1===a?"":"s"} on the ${r} plan)${"plus"!==r?` — Plus raises it to ${i.plusMaxSpawned}.`:"."} Close a dispatched lane to open another.`}:{ok:!0}};export const recentSpawnCount=(e,t,r=SPAWN.windowMs)=>(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>t-r).length;export const spawnDecision=({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN)=>{const l={...SPAWN,...a||{}};if(s)return{ok:!1,reason:"Spawning terminals is disabled in this room."};if(e>=l.maxHop)return{ok:!1,reason:"Spawn limit reached — a terminal reached via cross-terminal cannot itself open further terminals; a person must open the next lane."};const c=activeWorkerCapacityDecision({spawnedLive:n,totalLive:o,plan:i},l);if(!c.ok)return c;if(recentSpawnCount(t,r,l.windowMs)>=l.perWindowCap){const e=Math.round(l.windowMs/6e4);return{ok:!1,reason:`Spawn burst limit reached (${l.perWindowCap} in ${e} min) — this replenishes as the window slides forward, or instantly when a person types into this lane.`}}return{ok:!0}};const d=Object.freeze(["cascade","mode","model","name","provider","runtime","sliceType","task"]),p=new Set(["default","acceptEdits","bypassPermissions","plan","review"]),u=new Set(["claude","codex","hermes"]),m=new Set(["scaffold","feature","fix","review"]),h=/((?<![a-z0-9])sk-[a-z0-9_-]{8,}|(?<![a-z0-9])gsk_[a-z0-9_-]{8,}|(?<![a-z0-9])AIza[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,})/gi,f=/(?:\/home\/|\/users\/|\/private\/|\/tmp\/|[a-z]:\\|\\\\)/gi,g=e=>Array.isArray(e)?e.map(g):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,g(e[t])])):e,y=(e,t)=>null==e?null:String(e).slice(0,t),w=(e,t)=>y(e,t)?.replace(h,"[credential redacted]").replace(f,"[host path redacted]")||"";export function normalizeDispatchArgs(e={},t={}){if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!d.includes(e)))throw new TypeError("Invalid dispatch arguments");const r=e.runtime||t.runtime||"claude",o=e.mode||("plan"===t.mode?"default":t.mode)||"default";if(!u.has(r)||!p.has(o)||e.sliceType&&!m.has(e.sliceType))throw new TypeError("Invalid dispatch arguments");const i=null==e.cascade?null:n(e.cascade);if(null!=e.cascade&&!i)throw new TypeError("Invalid Cascade metadata");return Object.freeze({cascade:i?Object.freeze(i):null,name:y(e.name,80),task:y(e.task,12e3),model:y(e.model,160),sliceType:y(e.sliceType,24),runtime:r,provider:y(e.provider,160),mode:o})}export function dispatchCapSnapshot({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN){const l={...SPAWN,...a||{}},c="plus"===i?"plus":"free",d=maxSpawnedFor(c,l);return Object.freeze({killSwitch:!0===s,hop:Object.freeze({used:Math.max(0,Number(e)||0),limit:l.maxHop}),width:Object.freeze({requested:1,live:Math.max(0,Number(n)||0),limit:d}),machine:Object.freeze({live:Math.max(0,Number(o)||0),limit:l.machineMax}),plan:Object.freeze({name:c,price:"plus"===c?"€5/mo":"€0",used:Math.max(0,Number(n)||0),limit:d}),burst:Object.freeze({used:recentSpawnCount(t,r,l.windowMs),limit:l.perWindowCap,windowMinutes:Math.round(l.windowMs/6e4)})})}export function buildDispatchPreview({args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){if(!(r&&n&&o&&i))throw new TypeError("Dispatch authority is incomplete");const l=normalizeDispatchArgs(t,{runtime:s.runtime,mode:s.mode}),c=dispatchCapSnapshot(s,a),d=(p={authority:Object.freeze({initiatorTerminalId:String(r),roomCode:String(n).toUpperCase(),bridgeAuthorityId:String(o),localAuthorityId:String(i)}),exactArgs:l,caps:c},e("sha256").update(JSON.stringify(g(p))).digest("hex"));var p;const u=l.provider||("codex"===l.runtime?"OpenAI / Codex login":"hermes"===l.runtime?"Hermes ACP profile":"Anthropic / host default"),m=l.model||s.resolvedModel||"runtime default",h=w(l.name||l.task?.split(/\r?\n/)[0]||"Open one worker lane",120);return Object.freeze({version:1,fingerprint:d,purpose:h,packages:Object.freeze([{title:w(l.name||"Worker package",80),task:w(l.task||"Open idle for a later hand-off.",600)}]),runtime:Object.freeze({name:l.runtime,provider:w(s.providerName||u,120),model:w(m,160),band:"Start: now–1 min · completion: task-dependent",execution:"Runs locally on the bridge machine; no cloud worker is created.",accountAndSpend:`Uses the host’s ${w(s.providerName||u,100)} account/BYOK. Provider usage is the spend source; thinkpool has no credit meter.`}),permissions:Object.freeze({mode:l.mode,consequence:"bypassPermissions"===l.mode?"Unattended local access within this worker’s sandbox and tool policy.":"review"===l.mode?"Read-only inspection of the pinned parent snapshot; writes and approval escapes are unavailable.":"plan"===l.mode?"Planning only; execution remains gated.":"Protected local actions keep their normal permission gates."}),worktree:"Automatically creates one visible lateral worker lane and an isolated git worktree on this machine.",caps:c})}export function authorizeDispatchApproval({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},decision:l="allow",consumed:c=!1,permissionId:d=null,consumedPermissionId:p=null,limits:u=SPAWN}={}){return"allow"!==l?{ok:!1,code:"dispatch_canceled"}:c?{ok:!1,code:d&&d===p?"idempotent_replay":"approval_replayed"}:b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:u})}export function authorizeDirectDispatch({preview:e,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){return b({preview:e,approvedFingerprint:e?.fingerprint,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s,limits:a})}function b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},limits:l=SPAWN}={}){let c;try{c=buildDispatchPreview({args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:l})}catch{return{ok:!1,code:"invalid_dispatch"}}if(!e||e.fingerprint!==t)return{ok:!1,code:"approval_mismatch"};if(c.fingerprint!==e.fingerprint)return{ok:!1,code:"stale_dispatch"};const d=spawnDecision(a,l);return d.ok?{ok:!0,code:"approved",preview:c}:{ok:!1,code:"cap_denied",reason:d.reason}}export function verifyDurableDispatchAuthority({item:e,events:t=[],expected:r={}}={}){const n=e=>({ok:!1,code:e}),o=String(r.fingerprint||"");if(!e||e.id!==r.controlItemId)return n("control_item_mismatch");if(e.session_code!==String(r.roomCode||"").toUpperCase())return n("wrong_room");if("dispatch"!==e.action_kind||"approval"!==e.item_kind)return n("wrong_action");if("resolved"!==e.status||"approved"!==e.resolution_code||!e.resolved_by)return n("not_approved");if("either_member"!==e.resolution_policy||null!=e.required_actor_id)return n("wrong_resolution_policy");if(e.bridge_authority_id!==r.bridgeAuthorityId)return n("wrong_bridge_authority");if(e.local_authority_id!==r.localAuthorityId)return n("wrong_local_authority");if(!/^[0-9a-f]{64}$/.test(o))return n("invalid_fingerprint");if(!String(e.request_context?.action||"").includes(`fp:${o}`))return n("request_fingerprint_mismatch");if(e.resolution_payload?.dispatch_fingerprint!==o)return n("resolution_fingerprint_mismatch");const i=(Array.isArray(t)?t:[]).filter(t=>t?.item_id===e.id);if(1!==i.filter(t=>"resolved"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.resolution_payload?.dispatch_fingerprint===o&&"approved"===t.payload?.resolution_code).length)return n("resolution_event_mismatch");const s=i.filter(t=>"delivery_attempted"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.data?.dispatch_fingerprint===o);return 1!==s.length?n("delivery_attempt_mismatch"):i.some(e=>"delivered"===e.event_type||"delivery_failed"===e.event_type)?n("delivery_already_final"):{ok:!0,code:"durable_dispatch_authorized",fingerprint:o,humanActorId:e.resolved_by,attemptKey:s[0].correlation_key}}export const CLOSABLE_HINT_MAX=6;export const closeLaneDecision=({callerId:e,spawnedBy:t,busy:r}={})=>e&&t===e?r?{ok:!1,reason:"busy"}:{ok:!0}:{ok:!1,reason:"not-owner"};export const closableLanes=(e,t=[])=>(Array.isArray(t)?t:[]).filter(t=>t&&closeLaneDecision({callerId:e,spawnedBy:t.spawnedBy,busy:t.busy}).ok);export const formatClosableHint=(e,t=[],r=6)=>{const n=closableLanes(e,t);if(!n.length)return"";const o=n.slice(0,r),i=o.map(e=>`${e.ref}${e.name?` ("${e.name}")`:""}`).join(", "),s=n.length>o.length?` (+${n.length-o.length} more)`:"";return`\n\nYou have ${n.length} finished lane${1===n.length?"":"s"} preserved for review. A person can free a slot by closing one with the X in its Ensemble row: ${i}${s}`};export const CROSSROOM={peekPerTurnCap:5};export const PEER_READ_MCP_TOOLS=Object.freeze(["read_terminal","list_sessions","read_session"]);export const PEER_POST_MCP_TOOLS=Object.freeze(["post_to_terminal","post_to_session"]);export const PEER_MCP_TOOLS=Object.freeze([...PEER_READ_MCP_TOOLS,...PEER_POST_MCP_TOOLS]);export const formatPairRoster=(e,{thisRoom:t,error:r}={})=>{if(r)return String(r);const n=new Set,o=(Array.isArray(e)?e:[]).filter(e=>!(!e||!e.code||e.code===t||n.has(e.code)||(n.add(e.code),0)));return o.length?`Your other thinkpool Code sessions (call read_session with a room code to look inside one):\n${o.map(e=>`- ${e.code}${e.name?` · "${e.name}"`:""}${e.host?` · on ${e.host}`:""}${!1===e.online?" · partner bridge offline":""}`).join("\n")}`:"No other sessions of yours are open right now. (Cross-session reach is limited to rooms this account is running here, plus your partner's rooms in the same pair.)"};export const CROSSROOM_BUS={busTimeoutMs:4e3};export const pairKeyFor=(e,t)=>{const r=String(null==e?"":e).toLowerCase().trim(),n=String(null==t?"":t).toLowerCase().trim();return r&&n&&r!==n?r<n?`${r}.${n}`:`${n}.${r}`:null};export const pairTopic=e=>e?`tppair:${e}`:null;export const CROSSROOM_POST={maxRoomHop:1,crossRoomPostCap:1};export const crossRoomPostDecision=({roomHop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSROOM_POST)=>{const o={...CROSSROOM_POST,...n||{}};return r?{ok:!1,reason:"Cross-room posting is turned off on this bridge."}:e>=o.maxRoomHop?{ok:!1,reason:"Cross-room hop limit reached — a room reached via a cross-room post cannot itself post into another room; a person must direct the next cross-room action."}:t>=o.crossRoomPostCap?{ok:!1,reason:`Cross-room post limit reached for this turn (${o.crossRoomPostCap}).`}:{ok:!0}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.430",
3
+ "version": "0.7.432",
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",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": 1,
3
3
  "package": "thinkpool-pair",
4
- "version": "0.7.430",
4
+ "version": "0.7.432",
5
5
  "kind": "minified",
6
6
  "minifiedModules": 143,
7
- "sourceModuleBytes": 1904901,
8
- "artifactModuleBytes": 873949,
7
+ "sourceModuleBytes": 1917380,
8
+ "artifactModuleBytes": 881021,
9
9
  "files": {
10
- "package.json": "256d5c6d74bed0f1a3c283af681915c7323f573fcc1ed960e81768b68bdf3656",
10
+ "package.json": "6e8829fe919e8c2027e5fb17989d31ecd062283235e930d0539c66fc6297961a",
11
11
  "LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
12
- "bridge.mjs": "d5eae65e3c4985b50431988f291d539eda906fdec0cb7867b8ce8aa29e52f2ee",
12
+ "bridge.mjs": "3ad4b84f77c864f48be1d297fd34e583271f13262a7c18493f3472388d2436c5",
13
13
  "bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
14
14
  "command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
15
15
  "abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
@@ -29,14 +29,14 @@
29
29
  "code-automations.mjs": "03c70058bc6d5a0b245f3fb8f5e2cb629c5ef7c1c6cd7d7fa018c40523b91036",
30
30
  "terminal-name.mjs": "f39bb8d1e148ab9a74cbb96438d31e524d6597b8035f00d7a906c28c306c0cf8",
31
31
  "claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
32
- "codex-session.mjs": "31684859879c1d796bef4c03b0d6d52e969403b9bcae25e656a2d5e159a83b1d",
32
+ "codex-session.mjs": "78458dbcf0f4e4cf2b5c88dac86030f40e95d28248b0a1a9bbc5208367d2fc5c",
33
33
  "codex-host-heal.mjs": "14ea191ed2072203be2edd40a41272f176aafc044753919f65b1071ce48f060f",
34
34
  "question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
35
35
  "thinkpool-room-prompt.mjs": "b8cc82ccf4262f6431c2047bbff31ac29c47a0d6c68a64640a2c24d94df4a746",
36
36
  "thinkpool-prompt-contracts.mjs": "051602d7d2fe9b34df92957bd3922c5eeebedbcc60c892adf415243887d8c62f",
37
37
  "thinkpool-capabilities.json": "b3177f1b75383a494b9aab69d5cf1bd449648c11610376cd41f34e110d14509c",
38
38
  "room-file-delivery.mjs": "a33300b7f12152e8d016771aabbd4616896dec4bf7a3762e059d271e107a1b6a",
39
- "codex-app-server.mjs": "f032cc1f4e1ecd25d165901c0eaf7037da01f0913f08badc43711c9ec79e5467",
39
+ "codex-app-server.mjs": "f49c4f6c15766765074b014eee11e9399f6c540420a1157f6e9ad79a432b18e3",
40
40
  "codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
41
41
  "codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
42
42
  "lane-worktree.mjs": "9dd70a4f089f8867ad5d6a77e912734dbb396c327c0231b7fae883f7603d5ea2",
@@ -50,7 +50,7 @@
50
50
  "codex-event-mapper.mjs": "a7c423806b5e80957733c21d756078740773ae7230305ed00dba72cecb3a558a",
51
51
  "edit-diff.mjs": "258c64416b4e60b1f8243546f1cc25e1b27457f0edeaa0e6c837a3e888f49ba2",
52
52
  "cumulative-event-relay.mjs": "cf6e6533e33b3c5bb0b462d15ccc86106f2b56f8b1e66fc1f4867dd8f44d7ae6",
53
- "codex-commands.mjs": "cae3f617ac42e1316d8980ef97e4255227fe5df5e8e9b556630a3bfb83bb7c2b",
53
+ "codex-commands.mjs": "2eb5087d8ff2c0c0ee82a90e34984ce0226992f77d91d8a3735de3739730ae43",
54
54
  "acp-client.mjs": "9e7919ff79825883820019ff7a0ca578f120853ed61c7caab21a8ffc3a67ffc9",
55
55
  "acp-event-mapper.mjs": "bcef2d7b5b940a4ee1dc5053e2283ac962dd0567c7fd87f8fb6e16b083be5847",
56
56
  "acp-runtime-adapter.mjs": "b0e65df88a2e5f64ad3bcb123843daca5ae30e32ac33aceee31ca4f4c167f38b",
@@ -70,11 +70,11 @@
70
70
  "hermes-delegation-guard.mjs": "f737d05fb93591848fa3839fd5df2c019779bda828d42ab785cd57213665f922",
71
71
  "catastrophic-command-guard.mjs": "65158e59266e4be1e2a72479fb9b2c7e7a4ec7902a8b764d3b26017cd0cdbfd5",
72
72
  "codex-catastrophic-command-hook.mjs": "5bf1eb5dd837b77044846af0cb8936a81edbc435e454217ddb15981f3626f7dc",
73
- "runtime-registry.mjs": "278ff20fe803dba5990f275ef4f2f26730a2e17d0886700d76cb5b37c6e3163e",
73
+ "runtime-registry.mjs": "5a1eb84616453f3c11d38464b8a73e5b2c281996c056f06dfe27312463e53183",
74
74
  "runtime-skill-parity.mjs": "933535bc7b70d7843cf7bf8eb83f306693045ccf7154ad781b6d42be75442f1c",
75
75
  "reasoning-effort.mjs": "3a514997656bbcd41ff1aefe9d5e59c5920a90e6bfcc928016d95987db2ceaa3",
76
76
  "runtime-contract.mjs": "410e55770d4b1ee72af5adc5c7706fb021c28126f728f270a1aad711d4ad3460",
77
- "command-catalog.mjs": "42a61109ce791bcdb4ca793ad343402fe3c7bbad44cbe8f1f135bc67207aa00e",
77
+ "command-catalog.mjs": "7b10575937605650445d311c28e2cc83a04310687224558839017cc00043b366",
78
78
  "repo-search.mjs": "10cbce1d86655a7add0d7350ea8862a57165ccb02c2afa406348f4669050c304",
79
79
  "project-intelligence.mjs": "b16108075648b331c84976cb63e267cf5835da7776ec7456b7642721225610a6",
80
80
  "git-diff-report.mjs": "a428b3129c90e207a125a86c4485ec16c8ee7e81d432060a070e37172d85ce7a",
@@ -89,7 +89,7 @@
89
89
  "error-recovery.mjs": "0ace80add28fa72b49a3f3a716f247e3ef9816671c09749b3863867700beae32",
90
90
  "pair-control-authority.mjs": "7a95f101153dcc6dd0633b5a01595bf2979cd0c4bf50cfc6cd44bb7612ef40a5",
91
91
  "pair-access.mjs": "953d32528a08cde1bfeef58caafa0ce97274df8f0251d8a3be2bd190e382b5cc",
92
- "scheduled-runs.mjs": "69d61ab5eb1bc12647a35a3a231625795f6460138eff78745093c611778740d9",
92
+ "scheduled-runs.mjs": "92425d84ec5a259fab4712a17111d701a0a8d28fa12f635a641a9cd0a17d2669",
93
93
  "scheduled-run-admission.mjs": "3a2098d904c3aff571bf314e0182d5636d25d242cef52207e70f9dc1618c94f5",
94
94
  "event-id.mjs": "09c75e7fc8da282fc00651add58e5d2f82d3020e0c4dddb9921250cf9a6fa8c0",
95
95
  "event-bounds.mjs": "aa8eb8517c4aa81b3378f71401ed39c550cdc80e28e3a57ab46d0a5ef5c7c9c1",
@@ -104,7 +104,7 @@
104
104
  "session-store.mjs": "fa3f7a27a8a7c49bb89364fb28c9c2680f7f487d4b4125329ab4a0ba9362bd8c",
105
105
  "side-lane.mjs": "6a6bc6d8041872ba4472d70fd1cece62607417982a72b8721f4fbed77655cc6d",
106
106
  "worker-completion.mjs": "3eab6abf9cde32e62b8bef676023bd59759836f88887d578d2db2e9fa092a151",
107
- "cross-terminal.mjs": "1e8b3320b0bd79ee8d532cc2fbf1825e46428c1c4c5efbb3877c69e1e8e3a21e",
107
+ "cross-terminal.mjs": "08d3889516f0b7edd7628dccb61b32731ab1e033efcc19f597a3d29a053fe020",
108
108
  "cloud-session-transcript.mjs": "1e8b3000d628eac0cff171901d6fed13983528b42e23e277583ef65bf8f9fa2b",
109
109
  "pair-bus.mjs": "cbc28cee7f4f06b2213091e0b7b07d61c1d01f0ff207c5c6d808cd616ece221e",
110
110
  "direct-pair-room.mjs": "92557b54307da2ca31f0a0d090e44b019a23b1ab32a7dfd6cbcb9d55fd761736",
@@ -144,7 +144,7 @@
144
144
  "terminal-row-reconcile.mjs": "7992fb6a1e854fb3a5fbb1978ef972f691ad2298803fdb416c3b8e83e5f2cdb4",
145
145
  "terminal-close-auth.mjs": "ea3d3614a113c38e62a11902aa0d4dbb441e2869aa395cdb6e9b9649160d109c",
146
146
  "switch-provider.mjs": "9805d59df49e1272ffd5ef7e3d2260f6e93c76d32265b78e461287ef500f5b71",
147
- "service.mjs": "f1bfabfbbb6dde5d28f8a623692d284b36a6883da3e8aad89bf6205fb4896b61",
147
+ "service.mjs": "51472fdda8fd249620924c5b9515bec2d754483bebae29fbe0cb4b02a59ecbed",
148
148
  "supervisor-ready.mjs": "7d5e4ebe3c7a93e0888dd335fb4556a670e55a80a89b861c8a44feac159cd840",
149
149
  "presence.mjs": "2516ebb4474a8f45dd881530335c2174af9cde44be8eed6896ff8c62497b6e2b",
150
150
  "account.mjs": "baf751dfb696d85035f00214390148ad4f7418636ef3ea84e86197f09dedc33d",
@@ -1 +1 @@
1
- import e from"node:path";import{runtimeCapability as t,runtimeSupportsCapability as o}from"./runtime-contract.mjs";export{normalizeStructuredEffort,reasoningEffortsForLane}from"./reasoning-effort.mjs";const r=Object.freeze({claude:Object.freeze({id:"claude",command:"claude",label:"Claude Code",protocol:"agent-sdk",structured:!0,flow:!0,canSteer:!1,images:!0,nativeModelCatalog:!1,effortControl:!0,defaultMode:"default",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions"])}),codex:Object.freeze({id:"codex",command:"codex",label:"Codex",protocol:"codex-app-server",structured:!0,flow:!0,canSteer:!0,images:!0,nativeModelCatalog:!0,effortControl:!0,defaultMode:"bypassPermissions",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions","review"])}),hermes:Object.freeze({id:"hermes",command:"thinkpool",label:"Hermes Agent",protocol:"acp",structured:!0,flow:!0,canSteer:!0,images:!0,nativeModelCatalog:!0,catalogRequiresSession:!0,effortControl:!0,defaultMode:"default",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions"]),beta:!0}),opencode:Object.freeze({id:"opencode",command:"opencode",label:"OpenCode",protocol:"acp",structured:!0,flow:!1,canSteer:!1,images:!0,nativeModelCatalog:!1,catalogRequiresSession:!1,effortControl:!1,defaultMode:"default",modes:Object.freeze(["default"]),ordinaryOnly:!0})});export const STRUCTURED_RUNTIME_IDS=Object.freeze(Object.keys(r));export const structuredRuntimeMetadata=e=>r[e]||null;export const structuredRuntimeForCommand=t=>{const o=e.basename(String(t||"")).toLowerCase();return Object.values(r).find(e=>e.command===o)?.id||null};export const defaultStructuredMode=e=>structuredRuntimeMetadata(e)?.defaultMode||"default";export const structuredRuntimeSupportsMode=(e,t)=>!0===structuredRuntimeMetadata(e)?.modes?.includes(t);export const structuredRuntimeSupportsFlow=e=>!0===structuredRuntimeMetadata(e)?.flow;export const structuredRuntimeRoleSupported=(e,t={})=>{const o=structuredRuntimeMetadata(e);return!!o&&(!o.ordinaryOnly||!t.flowRole&&"review"!==t.sliceType&&"plan"!==t.sliceType)};export const structuredRuntimeCapability=(e,r)=>o(e,r)?t(r):null;export const structuredModeLocked=({flowRole:e,sliceType:t}={})=>"conductor"===e||"reviewer"===e||"review"===t;export const structuredModeForSlice=(e,t={})=>structuredRuntimeRoleSupported(e,t)?"reviewer"!==t.flowRole&&"review"!==t.sliceType?t.mode:structuredRuntimeSupportsMode(e,"review")?"review":structuredRuntimeSupportsMode(e,"plan")?"plan":t.mode:null;export const structuredModesForLane=(e,t={})=>{const o=structuredRuntimeMetadata(e)?.modes||[];return structuredModeLocked(t)&&o.includes(t.mode)?[t.mode]:o};export const shouldDeferStructuredRuntime=({runtime:e,defer:t,cwd:o,flowSessionId:r,flowTaskKey:d,models:s}={})=>{const a=structuredRuntimeMetadata(e);return!(!(!a?.catalogRequiresSession||Array.isArray(s)&&s.length)||!t&&(o||r||d))};
1
+ import e from"node:path";import{runtimeCapability as t,runtimeSupportsCapability as o}from"./runtime-contract.mjs";export{normalizeStructuredEffort,reasoningEffortsForLane}from"./reasoning-effort.mjs";const r=Object.freeze({claude:Object.freeze({id:"claude",command:"claude",label:"Claude Code",protocol:"agent-sdk",structured:!0,flow:!0,canSteer:!1,images:!0,nativeModelCatalog:!1,effortControl:!0,defaultMode:"default",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions"])}),codex:Object.freeze({id:"codex",command:"codex",label:"Codex",protocol:"codex-app-server",structured:!0,flow:!0,canSteer:!0,images:!0,nativeModelCatalog:!0,effortControl:!0,serviceTierControl:!0,defaultMode:"bypassPermissions",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions","review"])}),hermes:Object.freeze({id:"hermes",command:"thinkpool",label:"Hermes Agent",protocol:"acp",structured:!0,flow:!0,canSteer:!0,images:!0,nativeModelCatalog:!0,catalogRequiresSession:!0,effortControl:!0,defaultMode:"default",modes:Object.freeze(["default","acceptEdits","plan","bypassPermissions"]),beta:!0}),opencode:Object.freeze({id:"opencode",command:"opencode",label:"OpenCode",protocol:"acp",structured:!0,flow:!1,canSteer:!1,images:!0,nativeModelCatalog:!1,catalogRequiresSession:!1,effortControl:!1,defaultMode:"default",modes:Object.freeze(["default"]),ordinaryOnly:!0})});export const STRUCTURED_RUNTIME_IDS=Object.freeze(Object.keys(r));export const structuredRuntimeMetadata=e=>r[e]||null;export const structuredRuntimeForCommand=t=>{const o=e.basename(String(t||"")).toLowerCase();return Object.values(r).find(e=>e.command===o)?.id||null};export const defaultStructuredMode=e=>structuredRuntimeMetadata(e)?.defaultMode||"default";export const structuredRuntimeSupportsMode=(e,t)=>!0===structuredRuntimeMetadata(e)?.modes?.includes(t);export const structuredRuntimeSupportsFlow=e=>!0===structuredRuntimeMetadata(e)?.flow;export const structuredRuntimeRoleSupported=(e,t={})=>{const o=structuredRuntimeMetadata(e);return!!o&&(!o.ordinaryOnly||!t.flowRole&&"review"!==t.sliceType&&"plan"!==t.sliceType)};export const structuredRuntimeCapability=(e,r)=>o(e,r)?t(r):null;export const structuredModeLocked=({flowRole:e,sliceType:t}={})=>"conductor"===e||"reviewer"===e||"review"===t;export const structuredModeForSlice=(e,t={})=>structuredRuntimeRoleSupported(e,t)?"reviewer"!==t.flowRole&&"review"!==t.sliceType?t.mode:structuredRuntimeSupportsMode(e,"review")?"review":structuredRuntimeSupportsMode(e,"plan")?"plan":t.mode:null;export const structuredModesForLane=(e,t={})=>{const o=structuredRuntimeMetadata(e)?.modes||[];return structuredModeLocked(t)&&o.includes(t.mode)?[t.mode]:o};export const shouldDeferStructuredRuntime=({runtime:e,defer:t,cwd:o,flowSessionId:r,flowTaskKey:d,models:s}={})=>{const a=structuredRuntimeMetadata(e);return!(!(!a?.catalogRequiresSession||Array.isArray(s)&&s.length)||!t&&(o||r||d))};
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{SPAWN as t,spawnDecision as n}from"./cross-terminal.mjs";const i=new Set(["once","daily","weekly"]),r=new Set(["claude","codex","hermes"]),o=new Set(["delivered","needs_decision","failed","canceled"]),d=new Set(["artifact","preview","commit","test","flow_task","control_item"]),a=/(?:sk-[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9]{20,}|github_pat_[a-z0-9_]{20,}|xox[baprs]-[a-z0-9-]{10,}|AKIA[0-9A-Z]{16}|sbp_[a-z0-9]{20,}|eyJ[a-z0-9_-]{16,}\.[a-z0-9_-]{16,}\.[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{16,}|(?:token|secret|api[_-]?key|authorization)\s*=)/i,c=/(?:^|\s)(?:~\/|\/Users\/|\/home\/|\/private\/|\/tmp\/|[A-Za-z]:\\|\.\.[\\/])/i,s=/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,159}$/,u=/^[A-Za-z0-9][A-Za-z0-9._:@-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:@-]*)*$/,l=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/,p=new Map,_=new Set([15,30,60]);export const isScheduledBackgroundRuntime=e=>Boolean(e&&"string"==typeof e.scheduleRunId&&s.test(e.scheduleRunId));export const scheduledRuntimeMode=e=>r.has(e)?"bypassPermissions":"default";export function scheduledEvidenceRefs({events:e=[],previews:t=[]}={}){const n=[],i=new Set,r=(e,t)=>{if(!d.has(e)||"string"!=typeof t||!s.test(t))return;const r=`${e}:${t}`;i.has(r)||n.length>=16||(i.add(r),n.push(Object.freeze({type:e,id:t})))};for(const e of Array.isArray(t)?t:[])r("preview",e?.id);for(const t of Array.isArray(e)?e:[])for(const e of Array.isArray(t?.files)?t.files:[])r("artifact",e?.id);return Object.freeze(n)}export const scheduledRunsEnabled=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>"1"===e;export const scheduledRunsCapability=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>Object.freeze({enabled:scheduledRunsEnabled(e)});export async function admitScheduledRunExecution({terminalId:e,localAdmission:t,memoryAdmission:n,acquireHost:i,acquireScheduled:r,releaseHost:o}={}){if("string"!=typeof e||!s.test(e))return{ok:!1,code:"terminal_id_unavailable"};if("function"!=typeof t||"function"!=typeof n||"function"!=typeof i||"function"!=typeof r||"function"!=typeof o)return{ok:!1,code:"spawn_admission_unavailable"};let d=null;try{d=await t()}catch{d=null}if(!d?.ok)return d||{ok:!1,code:"spawn_not_admitted"};let a=null;try{a=await n()}catch{a=null}if(!a?.ok)return a||{ok:!1,code:"host_memory_unavailable"};let c=null;try{c=await i()}catch{c=null}if(!c?.ok)return c||{ok:!1,code:"capacity_authority_unavailable"};let u=null;try{u=await r()}catch{u=null}if(!u?.ok||!u.lease){try{await o(e)}catch{}return u||{ok:!1,code:"scheduled_admission_unavailable"}}return{ok:!0,code:"admitted",lease:{terminalId:e,scheduled:u.lease}}}export async function releaseScheduledRunExecution(e,{releaseScheduled:t,releaseHost:n}={}){if(!e?.scheduled||"string"!=typeof e.terminalId||"function"!=typeof t||"function"!=typeof n)return{ok:!1,code:"invalid_execution_lease"};let i=null;try{i=await t(e.scheduled)}catch{i=null}try{await n(e.terminalId)}catch{}return i||{ok:!1,code:"scheduled_release_failed"}}const f=(e,t)=>"string"==typeof e&&e.trim().length>0&&e.length<=t&&!a.test(e)&&!c.test(e),m=e=>e instanceof Date?e:new Date(e),h=e=>!Number.isNaN(m(e).getTime()),b=(e,t)=>{let n=p.get(e);n||(n=new Intl.DateTimeFormat("en-CA",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),p.set(e,n));const i=Object.fromEntries(n.formatToParts(t).filter(e=>"literal"!==e.type).map(e=>[e.type,e.value]));return{weekday:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(i.weekday),time:`${i.hour}:${i.minute}`}};export function scheduleValidationError(e={}){if(!e||"object"!=typeof e||!i.has(e.kind))return"invalid_kind";if(!f(e.title,120)||!f(e.prompt,12e3))return"unsafe_or_invalid_text";if(!r.has(e.runtime))return"invalid_runtime";if(!(null==e.provider||f(e.provider,80)&&s.test(e.provider)))return"invalid_provider";if(!(null==e.model||f(e.model,160)&&u.test(e.model)))return"invalid_model";if(!Number.isInteger(e.maxRuntimeMinutes)||e.maxRuntimeMinutes<1||e.maxRuntimeMinutes>30)return"invalid_max_runtime";try{new Intl.DateTimeFormat("en",{timeZone:e.timezone})}catch{return"invalid_timezone"}return"once"!==e.kind||h(e.at)?null==e.intervalMinutes||"daily"===e.kind&&_.has(e.intervalMinutes)?"once"!==e.kind&&!/^\d{2}:\d{2}$/.test(e.time)||"once"!==e.kind&&(Number(e.time.slice(0,2))>23||Number(e.time.slice(3))>59)?"invalid_local_time":"weekly"===e.kind&&(!Number.isInteger(e.weekday)||e.weekday<0||e.weekday>6)?"invalid_weekday":null:"invalid_interval":"invalid_once_time"}export function normalizeSchedule(e={},t=new Date){const n=scheduleValidationError(e);if(n)return Object.freeze({ok:!1,code:n});const i={kind:e.kind,title:e.title.trim(),prompt:e.prompt.trim(),runtime:e.runtime,provider:e.provider||null,model:e.model||null,timezone:e.timezone,maxRuntimeMinutes:e.maxRuntimeMinutes,..."once"===e.kind?{at:m(e.at).toISOString()}:{time:e.time,..."weekly"===e.kind?{weekday:e.weekday}:{},...e.intervalMinutes?{interval_minutes:e.intervalMinutes}:{}}},r=nextScheduledAt(i,t);return r?Object.freeze({ok:!0,value:Object.freeze(i),nextRunAt:r.toISOString()}):Object.freeze({ok:!1,code:"schedule_exhausted"})}export function nextScheduledAt(e,t=new Date){if("once"===e?.kind){const n=m(e.at);return h(n)&&n.getTime()>m(t).getTime()?n:null}const n=Number(e?.interval_minutes);if(_.has(n))return new Date(m(t).getTime()+6e4*n);const i=6e4*Math.floor(m(t).getTime()/6e4)+6e4;for(let t=i,n=i+3456e7;t<n;t+=6e4){const n=b(e.timezone,new Date(t));if(n.time===e.time&&("daily"===e.kind||n.weekday===e.weekday))return new Date(t)}return null}export function classifyRunOutcome({result:e=null,evidenceRefs:t=[],pendingControls:n=[],runId:i=null,canceled:r=!1}={}){if("runtime_limit"===e?.subtype)return Object.freeze({outcome:"failed",code:"runtime_limit"});if(r||["aborted","canceled","cancelled","interrupted"].includes(e?.subtype))return Object.freeze({outcome:"canceled",code:"canceled"});const o=(Array.isArray(t)?t:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&s.test(e.id)).slice(0,16);if(o.length)return Object.freeze({outcome:"delivered",code:"evidence_delivered",evidence:o});const a=(Array.isArray(n)?n:[]).find(e=>"pending"===e?.status&&e?.id&&e?.request_context?.run_id&&(!i||e.request_context.run_id===i)&&Number.isSafeInteger(Number(e.source_event_from_seq)));return a?Object.freeze({outcome:"needs_decision",code:"pending_pair_control",controlItemId:a.id}):"success"===e?.subtype?Object.freeze({outcome:"failed",code:"missing_deliverable"}):Object.freeze({outcome:"failed",code:e?.subtype||"runtime_failed"})}export function sanitizeScheduledOutcomeIntent(e={}){const{runId:t,result:n=null,evidenceRefs:i=[],canceled:r=!1}=e&&"object"==typeof e?e:{};if("string"!=typeof t||!s.test(t))return null;const o="string"==typeof n?.subtype&&l.test(n.subtype)?n.subtype:"runtime_failed";return Object.freeze({runId:t,result:Object.freeze({subtype:o}),evidenceRefs:Object.freeze((a=i,(Array.isArray(a)?a:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&s.test(e.id)).slice(0,16).map(e=>Object.freeze({type:e.type,id:e.id})))),canceled:!0===r});var a}export function scheduledRunDeadlineRequired({runId:e,deadlineAt:t,outcomeRecorded:n=!1,outcomePending:i=null}={}){return Boolean(e&&Number.isFinite(t)&&!n&&!i)}export function scheduledOutcomeReconciliationComplete(e=[]){return Array.isArray(e)&&0===e.length}export function scheduledRunSpawnAdmission({spawnTimes:e=[],now:i=Date.now(),totalLive:r=0,disabled:o=!1}={},d=t){const a={...t,...d||{}},c=(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>i-a.windowMs).slice(-(a.perWindowCap||1)),s=n({hop:0,spawnTimes:c,now:i,spawnedLive:0,totalLive:r,plan:"free",disabled:o},a);return Object.freeze({...s,spawnTimes:Object.freeze(s.ok?[...c,i].slice(-a.perWindowCap):c)})}export function preflightScheduledRun({schedule:e,occurrence:t,ownerId:n,bridgeId:i,bridgeFencing:r,providerAvailable:o,capsOk:d=!0,now:a=new Date}={}){return e?.enabled?t?.id&&t.schedule_revision===e.revision?!n||!i||!Number.isSafeInteger(r)||r<1||t.owner_id!==n||t.bridge_id!==i||t.bridge_fencing!==r?Object.freeze({ok:!1,code:"owner_bridge_mismatch"}):d?"function"==typeof o&&o(e)?!t.lease_expires_at||m(t.lease_expires_at).getTime()<=m(a).getTime()?Object.freeze({ok:!1,code:"lease_expired"}):Object.freeze({ok:!0,code:"admitted"}):Object.freeze({ok:!1,code:"provider_unavailable"}):Object.freeze({ok:!1,code:"runtime_cap_exceeded"}):Object.freeze({ok:!1,code:"stale_schedule_revision"}):Object.freeze({ok:!1,code:"schedule_disabled"})}const y=async({fetchImpl:e,supabaseUrl:t,headers:n,name:i,body:r})=>{const o=await e(`${String(t).replace(/\/$/,"")}/rest/v1/rpc/${i}`,{method:"POST",headers:n,body:JSON.stringify(r)});if(!o?.ok)throw new Error(`${i}_failed`);return o.json()};export async function pollDueScheduledRuns({enabled:t=!1,fetchImpl:n=globalThis.fetch,supabaseUrl:i,anonKey:r,token:o,roomCode:d,bridgeId:a,bridgeFencing:c,ownerId:u,providerAvailable:l,openTerminal:p,admitRun:_,releaseRun:f,makeTerminalId:h=e,capsOk:b=()=>!0,now:g=new Date}={}){if(!t)return Object.freeze({ok:!1,code:"feature_disabled",runs:[]});if(!("function"==typeof n&&i&&r&&o&&d&&a&&Number.isSafeInteger(c))||c<1||!u||"function"!=typeof p||"function"!=typeof _||"function"!=typeof f||"function"!=typeof h)return Object.freeze({ok:!1,code:"runner_unavailable",runs:[]});const k={apikey:r,Authorization:`Bearer ${o}`,"Content-Type":"application/json"},w=String(d).trim().toUpperCase();try{const e=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"expire_code_scheduled_runs",body:{p_room_code:w,p_bridge_id:a,p_bridge_fencing:c,p_limit:24}});if(!e?.ok)throw new Error("expire_code_scheduled_runs_failed");const t=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"list_due_code_schedules",body:{p_room_code:w,p_limit:12}}),r=[];for(const e of Array.isArray(t)?t:[]){if(String(e?.session_code||"").trim().toUpperCase()!==w){r.push({scheduleId:e?.id,code:"room_scope_mismatch"});continue}if(!b(e)){r.push({scheduleId:e.id,code:"runtime_cap_exceeded"});continue}if("function"!=typeof l||!l(e)){r.push({scheduleId:e.id,code:"provider_unavailable"});continue}const t=h();if("string"!=typeof t||!s.test(t)){r.push({scheduleId:e.id,code:"terminal_id_unavailable"});continue}const o={...e.schedule_spec||{},kind:e.schedule_kind||e.kind,timezone:e.timezone},d="once"===o.kind?new Date("9999-12-31T23:59:00.000Z"):nextScheduledAt(o,new Date(e.next_run_at));if(!d){r.push({scheduleId:e.id,code:"next_run_invalid"});continue}const v=`${e.id}:${m(e.next_run_at).toISOString()}`,I=Math.min(1860,Math.max(60,60*e.max_runtime_minutes+60)),z=m(g).getTime()+1e3*I;let x=null;try{x=await _({schedule:e,runId:v,terminalId:t,deadlineAt:z})}catch{x=null}if(!x?.ok){r.push({scheduleId:e.id,code:x?.code||"spawn_not_admitted"});continue}let O=!1;try{const o=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"claim_due_code_schedule",body:{p_schedule_id:e.id,p_expected_revision:e.revision,p_bridge_id:a,p_bridge_fencing:c,p_occurrence_key:v,p_lease_seconds:I}});if(!o?.ok||!o?.run){r.push({scheduleId:e.id,code:o?.code||"claim_failed"});continue}const s=preflightScheduledRun({schedule:e,occurrence:o.run,ownerId:u,bridgeId:a,bridgeFencing:c,providerAvailable:l,capsOk:!0,now:g});if(!s.ok){r.push({scheduleId:e.id,runId:o.run.id,code:s.code});continue}const _=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"open_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_terminal_id:t,p_next_run_at:d.toISOString()}});if(!_?.ok){r.push({scheduleId:e.id,runId:o.run.id,code:_?.code||"open_not_persisted"});continue}const f=_.run||o.run;let m=null;try{m=await p({schedule:e,run:f,terminalId:t,admission:x.lease})}catch{m=null}if("string"==typeof m?m:!0===m?t:!0===m?.ok&&"string"==typeof m.terminalId&&m.terminalId){O=!0,r.push({scheduleId:e.id,runId:o.run.id,code:"opened"});continue}const h=!1===m?.ok&&"string"==typeof m.code&&/^terminal_[a-z0-9_]{1,100}$/.test(m.code)?m.code:"terminal_open_failed",b=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"finish_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_outcome:"failed",p_outcome_code:h,p_evidence_refs:[],p_decision_item_id:null}});r.push({scheduleId:e.id,runId:o.run.id,code:b?.ok?h:b?.code||"open_failed_unrecorded"})}finally{if(!O)try{await f(x.lease)}catch{}}}return Object.freeze({ok:!0,code:"polled",runs:Object.freeze(r)})}catch{return Object.freeze({ok:!1,code:"runner_read_failed",runs:[]})}}export async function finishScheduledRun({fetchImpl:e=globalThis.fetch,supabaseUrl:t,anonKey:n,token:i,roomCode:r,runId:o,bridgeId:d,bridgeFencing:a,result:c,evidenceRefs:s,canceled:u=!1}={}){if(!(t&&n&&i&&r&&o&&d&&Number.isSafeInteger(a))||a<1)return Object.freeze({ok:!1,code:"outcome_unavailable"});const l={apikey:n,Authorization:`Bearer ${i}`,"Content-Type":"application/json"};try{const n=classifyRunOutcome({result:c,evidenceRefs:s,pendingControls:await y({fetchImpl:e,supabaseUrl:t,headers:l,name:"list_code_pair_controls",body:{p_session_code:String(r).toUpperCase(),p_status:"pending",p_limit:100}}),runId:o,canceled:u}),i=await y({fetchImpl:e,supabaseUrl:t,headers:l,name:"finish_code_scheduled_run",body:{p_run_id:o,p_outcome:n.outcome,p_outcome_code:n.code,p_bridge_id:d,p_bridge_fencing:a,p_evidence_refs:n.evidence||[],p_decision_item_id:n.controlItemId||null}});return Object.freeze({ok:Boolean(i?.ok),code:i?.code||"outcome_write_failed",outcome:n})}catch{return Object.freeze({ok:!1,code:"outcome_write_failed"})}}export async function finishScheduledRunWithRetry(e={},{attempts:t=3,delays:n=[100,400],sleep:i=e=>new Promise(t=>setTimeout(t,e))}={}){const r=Math.max(1,Math.min(5,Number.isInteger(t)?t:3));let o=Object.freeze({ok:!1,code:"outcome_write_failed"});for(let t=0;t<r;t++){if(o=await finishScheduledRun(e),o.ok)return o;t+1<r&&await i(Math.max(0,Math.min(5e3,Number(n[t]??n.at(-1)??0)||0)))}return o}export{o as OUTCOMES};
1
+ import{randomUUID as e}from"node:crypto";import{SPAWN as t,spawnDecision as n}from"./cross-terminal.mjs";const i=new Set(["once","daily","weekly"]),r=new Set(["claude","codex","hermes"]),o=new Set(["delivered","needs_decision","failed","canceled"]),d=new Set(["artifact","preview","commit","test","flow_task","control_item"]),a=/(?:sk-[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9]{20,}|github_pat_[a-z0-9_]{20,}|xox[baprs]-[a-z0-9-]{10,}|AKIA[0-9A-Z]{16}|sbp_[a-z0-9]{20,}|eyJ[a-z0-9_-]{16,}\.[a-z0-9_-]{16,}\.[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{16,}|(?:token|secret|api[_-]?key|authorization)\s*=)/i,c=/(?:^|\s)(?:~\/|\/Users\/|\/home\/|\/private\/|\/tmp\/|[A-Za-z]:\\|\.\.[\\/])/i,u=/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,159}$/,s=/^[A-Za-z0-9][A-Za-z0-9._:@-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:@-]*)*$/,l=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/,_=new Map,p=new Set([15,30,60]);export const isScheduledBackgroundRuntime=e=>Boolean(e&&"string"==typeof e.scheduleRunId&&u.test(e.scheduleRunId));export const scheduledRunsEnabled=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>"1"===e;export const scheduledRunsCapability=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>Object.freeze({enabled:scheduledRunsEnabled(e)});export async function admitScheduledRunExecution({terminalId:e,localAdmission:t,memoryAdmission:n,acquireHost:i,acquireScheduled:r,releaseHost:o}={}){if("string"!=typeof e||!u.test(e))return{ok:!1,code:"terminal_id_unavailable"};if("function"!=typeof t||"function"!=typeof n||"function"!=typeof i||"function"!=typeof r||"function"!=typeof o)return{ok:!1,code:"spawn_admission_unavailable"};let d=null;try{d=await t()}catch{d=null}if(!d?.ok)return d||{ok:!1,code:"spawn_not_admitted"};let a=null;try{a=await n()}catch{a=null}if(!a?.ok)return a||{ok:!1,code:"host_memory_unavailable"};let c=null;try{c=await i()}catch{c=null}if(!c?.ok)return c||{ok:!1,code:"capacity_authority_unavailable"};let s=null;try{s=await r()}catch{s=null}if(!s?.ok||!s.lease){try{await o(e)}catch{}return s||{ok:!1,code:"scheduled_admission_unavailable"}}return{ok:!0,code:"admitted",lease:{terminalId:e,scheduled:s.lease}}}export async function releaseScheduledRunExecution(e,{releaseScheduled:t,releaseHost:n}={}){if(!e?.scheduled||"string"!=typeof e.terminalId||"function"!=typeof t||"function"!=typeof n)return{ok:!1,code:"invalid_execution_lease"};let i=null;try{i=await t(e.scheduled)}catch{i=null}try{await n(e.terminalId)}catch{}return i||{ok:!1,code:"scheduled_release_failed"}}const m=(e,t)=>"string"==typeof e&&e.trim().length>0&&e.length<=t&&!a.test(e)&&!c.test(e),f=e=>e instanceof Date?e:new Date(e),h=e=>!Number.isNaN(f(e).getTime()),b=(e,t)=>{let n=_.get(e);n||(n=new Intl.DateTimeFormat("en-CA",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),_.set(e,n));const i=Object.fromEntries(n.formatToParts(t).filter(e=>"literal"!==e.type).map(e=>[e.type,e.value]));return{weekday:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(i.weekday),time:`${i.hour}:${i.minute}`}};export function scheduleValidationError(e={}){if(!e||"object"!=typeof e||!i.has(e.kind))return"invalid_kind";if(!m(e.title,120)||!m(e.prompt,12e3))return"unsafe_or_invalid_text";if(!r.has(e.runtime))return"invalid_runtime";if(!(null==e.provider||m(e.provider,80)&&u.test(e.provider)))return"invalid_provider";if(!(null==e.model||m(e.model,160)&&s.test(e.model)))return"invalid_model";if(!Number.isInteger(e.maxRuntimeMinutes)||e.maxRuntimeMinutes<1||e.maxRuntimeMinutes>30)return"invalid_max_runtime";try{new Intl.DateTimeFormat("en",{timeZone:e.timezone})}catch{return"invalid_timezone"}return"once"!==e.kind||h(e.at)?null==e.intervalMinutes||"daily"===e.kind&&p.has(e.intervalMinutes)?"once"!==e.kind&&!/^\d{2}:\d{2}$/.test(e.time)||"once"!==e.kind&&(Number(e.time.slice(0,2))>23||Number(e.time.slice(3))>59)?"invalid_local_time":"weekly"===e.kind&&(!Number.isInteger(e.weekday)||e.weekday<0||e.weekday>6)?"invalid_weekday":null:"invalid_interval":"invalid_once_time"}export function normalizeSchedule(e={},t=new Date){const n=scheduleValidationError(e);if(n)return Object.freeze({ok:!1,code:n});const i={kind:e.kind,title:e.title.trim(),prompt:e.prompt.trim(),runtime:e.runtime,provider:e.provider||null,model:e.model||null,timezone:e.timezone,maxRuntimeMinutes:e.maxRuntimeMinutes,..."once"===e.kind?{at:f(e.at).toISOString()}:{time:e.time,..."weekly"===e.kind?{weekday:e.weekday}:{},...e.intervalMinutes?{interval_minutes:e.intervalMinutes}:{}}},r=nextScheduledAt(i,t);return r?Object.freeze({ok:!0,value:Object.freeze(i),nextRunAt:r.toISOString()}):Object.freeze({ok:!1,code:"schedule_exhausted"})}export function nextScheduledAt(e,t=new Date){if("once"===e?.kind){const n=f(e.at);return h(n)&&n.getTime()>f(t).getTime()?n:null}const n=Number(e?.interval_minutes);if(p.has(n))return new Date(f(t).getTime()+6e4*n);const i=6e4*Math.floor(f(t).getTime()/6e4)+6e4;for(let t=i,n=i+3456e7;t<n;t+=6e4){const n=b(e.timezone,new Date(t));if(n.time===e.time&&("daily"===e.kind||n.weekday===e.weekday))return new Date(t)}return null}export function classifyRunOutcome({result:e=null,evidenceRefs:t=[],pendingControls:n=[],runId:i=null,canceled:r=!1}={}){if("runtime_limit"===e?.subtype)return Object.freeze({outcome:"failed",code:"runtime_limit"});if(r||["aborted","canceled","cancelled","interrupted"].includes(e?.subtype))return Object.freeze({outcome:"canceled",code:"canceled"});const o=(Array.isArray(t)?t:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16);if(o.length)return Object.freeze({outcome:"delivered",code:"evidence_delivered",evidence:o});const a=(Array.isArray(n)?n:[]).find(e=>"pending"===e?.status&&e?.id&&e?.request_context?.run_id&&(!i||e.request_context.run_id===i)&&Number.isSafeInteger(Number(e.source_event_from_seq)));return a?Object.freeze({outcome:"needs_decision",code:"pending_pair_control",controlItemId:a.id}):"success"===e?.subtype?Object.freeze({outcome:"failed",code:"missing_deliverable"}):Object.freeze({outcome:"failed",code:e?.subtype||"runtime_failed"})}export function sanitizeScheduledOutcomeIntent(e={}){const{runId:t,result:n=null,evidenceRefs:i=[],canceled:r=!1}=e&&"object"==typeof e?e:{};if("string"!=typeof t||!u.test(t))return null;const o="string"==typeof n?.subtype&&l.test(n.subtype)?n.subtype:"runtime_failed";return Object.freeze({runId:t,result:Object.freeze({subtype:o}),evidenceRefs:Object.freeze((a=i,(Array.isArray(a)?a:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16).map(e=>Object.freeze({type:e.type,id:e.id})))),canceled:!0===r});var a}export function scheduledRunDeadlineRequired({runId:e,deadlineAt:t,outcomeRecorded:n=!1,outcomePending:i=null}={}){return Boolean(e&&Number.isFinite(t)&&!n&&!i)}export function scheduledOutcomeReconciliationComplete(e=[]){return Array.isArray(e)&&0===e.length}export function scheduledRunSpawnAdmission({spawnTimes:e=[],now:i=Date.now(),totalLive:r=0,disabled:o=!1}={},d=t){const a={...t,...d||{}},c=(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>i-a.windowMs).slice(-(a.perWindowCap||1)),u=n({hop:0,spawnTimes:c,now:i,spawnedLive:0,totalLive:r,plan:"free",disabled:o},a);return Object.freeze({...u,spawnTimes:Object.freeze(u.ok?[...c,i].slice(-a.perWindowCap):c)})}export function preflightScheduledRun({schedule:e,occurrence:t,ownerId:n,bridgeId:i,bridgeFencing:r,providerAvailable:o,capsOk:d=!0,now:a=new Date}={}){return e?.enabled?t?.id&&t.schedule_revision===e.revision?!n||!i||!Number.isSafeInteger(r)||r<1||t.owner_id!==n||t.bridge_id!==i||t.bridge_fencing!==r?Object.freeze({ok:!1,code:"owner_bridge_mismatch"}):d?"function"==typeof o&&o(e)?!t.lease_expires_at||f(t.lease_expires_at).getTime()<=f(a).getTime()?Object.freeze({ok:!1,code:"lease_expired"}):Object.freeze({ok:!0,code:"admitted"}):Object.freeze({ok:!1,code:"provider_unavailable"}):Object.freeze({ok:!1,code:"runtime_cap_exceeded"}):Object.freeze({ok:!1,code:"stale_schedule_revision"}):Object.freeze({ok:!1,code:"schedule_disabled"})}const y=async({fetchImpl:e,supabaseUrl:t,headers:n,name:i,body:r})=>{const o=await e(`${String(t).replace(/\/$/,"")}/rest/v1/rpc/${i}`,{method:"POST",headers:n,body:JSON.stringify(r)});if(!o?.ok)throw new Error(`${i}_failed`);return o.json()};export async function pollDueScheduledRuns({enabled:t=!1,fetchImpl:n=globalThis.fetch,supabaseUrl:i,anonKey:r,token:o,roomCode:d,bridgeId:a,bridgeFencing:c,ownerId:s,providerAvailable:l,openTerminal:_,admitRun:p,releaseRun:m,makeTerminalId:h=e,capsOk:b=()=>!0,now:g=new Date}={}){if(!t)return Object.freeze({ok:!1,code:"feature_disabled",runs:[]});if(!("function"==typeof n&&i&&r&&o&&d&&a&&Number.isSafeInteger(c))||c<1||!s||"function"!=typeof _||"function"!=typeof p||"function"!=typeof m||"function"!=typeof h)return Object.freeze({ok:!1,code:"runner_unavailable",runs:[]});const k={apikey:r,Authorization:`Bearer ${o}`,"Content-Type":"application/json"},w=String(d).trim().toUpperCase();try{const e=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"expire_code_scheduled_runs",body:{p_room_code:w,p_bridge_id:a,p_bridge_fencing:c,p_limit:24}});if(!e?.ok)throw new Error("expire_code_scheduled_runs_failed");const t=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"list_due_code_schedules",body:{p_room_code:w,p_limit:12}}),r=[];for(const e of Array.isArray(t)?t:[]){if(String(e?.session_code||"").trim().toUpperCase()!==w){r.push({scheduleId:e?.id,code:"room_scope_mismatch"});continue}if(!b(e)){r.push({scheduleId:e.id,code:"runtime_cap_exceeded"});continue}if("function"!=typeof l||!l(e)){r.push({scheduleId:e.id,code:"provider_unavailable"});continue}const t=h();if("string"!=typeof t||!u.test(t)){r.push({scheduleId:e.id,code:"terminal_id_unavailable"});continue}const o={...e.schedule_spec||{},kind:e.schedule_kind||e.kind,timezone:e.timezone},d="once"===o.kind?new Date("9999-12-31T23:59:00.000Z"):nextScheduledAt(o,new Date(e.next_run_at));if(!d){r.push({scheduleId:e.id,code:"next_run_invalid"});continue}const v=`${e.id}:${f(e.next_run_at).toISOString()}`,I=Math.min(1860,Math.max(60,60*e.max_runtime_minutes+60)),z=f(g).getTime()+1e3*I;let x=null;try{x=await p({schedule:e,runId:v,terminalId:t,deadlineAt:z})}catch{x=null}if(!x?.ok){r.push({scheduleId:e.id,code:x?.code||"spawn_not_admitted"});continue}let O=!1;try{const o=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"claim_due_code_schedule",body:{p_schedule_id:e.id,p_expected_revision:e.revision,p_bridge_id:a,p_bridge_fencing:c,p_occurrence_key:v,p_lease_seconds:I}});if(!o?.ok||!o?.run){r.push({scheduleId:e.id,code:o?.code||"claim_failed"});continue}const u=preflightScheduledRun({schedule:e,occurrence:o.run,ownerId:s,bridgeId:a,bridgeFencing:c,providerAvailable:l,capsOk:!0,now:g});if(!u.ok){r.push({scheduleId:e.id,runId:o.run.id,code:u.code});continue}const p=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"open_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_terminal_id:t,p_next_run_at:d.toISOString()}});if(!p?.ok){r.push({scheduleId:e.id,runId:o.run.id,code:p?.code||"open_not_persisted"});continue}const m=p.run||o.run;let f=null;try{f=await _({schedule:e,run:m,terminalId:t,admission:x.lease})}catch{f=null}if("string"==typeof f?f:!0===f?t:!0===f?.ok&&"string"==typeof f.terminalId&&f.terminalId){O=!0,r.push({scheduleId:e.id,runId:o.run.id,code:"opened"});continue}const h=!1===f?.ok&&"string"==typeof f.code&&/^terminal_[a-z0-9_]{1,100}$/.test(f.code)?f.code:"terminal_open_failed",b=await y({fetchImpl:n,supabaseUrl:i,headers:k,name:"finish_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_outcome:"failed",p_outcome_code:h,p_evidence_refs:[],p_decision_item_id:null}});r.push({scheduleId:e.id,runId:o.run.id,code:b?.ok?h:b?.code||"open_failed_unrecorded"})}finally{if(!O)try{await m(x.lease)}catch{}}}return Object.freeze({ok:!0,code:"polled",runs:Object.freeze(r)})}catch{return Object.freeze({ok:!1,code:"runner_read_failed",runs:[]})}}export async function finishScheduledRun({fetchImpl:e=globalThis.fetch,supabaseUrl:t,anonKey:n,token:i,roomCode:r,runId:o,bridgeId:d,bridgeFencing:a,result:c,evidenceRefs:u,canceled:s=!1}={}){if(!(t&&n&&i&&r&&o&&d&&Number.isSafeInteger(a))||a<1)return Object.freeze({ok:!1,code:"outcome_unavailable"});const l={apikey:n,Authorization:`Bearer ${i}`,"Content-Type":"application/json"};try{const n=classifyRunOutcome({result:c,evidenceRefs:u,pendingControls:await y({fetchImpl:e,supabaseUrl:t,headers:l,name:"list_code_pair_controls",body:{p_session_code:String(r).toUpperCase(),p_status:"pending",p_limit:100}}),runId:o,canceled:s}),i=await y({fetchImpl:e,supabaseUrl:t,headers:l,name:"finish_code_scheduled_run",body:{p_run_id:o,p_outcome:n.outcome,p_outcome_code:n.code,p_bridge_id:d,p_bridge_fencing:a,p_evidence_refs:n.evidence||[],p_decision_item_id:n.controlItemId||null}});return Object.freeze({ok:Boolean(i?.ok),code:i?.code||"outcome_write_failed",outcome:n})}catch{return Object.freeze({ok:!1,code:"outcome_write_failed"})}}export async function finishScheduledRunWithRetry(e={},{attempts:t=3,delays:n=[100,400],sleep:i=e=>new Promise(t=>setTimeout(t,e))}={}){const r=Math.max(1,Math.min(5,Number.isInteger(t)?t:3));let o=Object.freeze({ok:!1,code:"outcome_write_failed"});for(let t=0;t<r;t++){if(o=await finishScheduledRun(e),o.ok)return o;t+1<r&&await i(Math.max(0,Math.min(5e3,Number(n[t]??n.at(-1)??0)||0)))}return o}export{o as OUTCOMES};