thinkpool-pair 0.7.402 → 0.7.404
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -25
- package/bridge.mjs +1 -1
- package/codex-session.mjs +1 -1
- package/package.json +1 -2
- package/publish-manifest.json +9 -10
- package/thinkpool-capabilities.json +4 -4
- package/demo-project.mjs +0 -1
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 d}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as c,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as b}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as w}from"./catastrophic-command-guard.mjs";import{codexNativeReasoningEffort as k,codexReasoningEfforts as C,normalizeCodexReasoningEffort as I,normalizeReasoningEffortList as M}from"./reasoning-effort.mjs";const S=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 function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=k(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:s,timeoutMs:a=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:d=r,spawnImpl:c=t}={}){const p=String(e||"").trim();if(!p)throw new Error("codex one-shot requires a prompt");const m=!l,f=l||d.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let h=null;try{i&&(h=o.join(f,"output-schema.json"),d.writeFileSync(h,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...h?["--output-schema",h]:[],p];return await 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=c("codex",e,{cwd:f,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))})}finally{if(m)try{d.rmSync(f,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:M(e.supported_reasoning_levels)}))}catch{return[]}}const _=new Map;function T(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=_.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 _.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=T(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=T(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 S.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. If open_main_terminal is unavailable, say so; never substitute an Ensemble child. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate. Never use hidden in-process subagents."].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}),d=[o?c:"",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 d.length?`<thinkpool-context>\n${d.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:s="untrusted",providerConfig:a,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:d}){const c=normalizeCodexSandbox(n),p=[...w(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===c?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${c}"`]]:["exec","--json","--sandbox",c,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${s}"`);const m=k(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(d??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const E=/\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:E.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 F(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:c,models:p=[],effort:h="high",resume:w,env:M,sandbox:S,mode:_="default",onEvent:T,providerConfig:E,terminalRolePrompt:N,rolePrompt:O,roomContext:R,mcpServers:P,prepareCwd:A=null,requestPermission:D,appServerGate:U=i,appServerFactory:$=l,mcpHttpFactory:j=d,spawnImpl:q=t,admitStart:L=null,stallOptions:X={}}){let G=CODEX_MODE_CONFIG[_]?_:"default",B=codexConfigForMode(G);S=normalizeCodexSandbox(S||B.sandbox);const W=codexPublishAccess({cwd:e,env:{...process.env,...M||{}}}),z=W.env,H=w||null,J=!H||codexThreadCanResume(H,{home:z.CODEX_HOME||o.join(n.homedir(),".codex")});let K=J?H:null,V=K?1:0,Q=0,Y=!0;const Z=m(R);let ee=null,te=!1,re=!1,ne=!1;const oe="function"==typeof X.now?X.now:Date.now,se="function"==typeof X.setInterval?X.setInterval:setInterval,ae="function"==typeof X.clearInterval?X.clearInterval:clearInterval,ie=Number.isFinite(X.stallMs)?Math.max(1,Number(X.stallMs)):Math.max(3e4,parseInt(z.TP_STALL_MS,10)||9e4),le=Number.isFinite(X.forceStopMs)?Math.max(ie,Number(X.forceStopMs)):Math.max(3*ie,parseInt(z.TP_FORCE_STOP_MS,10)||3e5),ue=Number.isFinite(X.intervalMs)?Math.max(1,Number(X.intervalMs)):5e3;let de=oe(),ce=!1,pe=!1,me=!1,fe=!1,he=!0,xe=0,ge=null,ye=c||null,ve=I(h,{models:p,model:ye}),be=K?readCodexThreadUsage(K):null,we=null;const ke=U({env:z});let Ce=!ke.enabled,Ie=null,Me=!1;const Se=new WeakSet;let _e=null,Te=null,Ee=!1;const Fe=new Set,Ne=new Map,Oe=new Map;let Re=Promise.resolve(),Pe=Promise.resolve();const Ae=[],De=y(e=>{try{T?.(e)}catch{}}),Ue=()=>{de=oe(),ce=!1},$e=e=>{Ue(),De(e)};$e.cancel=()=>De.cancel();const je=new u({onEvent:$e,model:c||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(be=t),t||be}}),qe=()=>{const e=[...Oe.values()];Oe.clear();for(const t of e)t.text&&$e({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},Le=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(qe(),ne=!1),je.push(e)},Xe=e=>{qe(),ne=!1,$e(e)},Ge=({retry:e=!1}={})=>{ne=!0,de=oe(),ce=!1,me=!1,fe=!1,he=!0,e||(pe=!1)},Be=e=>$e({kind:"note",text:e}),We=(e=_e)=>{const t=String(e||"");if(t)for(Fe.add(t);Fe.size>64;)Fe.delete(Fe.values().next().value)},ze=(e={})=>{const t=String(e.turnId||"");return!(!t||re||te||Fe.has(t)||t!==String(_e||""))};async function He(){const e=P?.thinkpool;return e?(we||(we=j({sdkServer:e})),we):null}function Je(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&K&&String(t.threadId)!==String(K))return;const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(be=e,Ee){const{used:t,max:r}=e.context;$e({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:ye}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(re||te)return void We(e);if(Te){const t=Te;return Te=null,void t.resolve(e)}return _e||(_e=e),void Ue()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||ze(t)){if(Ue(),"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 $e({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||_e||"active"}`,r=Oe.get(e)||{cid:`codex-stream:${K||"thread"}:${e}`,text:""};return r.text+=String(t.delta),Oe.set(e,r),void $e({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&&Ne.set(r.id,r),"item/completed"===e&&(Ne.set(r.id,r),"agentMessage"===r.type&&Oe.has(r.id))){const e=Oe.get(r.id);return Oe.delete(r.id),void $e({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void Le({type:"item/started"===e?"item.started":"item.completed",item:F(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";Le({type:"error",message:e})}}}async function Ke(e,t={}){if(!ze(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,Ne.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";xe++;try{n=await(D?.(r))}catch{}finally{xe=Math.max(0,xe-1),Ue()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function Ve(){if(Ce)return!1;if(!1===Ie?.alive)return Me=!1,Ce=!0,!1;if(Ie&&Me)return!0;let t;try{t=await He()}catch(e){Be(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ie){const r=$({cwd:e,env:z,args:a({providerConfig:E,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(G,S,W.writableDirs)}),onNotification:Je,onServerRequest:Ke,onClose:e=>{re||Ce||Se.has(r)||(Me=!1,Ce=!0,Be(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ie=r,await Ie.start(),Ue()}return K=await Ie.startThread({threadId:V>0?K:null,cwd:e,model:ye,sandbox:S,approvalPolicy:B.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Ue(),t?.url&&(await Ie.waitForMcpServer({threadId:K,name:"thinkpool"}),Ue()),Me=!0,Le({type:"thread.started",thread_id:K}),!0}catch{Ce=!0;try{Ie?.end()}catch{}return Ie=null,Me=!1,!1}}function Qe({force:e=!1}={}){const t=Ie,r=_e;if(t&&e){We(r),Ce=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)}else if(t&&r)We(r),Promise.resolve(t.interrupt({threadId:K,turnId:r})).catch(()=>{Ce=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)});else if(t){Ce=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)}if(ee){const e=ee;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(ee===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function Ye(e,t={}){if(re||te||fe)return!0;if(!await Ve())return!1;if(re||te||fe)return!0;je.setUsageBaseline(K?readCodexThreadUsage(K):null),ne=!0;try{if(he=!1,_e=await Ie.startTurn({threadId:K,input:e,images:Array.isArray(t.images)?t.images:[],model:ye,effort:k(ve),approvalPolicy:B.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(ye,ve)}),Ue(),te||re){We(_e);try{await Ie.interrupt({threadId:K,turnId:_e})}catch{}return!0}const r=await Ie.waitForTurn(_e);We(r?.turn?.id||_e);const n=r?.turn?.status;te?Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(me||fe)||("interrupted"===n?Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(me=!1,Le({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(me=!1,Le({type:"turn.completed"})))}catch(e){Ce=!0,Me=!1,We(_e);try{Ie?.end()}catch{}Ie=null,re||me||fe||Le({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{qe(),_e=null,ne=!1,Ne.clear(),Oe.clear()}return!0}async function Ze(e){if(re||te||fe)return!0;if(!await Ve())return!1;if(re||te||fe)return!0;je.setUsageBaseline(K?readCodexThreadUsage(K):null),ne=!0;try{he=!1;const t=await Ie.startReview({threadId:K,target:x(e)});if(_e=t?.turn?.id||t?.turnId,!_e)throw new Error("Codex review/start returned no turn id");if(Ue(),te||re){We(_e);try{await Ie.interrupt({threadId:K,turnId:_e})}catch{}return!0}const r=await Ie.waitForTurn(_e);We(r?.turn?.id||_e);const n=r?.turn?.status;te?Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(me||fe)||("interrupted"===n?Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(me=!1,Le({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(me=!1,Le({type:"turn.completed"})))}catch(e){Ce=!0,Me=!1,We(_e);try{Ie?.end()}catch{}Ie=null,re||me||fe||Le({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{qe(),_e=null,ne=!1,Ne.clear(),Oe.clear()}return!0}async function et(t,n={}){if(re||te||fe)return;let o;try{o=await He()}catch(e){Be(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Be(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===V?null:K,model:ye,effort:ve,sandbox:S,approvalPolicy:B.approvalPolicy,providerConfig:E,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(G,S,W.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});return je.setUsageBaseline(K?readCodexThreadUsage(K):null),new Promise(t=>{let n;try{n=r.openSync("/dev/null","r")}catch{}const o={cwd:e,env:z,stdio:["ignore","pipe","pipe"]};if(null!=n&&(o.stdio=[n,"pipe","pipe"]),ne=!0,he=!1,ee=q("codex",a,o),null!=n)try{r.closeSync(n)}catch{}const i=[];s.createInterface({input:ee.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}i.push(t.type),"thread.started"===t.type&&t.thread_id&&(K=K||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(u=!0,me=!1),Le(t)});let l="",u=!1,d=!1;ee.stderr.on("data",e=>{l+=e.toString(),l.length>800&&(l=l.slice(-800))}),ee.on("error",e=>{d||(d=!0,ee=null,me||fe||Xe({kind:"error",message:`codex failed to start: ${e.message}`,recoverable:!0}),t({preTurnInitFailure:!1}))}),ee.on("close",e=>{if(d)return;d=!0,ee=null,ne=!1;const r=`codex exec exited ${e}${l?": "+l.trim().slice(-300):""}`;0===e||null==e||u||!codexExecFailedBeforeTurn({stderr:l,eventTypes:i})||te||fe?((!me&&!fe||u||te)&&(te&&!u?Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}):te||u||0===e||null==e||Xe({kind:"error",message:r,recoverable:!0})),t({preTurnInitFailure:!1})):t({preTurnInitFailure:!0,message:r})})})}function tt(){if(re)return;const e=Ae.shift();e&&(Pe=Pe.then(async()=>{Ge();const t=e.options?.turnCause||null,r=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:t}),n=buildCodexPrompt({text:e.text,terminalRolePrompt:N,rolePrompt:O,roomContext:()=>Z({force:r}),firstTurn:0===V,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:t}),o=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let s=!1,a=!1;for(;!re&&!te&&!fe;){if(!(o?await Ze(e.text):await Ye(n,e.options)))if(o)me=!1,Xe({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{me&&(me=!1,s=!0,Ge({retry:!0}),De({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const t=await et(n,e.options);if(t?.preTurnInitFailure){if(!(a||re||te||fe)){a=!0,Ge({retry:!0}),De({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Xe({kind:"error",message:t.message,recoverable:!0})}}if(!me||s||fe||re||te)break;me=!1,s=!0,Ge({retry:!0}),De({kind:"note",text:"retrying the stalled turn on a fresh connection"})}V++,tt()}))}return H&&!J&&queueMicrotask(()=>Be("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(z.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||ke.enabled||queueMicrotask(()=>Be(`Codex App Server disabled: ${ke.reason}`)),ge=se(()=>{const e=oe()-de,t=v({turnActive:ne,awaitingUser:xe,quietMs:e,stallMs:ie,forceStopMs:le,stalledSent:ce,stallRetried:pe});if("none"===t)return;if("retry"===t&&!he)return fe=!0,me=!1,De({kind:"error",recoverable:!0,message:"Codex stopped responding after the turn began. The transport was stopped, but the prompt was not replayed automatically."}),Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void Qe({force:!0});const r=b(t,e);if(r&&De(r),"status"!==t){if("retry"===t)return pe=!0,me=!0,de=oe(),ce=!1,void Qe();fe=!0,me=!1,Ae.length=0,Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),Qe({force:!0})}else ce=!0},ue),ge.unref?.(),{get sessionId(){return K},get usageSnapshot(){return be},get turnActive(){return ne||Ee},get canSteer(){return!(!Ie||!1===Ie.alive||!Me||Ce)},get started(){return ne||Ee||!!ee||!(!Ie||!1===Ie.alive)},sendTurn(t,r={}){if(re)return!1;if(!ne&&0===Ae.length&&!Me&&!ee){const e="function"==typeof L?L():{ok:!0};if(!1===e?.ok)return $e({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=Q++,o=Y;if(Y=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!ne&&0===V&&A)try{e=A()||e}catch{}if(ne&&Ie&&_e){Ue();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:N,rolePrompt:O,roomContext:()=>Z({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=_e;return Re=Re.then(()=>Ie.steer({threadId:K,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(Ae.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ne||1!==Ae.length||tt()):$e({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return Ae.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ne||Ee||(te=!1,Ge()),1!==Ae.length||Ee||tt(),!0},async park(){if(re||ne||Ee||Ae.length)return!1;const e=Pe;try{await e}catch{}try{await Re}catch{}if(re||ne||Ee||Ae.length||ee||_e)return!1;const t=Ie;if(t){Se.add(t),Ie=null,Me=!1,Ne.clear(),Oe.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Pe,t=ne&&!_e&&!ee;te=!0,me=!1,fe=!1,Ae.length=0,We(_e);let r=Promise.resolve();if(Ie&&_e&&(r=Ie.interrupt({threadId:K,turnId:_e}).catch(()=>{})),ee){try{ee.kill("SIGTERM")}catch{}setTimeout(()=>{try{ee?.kill("SIGKILL")}catch{}},1500)}else t&&Xe({kind:"result",subtype:"aborted",sessionId:K,model:ye,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(re=!0,ne=!1,ge&&ae(ge),$e.cancel(),Ae.length=0,We(_e),ee)try{ee.kill()}catch{}try{Ie?.end()}catch{}we?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||ne)return!1;ye=String(e),je.setModel(ye);const t=I(ve,{models:p,model:ye});return t!==ve&&(ve=t,$e({kind:"effort",level:ve})),!0},setMode:e=>!(ne||!CODEX_MODE_CONFIG[e]||(G=e,B=codexConfigForMode(G),S=B.sandbox,K=null,V=0,be=null,Me=!1,0)),setEffort:e=>!(ne||!C({models:p,model:ye}).includes(e)||(ve=e,$e({kind:"effort",level:ve}),0)),clearContext:()=>!ne&&(K=null,V=0,be=null,Me=!1,!0),async compactContext(){if(ne||Ee)return!1;te=!1,Ee=!0;let e=null,t=!1;try{if(!await Ve()||ne)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Te=null,r(new Error("Codex did not start the compaction turn"))},5e3),Te={resolve:t,reject:r}});await Ie.compact({threadId:K}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ie.waitForTurn(n);We(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)?(Be(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Be(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Te=null,Ee=!1,Ae.length&&tt()}},async accountUsage(){if(!await Ve())return null;const[e,t]=await Promise.all([Ie.accountUsage().catch(()=>null),Ie.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 d}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as c,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as b}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as w}from"./catastrophic-command-guard.mjs";import{codexNativeReasoningEffort as k,codexReasoningEfforts as _,normalizeCodexReasoningEffort as C,normalizeReasoningEffortList as S}from"./reasoning-effort.mjs";const I=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const M=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=k(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:s,timeoutMs:a=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:d=r,spawnImpl:c=t}={}){const p=String(e||"").trim();if(!p)throw new Error("codex one-shot requires a prompt");const m=!l,f=l||d.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let h=null;try{i&&(h=o.join(f,"output-schema.json"),d.writeFileSync(h,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...h?["--output-schema",h]:[],p];return await 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=c("codex",e,{cwd:f,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))})}finally{if(m)try{d.rmSync(f,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:S(e.supported_reasoning_levels)}))}catch{return[]}}const T=new Map;function E(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=T.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 T.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=E(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=E(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 I.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}),d=[o?c:"",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 d.length?`<thinkpool-context>\n${d.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:s="untrusted",providerConfig:a,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:d}){const c=normalizeCodexSandbox(n),p=[...w(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===c?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${c}"`]]:["exec","--json","--sandbox",c,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${s}"`);const m=k(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(d??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const F=/\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i;export function codexApprovalCard(e,t={},r=null){const n=String(t.approvalId||t.itemId||`${t.turnId||"turn"}:${e}`);if("item/tool/requestUserInput"===e){const e=Array.isArray(t.questions)?t.questions:[];return{id:n,toolName:"AskUserQuestion",input:{questions:e},risk:"ask",questions:e,asker:"Codex",answerFormat:"codex",...Number.isFinite(t.autoResolutionMs)&&t.autoResolutionMs>0?{autoResolutionMs:t.autoResolutionMs}:{}}}if("item/commandExecution/requestApproval"===e){const e=t.command||r?.command||"";return{id:n,toolName:"Bash",input:{command:e,cwd:t.cwd||r?.cwd||"",...t.reason?{reason:t.reason}:{}},risk:F.test(e)?"high":"medium"}}if("item/fileChange/requestApproval"===e){const e=Array.isArray(r?.changes)?r.changes:[],o=e[0]||{};return{id:n,toolName:e.length>1?"MultiEdit":"add"===o.kind?"Write":"Edit",input:e.length>1?{edits:e.map(e=>({file_path:e.path||"",path:e.path||"",kind:e.kind||"update"})),...t.reason?{reason:t.reason}:{}}:{file_path:o.path||t.grantRoot||"",path:o.path||t.grantRoot||"",kind:o.kind||"update",...t.reason?{reason:t.reason}:{}},risk:"medium"}}return null}export function codexApprovalResponse(e){return{decision:"always"===e?"acceptForSession":"allow"===e?"accept":"decline"}}export function codexUserInputResponse(e,t=[]){return h(e,t)}function N(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:c,models:p=[],effort:h="high",resume:w,env:S,sandbox:I,mode:T="default",onEvent:E,providerConfig:F,terminalRolePrompt:O,rolePrompt:A,roomContext:R,mcpServers:P,prepareCwd:D=null,requestPermission:U,appServerGate:j=i,appServerFactory:$=l,mcpHttpFactory:q=d,spawnImpl:L=t,admitStart:X=null,stallOptions:G={}}){let z=CODEX_MODE_CONFIG[T]?T:"default",B=codexConfigForMode(z);I=normalizeCodexSandbox(I||B.sandbox);const W=codexPublishAccess({cwd:e,env:{...process.env,...S||{}}}),H=W.env,J=w||null,K=!J||codexThreadCanResume(J,{home:H.CODEX_HOME||o.join(n.homedir(),".codex")});let V=K?J:null,Y=V?1:0,Q=0,Z=!0;const ee=m(R);let te=null,re=!1,ne=!1,oe=!1;const se="function"==typeof G.now?G.now:Date.now,ae="function"==typeof G.setInterval?G.setInterval:setInterval,ie="function"==typeof G.clearInterval?G.clearInterval:clearInterval,le=Number.isFinite(G.stallMs)?Math.max(1,Number(G.stallMs)):Math.max(3e4,parseInt(H.TP_STALL_MS,10)||9e4),ue=Number.isFinite(G.forceStopMs)?Math.max(le,Number(G.forceStopMs)):Math.max(3*le,parseInt(H.TP_FORCE_STOP_MS,10)||3e5),de=Number.isFinite(G.intervalMs)?Math.max(1,Number(G.intervalMs)):5e3;let ce=se(),pe=!1,me=!1,fe=!1,he=!1,xe=!0,ge=0,ye=0,ve=null,be=c||null,we=C(h,{models:p,model:be}),ke=V?readCodexThreadUsage(V):null,_e=null;const Ce=j({env:H});let Se=!Ce.enabled,Ie=null,Me=!1;const Te=new WeakSet;let Ee=null,Fe=null,Ne=!1;const Oe=new Set,Ae=new Map,Re=new Set,Pe=new Map;let De=Promise.resolve(),Ue=Promise.resolve();const je=[],$e=y(e=>{try{E?.(e)}catch{}}),qe=()=>{ce=se(),pe=!1},Le=e=>{qe(),$e(e)};Le.cancel=()=>$e.cancel();const Xe=new u({onEvent:Le,model:c||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(ke=t),t||ke}}),Ge=()=>{const e=[...Pe.values()];Pe.clear();for(const t of e)t.text&&Le({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},ze=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Ge(),oe=!1),Xe.push(e)},Be=e=>{Ge(),oe=!1,Le(e)},We=({retry:e=!1,recoveryAttempt:t=0}={})=>{oe=!0,ce=se(),pe=!1,fe=!1,he=!1,xe=!0,ge=Math.max(0,Number(t)||0),e||(me=!1)},He=e=>Le({kind:"note",text:e}),Je=(e=Ee)=>{const t=String(e||"");if(t)for(Oe.add(t);Oe.size>64;)Oe.delete(Oe.values().next().value)},Ke=(e={})=>{const t=String(e.turnId||"");return!(!t||ne||re||Oe.has(t)||t!==String(Ee||""))};async function Ve(){const e=P?.thinkpool;return e?(_e||(_e=q({sdkServer:e})),_e):null}function Ye(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&V&&String(t.threadId)!==String(V))return;const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(ke=e,Ne){const{used:t,max:r}=e.context;Le({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:be}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(ne||re)return void Je(e);if(Fe){const t=Fe;return Fe=null,void t.resolve(e)}return Ee||(Ee=e),void qe()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||Ke(t)){if(qe(),"turn/plan/updated"===e){const e=(Array.isArray(t.plan)?t.plan:[]).map(({step:e,status:t})=>({content:e||"",activeForm:e||"",status:"inProgress"===t?"in_progress":"completed"===t?"completed":"pending"})).filter(e=>e.content);return void Le({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||Ee||"active"}`,r=Pe.get(e)||{cid:`codex-stream:${V||"thread"}:${e}`,text:""};return r.text+=String(t.delta),Pe.set(e,r),void Le({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&&(Ae.set(r.id,r),r.id&&M(r)&&Re.add(r.id)),"item/completed"===e&&(r.id&&Re.delete(r.id),Ae.set(r.id,r),"agentMessage"===r.type&&Pe.has(r.id))){const e=Pe.get(r.id);return Pe.delete(r.id),void Le({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void ze({type:"item/started"===e?"item.started":"item.completed",item:N(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";ze({type:"error",message:e})}}}async function Qe(e,t={}){if(!Ke(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,Ae.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";ye++;try{n=await(U?.(r))}catch{}finally{ye=Math.max(0,ye-1),qe()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function Ze(){if(Se)return!1;if(!1===Ie?.alive)return Me=!1,Se=!0,!1;if(Ie&&Me)return!0;let t;try{t=await Ve()}catch(e){He(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ie){const r=$({cwd:e,env:H,args:a({providerConfig:F,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(z,I,W.writableDirs)}),onNotification:Ye,onServerRequest:Qe,onClose:e=>{ne||Se||Te.has(r)||(Me=!1,Se=!0,He(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ie=r,await Ie.start(),qe()}return V=await Ie.startThread({threadId:Y>0?V:null,cwd:e,model:be,sandbox:I,approvalPolicy:B.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),qe(),t?.url&&(await Ie.waitForMcpServer({threadId:V,name:"thinkpool"}),qe()),Me=!0,ze({type:"thread.started",thread_id:V}),!0}catch{Se=!0;try{Ie?.end()}catch{}return Ie=null,Me=!1,!1}}function et({force:e=!1}={}){const t=Ie,r=Ee;if(t&&e){Je(r),Se=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)}else if(t&&r)Je(r),Promise.resolve(t.interrupt({threadId:V,turnId:r})).catch(()=>{Se=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)});else if(t){Se=!0,Me=!1;try{t.end()}catch{}Ie===t&&(Ie=null)}if(te){const e=te;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(te===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function tt(e,t={}){if(ne||re||he)return!0;if(!await Ze())return!1;if(ne||re||he)return!0;Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),oe=!0;try{if(xe=!1,Ee=await Ie.startTurn({threadId:V,input:e,images:Array.isArray(t.images)?t.images:[],model:be,effort:k(we),approvalPolicy:B.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(be,we)}),qe(),re||ne){Je(Ee);try{await Ie.interrupt({threadId:V,turnId:Ee})}catch{}return!0}const r=await Ie.waitForTurn(Ee);Je(r?.turn?.id||Ee);const n=r?.turn?.status;re?Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(fe||he)||("interrupted"===n?Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(fe=!1,ze({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(fe=!1,ze({type:"turn.completed"})))}catch(e){Se=!0,Me=!1,Je(Ee);try{Ie?.end()}catch{}Ie=null,ne||fe||he||ze({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Ge(),Ee=null,oe=!1,Re.clear(),Ae.clear(),Pe.clear()}return!0}async function rt(e){if(ne||re||he)return!0;if(!await Ze())return!1;if(ne||re||he)return!0;Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),oe=!0;try{xe=!1;const t=await Ie.startReview({threadId:V,target:x(e)});if(Ee=t?.turn?.id||t?.turnId,!Ee)throw new Error("Codex review/start returned no turn id");if(qe(),re||ne){Je(Ee);try{await Ie.interrupt({threadId:V,turnId:Ee})}catch{}return!0}const r=await Ie.waitForTurn(Ee);Je(r?.turn?.id||Ee);const n=r?.turn?.status;re?Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(fe||he)||("interrupted"===n?Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(fe=!1,ze({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(fe=!1,ze({type:"turn.completed"})))}catch(e){Se=!0,Me=!1,Je(Ee);try{Ie?.end()}catch{}Ie=null,ne||fe||he||ze({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Ge(),Ee=null,oe=!1,Re.clear(),Ae.clear(),Pe.clear()}return!0}async function nt(t,n={}){if(ne||re||he)return;let o;try{o=await Ve()}catch(e){He(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){He(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===Y?null:V,model:be,effort:we,sandbox:I,approvalPolicy:B.approvalPolicy,providerConfig:F,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(z,I,W.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});return Xe.setUsageBaseline(V?readCodexThreadUsage(V):null),new Promise(t=>{let n;try{n=r.openSync("/dev/null","r")}catch{}const o={cwd:e,env:H,stdio:["ignore","pipe","pipe"]};if(null!=n&&(o.stdio=[n,"pipe","pipe"]),oe=!0,xe=!1,te=L("codex",a,o),null!=n)try{r.closeSync(n)}catch{}const i=[];s.createInterface({input:te.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}i.push(t.type),"item.started"===t.type&&t.item?.id&&M(t.item)&&Re.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Re.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(V=V||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(u=!0,fe=!1),ze(t)});let l="",u=!1,d=!1;te.stderr.on("data",e=>{l+=e.toString(),l.length>800&&(l=l.slice(-800))}),te.on("error",e=>{d||(d=!0,te=null,Re.clear(),fe||he||Be({kind:"error",message:`codex failed to start: ${e.message}`,recoverable:!0}),t({preTurnInitFailure:!1}))}),te.on("close",e=>{if(d)return;d=!0,te=null,oe=!1,Re.clear();const r=`codex exec exited ${e}${l?": "+l.trim().slice(-300):""}`;0===e||null==e||u||!codexExecFailedBeforeTurn({stderr:l,eventTypes:i})||re||he?((!fe&&!he||u||re)&&(re&&!u?Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}):re||u||0===e||null==e||Be({kind:"error",message:r,recoverable:!0})),t({preTurnInitFailure:!1})):t({preTurnInitFailure:!0,message:r})})})}function ot(){if(ne)return;const e=je.shift();e&&(Ue=Ue.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);We({recoveryAttempt:t}),t>0&&$e({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>ee({force:n}),firstTurn:0===Y,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!ne&&!re&&!he;){if(!(s?await rt(e.text):await tt(o,e.options)))if(s)fe=!1,Be({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{fe&&(fe=!1,a=!0,We({retry:!0,recoveryAttempt:t}),$e({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await nt(o,e.options);if(r?.preTurnInitFailure){if(!(i||ne||re||he)){i=!0,We({retry:!0,recoveryAttempt:t}),$e({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Be({kind:"error",message:r.message,recoverable:!0})}}if(!fe||a||he||ne||re)break;fe=!1,a=!0,We({retry:!0,recoveryAttempt:t}),$e({kind:"note",text:"retrying the stalled turn on a fresh connection"})}Y++,ot()}))}return J&&!K&&queueMicrotask(()=>He("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(H.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Ce.enabled||queueMicrotask(()=>He(`Codex App Server disabled: ${Ce.reason}`)),ve=ae(()=>{const e=se()-ce,t=v({turnActive:oe,awaitingUser:ye,quietMs:e,stallMs:le,forceStopMs:ue,stalledSent:pe,stallRetried:me});if("none"===t)return;if("retry"===t&&!xe)return he=!0,fe=!1,ge>0?($e({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."}),$e({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Re.size>0?$e({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."}):(je.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:Q++,forceFullReminder:!0}),$e({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."})),Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void et({force:!0});const r=b(t,e);if(r&&$e(r),"status"!==t){if("retry"===t)return me=!0,fe=!0,ce=se(),pe=!1,void et();he=!0,fe=!1,je.length=0,Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),et({force:!0})}else pe=!0},de),ve.unref?.(),{get sessionId(){return V},get usageSnapshot(){return ke},get turnActive(){return oe||Ne},get canSteer(){return!(!Ie||!1===Ie.alive||!Me||Se)},get started(){return oe||Ne||!!te||!(!Ie||!1===Ie.alive)},sendTurn(t,r={}){if(ne)return!1;if(!oe&&0===je.length&&!Me&&!te){const e="function"==typeof X?X():{ok:!0};if(!1===e?.ok)return Le({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=Q++,o=Z;if(Z=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!oe&&0===Y&&D)try{e=D()||e}catch{}if(oe&&Ie&&Ee){qe();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>ee({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Ee;return De=De.then(()=>Ie.steer({threadId:V,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(je.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),oe||1!==je.length||ot()):Le({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return je.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),oe||Ne||(re=!1,We({recoveryAttempt:0})),1!==je.length||Ne||ot(),!0},async park(){if(ne||oe||Ne||je.length)return!1;const e=Ue;try{await e}catch{}try{await De}catch{}if(ne||oe||Ne||je.length||te||Ee)return!1;const t=Ie;if(t){Te.add(t),Ie=null,Me=!1,Ae.clear(),Pe.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Ue,t=oe&&!Ee&&!te;re=!0,fe=!1,he=!1,je.length=0,Je(Ee);let r=Promise.resolve();if(Ie&&Ee&&(r=Ie.interrupt({threadId:V,turnId:Ee}).catch(()=>{})),te){try{te.kill("SIGTERM")}catch{}setTimeout(()=>{try{te?.kill("SIGKILL")}catch{}},1500)}else t&&Be({kind:"result",subtype:"aborted",sessionId:V,model:be,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(ne=!0,oe=!1,ve&&ie(ve),Le.cancel(),je.length=0,Je(Ee),te)try{te.kill()}catch{}try{Ie?.end()}catch{}_e?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||oe)return!1;be=String(e),Xe.setModel(be);const t=C(we,{models:p,model:be});return t!==we&&(we=t,Le({kind:"effort",level:we})),!0},setMode:e=>!(oe||!CODEX_MODE_CONFIG[e]||(z=e,B=codexConfigForMode(z),I=B.sandbox,V=null,Y=0,ke=null,Me=!1,0)),setEffort:e=>!(oe||!_({models:p,model:be}).includes(e)||(we=e,Le({kind:"effort",level:we}),0)),clearContext:()=>!oe&&(V=null,Y=0,ke=null,Me=!1,!0),async compactContext(){if(oe||Ne)return!1;re=!1,Ne=!0;let e=null,t=!1;try{if(!await Ze()||oe)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Fe=null,r(new Error("Codex did not start the compaction turn"))},5e3),Fe={resolve:t,reject:r}});await Ie.compact({threadId:V}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ie.waitForTurn(n);Je(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)?(He(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(He(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Fe=null,Ne=!1,je.length&&ot()}},async accountUsage(){if(!await Ze())return null;const[e,t]=await Promise.all([Ie.accountUsage().catch(()=>null),Ie.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.404",
|
|
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",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"sdk-admission.mjs",
|
|
21
21
|
"launcher.mjs",
|
|
22
22
|
"privacy-report.mjs",
|
|
23
|
-
"demo-project.mjs",
|
|
24
23
|
"agent-visibility.mjs",
|
|
25
24
|
"byok-detect.mjs",
|
|
26
25
|
"context-contract.mjs",
|
package/publish-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"package": "thinkpool-pair",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.404",
|
|
5
5
|
"kind": "minified",
|
|
6
|
-
"minifiedModules":
|
|
7
|
-
"sourceModuleBytes":
|
|
8
|
-
"artifactModuleBytes":
|
|
6
|
+
"minifiedModules": 133,
|
|
7
|
+
"sourceModuleBytes": 1817244,
|
|
8
|
+
"artifactModuleBytes": 830823,
|
|
9
9
|
"files": {
|
|
10
|
-
"package.json": "
|
|
10
|
+
"package.json": "937a772637482ffb5819ec93057e6a12d1488ab9198315107ab317bfded710eb",
|
|
11
11
|
"LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
|
|
12
|
-
"bridge.mjs": "
|
|
12
|
+
"bridge.mjs": "82a2fc179d1202c86cea0c2f90399f377d2d653fb6108a77a942aba118226870",
|
|
13
13
|
"bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
|
|
14
14
|
"command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
|
|
15
15
|
"abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"sdk-admission.mjs": "eb33725cc33347b900c75289eae7917f6ce3526203711515aecd48d43b3c895d",
|
|
21
21
|
"launcher.mjs": "a63c63492be0e97b41aae9c0ae24045903207a768c2cb86f8d7a9297d2741f1b",
|
|
22
22
|
"privacy-report.mjs": "b444610be6de8b3bcf0086d585594286ef000dad1e075df87013003a315bf211",
|
|
23
|
-
"demo-project.mjs": "dcabce0fe1f8e612d2a1dbec7c67ab71ce24792fe6044b1a4e94a53161ccc6ac",
|
|
24
23
|
"agent-visibility.mjs": "6afed5ce9120c4aaa7582d3cfb30c0658186a936ad1f87b1b01dfd20e988f839",
|
|
25
24
|
"byok-detect.mjs": "f31f2ca426737581a97c391b3cecfc75f0207cfa31f51c6bf4a9148309c9625d",
|
|
26
25
|
"context-contract.mjs": "ffebb435dc5d75c289329bf16ab909df9d634a2b508154c250c6b25906a02a91",
|
|
@@ -28,11 +27,11 @@
|
|
|
28
27
|
"claude-session.mjs": "8992a67c66cb5745f7a72c373ea2bf06f3dcc77a68a3735f21dcbe8968623edc",
|
|
29
28
|
"terminal-name.mjs": "f39bb8d1e148ab9a74cbb96438d31e524d6597b8035f00d7a906c28c306c0cf8",
|
|
30
29
|
"claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
|
|
31
|
-
"codex-session.mjs": "
|
|
30
|
+
"codex-session.mjs": "6b8c2e5d95e5ccaa8238e448e749cf8f6907ebd844af44b0c5b91ebd4aa2e280",
|
|
32
31
|
"question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
|
|
33
32
|
"thinkpool-room-prompt.mjs": "b8cc82ccf4262f6431c2047bbff31ac29c47a0d6c68a64640a2c24d94df4a746",
|
|
34
33
|
"thinkpool-prompt-contracts.mjs": "051602d7d2fe9b34df92957bd3922c5eeebedbcc60c892adf415243887d8c62f",
|
|
35
|
-
"thinkpool-capabilities.json": "
|
|
34
|
+
"thinkpool-capabilities.json": "5cf22e3a779aa0d6fb76c8162c3ffb334fc4a0d20d61e024740fd2467fd21f92",
|
|
36
35
|
"codex-app-server.mjs": "d50e30ba6cb2a383c90d89186bc7a13f4caf900c977dd501572468986009470e",
|
|
37
36
|
"codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
|
|
38
37
|
"codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
|
|
@@ -146,7 +145,7 @@
|
|
|
146
145
|
"providers.mjs": "c0757433d30bf2f22e80f85e779cdeeeb10b412d568965f1115d4de2c8aa0314",
|
|
147
146
|
"provider-resilience.mjs": "a5a3921040d1860bb82f1d3b53c6ec25a5e230345c0131f2213c535e07fc497e",
|
|
148
147
|
"model-prices.mjs": "91f0bd8688f59ce6bff7743710d3b1139f4f0823d6b95fa68cd9b6fdcdba6ab4",
|
|
149
|
-
"README.md": "
|
|
148
|
+
"README.md": "94c866f940b8a21d4c8dbb0fe256b7910f423eadb05140da076bc17326170b79",
|
|
150
149
|
"publish-guard.mjs": "569dd788790e68ea822129f588fd16c1bb435324666552fe9dfe7c435ab3f617"
|
|
151
150
|
}
|
|
152
151
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 36,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"id": "work-routing",
|
|
81
|
-
"version":
|
|
81
|
+
"version": 13,
|
|
82
82
|
"providerRoutingContract": "A Claude lane may select a connected Anthropic-compatible provider by durable id, unique display name, or unique configured model. Unknown or ambiguous references fail closed and must never fall through to built-in Claude.",
|
|
83
83
|
"openerParityContract": "Every top-level terminal may use spawn_terminal or open_main_terminal to open every supported structured runtime: Claude, Codex, or Hermes. Runtime-specific provider/model validation remains authoritative and occurs before inference. spawn_terminal may carry bounded Cascade handback metadata (cascade/task/wave/dependencies/expected wave keys/wake policy); wave workers wake their owner once only after the declared wave settles, while legacy workers remain immediate.",
|
|
84
84
|
"codexEffortContract": "Codex Ultra is a native model setting. It never enables or implies thinkpool Cascade; Cascade remains prompt intent and has no dedicated UI control.",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"id": "work-routing",
|
|
88
88
|
"tools": ["spawn_terminal", "open_main_terminal", "close_terminal"],
|
|
89
89
|
"trigger": "\\b(?:open|launch|start|spawn|create)\\b.{0,40}\\b(?:new|separate|independent|main|top[- ]?level)?\\s*terminals?\\b|\\b(?:new|separate|independent|main|top[- ]?level)\\s+terminals?\\b|\\b(parallel|delegate|worker|sub[- ]?terminal|ensemble|conductor|cascade|spawn_terminal|open_main_terminal|close_terminal)\\b",
|
|
90
|
-
"prompt": "A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. 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. For a Cascade wave, pass the bounded cascade object so dependencies and the expected wave task keys are durable; choose wakePolicy=wave to receive one batch only after every declared member finishes, or immediate for legacy behavior. Collect and verify each worker result with one targeted read_terminal call after completion. Leave every finished worker visible for review until a person dismisses it with the Ensemble X. Never call close_terminal merely because work finished. Do not poll workers or repeat the ROOM NOW roster. Review slices are structurally read-only and must use the review slice type; they never inherit autonomous bypass authority."
|
|
90
|
+
"prompt": "A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. 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. Launch receipt gate: never say or imply that a Cascade, worker wave, or visible terminal has started until every required spawn_terminal or open_main_terminal call returns success with the room-visible terminal ref. A plan, intent, internal runtime subagent, background task, or collaboration worker is not a launch receipt and must never substitute for thinkpool room tools. If the required opener is absent, fails, or returns no visible ref, say that it did not start and report the exact blocker. For a Cascade wave, pass the bounded cascade object so dependencies and the expected wave task keys are durable; choose wakePolicy=wave to receive one batch only after every declared member finishes, or immediate for legacy behavior. Collect and verify each worker result with one targeted read_terminal call after completion. Leave every finished worker visible for review until a person dismisses it with the Ensemble X. Never call close_terminal merely because work finished. Do not poll workers or repeat the ROOM NOW roster. Review slices are structurally read-only and must use the review slice type; they never inherit autonomous bypass authority."
|
|
91
91
|
}
|
|
92
92
|
],
|
|
93
|
-
"expandedPrompt": "MAIN TERMINAL ROUTING (authoritative): a person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. spawn_terminal is only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. CASCADE WORKFLOW (default for non-trivial work that genuinely decomposes; mandatory when the people ask for Cascade, tiered lanes, one-shot flow, or unattended multi-slice execution): first investigate enough to name evidence-backed slices and their dependencies, then post a short plan in the room. The current top-level/conductor terminal keeps decomposition, integration, and final judgment; use spawn_terminal only for bounded worker slices. Parallelize only disjoint slices and encode dependencies instead of holding them in your head. Choose sliceType=scaffold for mechanical/search work, feature or fix for implementation, and review with a balanced capable tier for adversarial verification. Review slices are structurally read-only: they resolve to a native review/Plan mode before launch and can never inherit autonomous bypass authority. Every worker brief names the evidence/diagnosis, exact scope, observable acceptance proof, relevant repo gates, whether merge/publish is required, and a safe-skip escape hatch. ROOM NOW is the default roster and completion signals wake the conductor; do not spend the bounded read allowance polling or repeating a no-argument roster. After dispatch, remain responsible: once an owned worker is finished, make one targeted read_terminal call to collect its result, verify its claim, leave the finished lane visible for the person to review and dismiss with the Ensemble X, and dispatch newly unblocked work. Never call close_terminal merely because work finished. Preserve enough targeted reads for every open worker and the final adversarial review; never declare the Cascade done or yield a final result while owned workers remain uncollected. Check origin/main CI before merge-bearing waves, coordinate shared fixtures/publishers, and treat sibling pushes as context rather than proof. Run an adversarial review after builders (or pipeline review behind completed phases), finish with production-condition evidence, verify every reported SHA/version, and leave every collected worker visible until a person dismisses it with the Ensemble X.",
|
|
93
|
+
"expandedPrompt": "MAIN TERMINAL ROUTING (authoritative): a person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. spawn_terminal is only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. LAUNCH RECEIPT GATE (hard invariant): never say or imply that a Cascade, worker wave, or visible terminal has started until every required spawn_terminal or open_main_terminal call returns success with the room-visible terminal ref. A plan, intent, internal runtime subagent, background task, or collaboration worker is not a launch receipt and must never substitute for thinkpool room tools. If the required opener is absent, fails, or returns no visible ref, say that it did not start and report the exact blocker. CASCADE WORKFLOW (default for non-trivial work that genuinely decomposes; mandatory when the people ask for Cascade, tiered lanes, one-shot flow, or unattended multi-slice execution): first investigate enough to name evidence-backed slices and their dependencies, then post a short plan in the room. The current top-level/conductor terminal keeps decomposition, integration, and final judgment; use spawn_terminal only for bounded worker slices. Parallelize only disjoint slices and encode dependencies instead of holding them in your head. Choose sliceType=scaffold for mechanical/search work, feature or fix for implementation, and review with a balanced capable tier for adversarial verification. Review slices are structurally read-only: they resolve to a native review/Plan mode before launch and can never inherit autonomous bypass authority. Every worker brief names the evidence/diagnosis, exact scope, observable acceptance proof, relevant repo gates, whether merge/publish is required, and a safe-skip escape hatch. ROOM NOW is the default roster and completion signals wake the conductor; do not spend the bounded read allowance polling or repeating a no-argument roster. After dispatch, remain responsible: once an owned worker is finished, make one targeted read_terminal call to collect its result, verify its claim, leave the finished lane visible for the person to review and dismiss with the Ensemble X, and dispatch newly unblocked work. Never call close_terminal merely because work finished. Preserve enough targeted reads for every open worker and the final adversarial review; never declare the Cascade done or yield a final result while owned workers remain uncollected. Check origin/main CI before merge-bearing waves, coordinate shared fixtures/publishers, and treat sibling pushes as context rather than proof. Run an adversarial review after builders (or pipeline review behind completed phases), finish with production-condition evidence, verify every reported SHA/version, and leave every collected worker visible until a person dismisses it with the Ensemble X.",
|
|
94
94
|
"impact": [
|
|
95
95
|
{"path": "bridge/bridge.mjs", "diffPattern": "spawn_terminal|open_main_terminal|close_terminal|cascadeRole|spawnDepth"},
|
|
96
96
|
{"path": "bridge/lane-lifecycle.mjs"},
|
package/demo-project.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:os";import n from"node:path";const r=Object.freeze({"README.md":"# thinkpool demo project\n\nThis is a disposable, dependency-free project created by `thinkpool-pair demo-project`.\nIt is deliberately separate from your real repositories.\n\nTry this first task in a thinkpool room:\n\n> Read the project, explain the smallest safe change, then add a way to hide completed items. Run the tests before showing the result.\n\nUseful commands:\n\n```bash\nnpm test\nnpm run serve\n```\n\nThe local server uses only Node.js. Open the printed URL in a browser on the bridge computer.\n","package.json":'{\n "name": "thinkpool-disposable-demo",\n "version": "1.0.0",\n "private": true,\n "type": "module",\n "scripts": {\n "test": "node --test",\n "serve": "node serve.mjs"\n }\n}\n',"index.html":'<!doctype html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <title>Release checklist</title>\n <link rel="stylesheet" href="styles.css" />\n </head>\n <body>\n <main>\n <p class="eyebrow">Disposable demo</p>\n <h1>Release checklist</h1>\n <p class="intro">A tiny project you can safely change with a coding agent.</p>\n <ul id="checklist" aria-label="Release checklist"></ul>\n </main>\n <script type="module" src="app.js"><\/script>\n </body>\n</html>\n',"styles.css":':root {\n color-scheme: dark;\n font-family: Inter, ui-sans-serif, system-ui, sans-serif;\n background: #0d1017;\n color: #f3f5fb;\n}\n\nbody {\n min-height: 100vh;\n margin: 0;\n display: grid;\n place-items: center;\n}\n\nmain {\n width: min(34rem, calc(100% - 2rem));\n border: 1px solid #2a3040;\n border-radius: 12px;\n background: #151925;\n padding: 2rem;\n}\n\n.eyebrow {\n color: #7f98ff;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.72rem;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\nh1 { margin: 0.5rem 0; }\n.intro { color: #a9b0c1; }\nul { display: grid; gap: 0.75rem; margin: 1.5rem 0 0; padding: 0; }\nli { list-style: none; border-top: 1px solid #2a3040; padding-top: 0.75rem; }\nli[data-done="true"] { color: #7f8798; text-decoration: line-through; }\n',"checklist.mjs":"export const checklist = Object.freeze([\n { id: 'scope', label: 'Confirm the scope', done: true },\n { id: 'tests', label: 'Run the tests', done: false },\n { id: 'review', label: 'Review the evidence', done: false },\n])\n\nexport function visibleItems(items, { hideCompleted = false } = {}) {\n return hideCompleted ? items.filter((item) => !item.done) : [...items]\n}\n","app.js":"import { checklist, visibleItems } from './checklist.mjs'\n\nconst list = document.querySelector('#checklist')\nfor (const item of visibleItems(checklist)) {\n const row = document.createElement('li')\n row.dataset.done = String(item.done)\n row.textContent = item.label\n list.append(row)\n}\n","checklist.test.mjs":"import assert from 'node:assert/strict'\nimport test from 'node:test'\nimport { checklist, visibleItems } from './checklist.mjs'\n\ntest('the default view keeps every checklist item', () => {\n assert.deepEqual(visibleItems(checklist).map((item) => item.id), ['scope', 'tests', 'review'])\n})\n\ntest('the optional filter hides completed items', () => {\n assert.deepEqual(visibleItems(checklist, { hideCompleted: true }).map((item) => item.id), ['tests', 'review'])\n})\n","serve.mjs":"import { createServer } from 'node:http'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst root = path.dirname(fileURLToPath(import.meta.url))\nconst realRoot = fs.realpathSync(root)\nconst types = { '.css': 'text/css', '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript' }\nconst server = createServer((request, response) => {\n let pathname\n try {\n pathname = decodeURIComponent(new URL(request.url, 'http://127.0.0.1').pathname)\n } catch {\n response.writeHead(400).end('Bad request')\n return\n }\n const name = pathname === '/' ? 'index.html' : pathname.slice(1)\n const file = path.resolve(root, name)\n if (!file.startsWith(root + path.sep)) {\n response.writeHead(403).end('Forbidden')\n return\n }\n try {\n const stat = fs.lstatSync(file)\n const realFile = fs.realpathSync(file)\n if (!stat.isFile() || stat.isSymbolicLink() || !realFile.startsWith(realRoot + path.sep)) {\n response.writeHead(403).end('Forbidden')\n return\n }\n const body = fs.readFileSync(file)\n response.writeHead(200, { 'Content-Type': types[path.extname(file)] || 'application/octet-stream' }).end(body)\n } catch {\n response.writeHead(404).end('Not found')\n }\n})\n\nserver.listen(4173, '127.0.0.1', () => console.log('Demo: http://127.0.0.1:4173'))\n"}),o=t=>{let r;try{r=e.lstatSync(t)}catch(e){if("ENOENT"===e?.code)return!1;throw e}if(r.isSymbolicLink()||!r.isFile())throw new Error(`refused non-regular demo file: ${n.basename(t)}`);return!0};export function createDemoProject({root:s=process.env.TP_PAIR_ROOT||n.join(t.homedir(),".thinkpool-pair")}={}){const i=(t=>{const r=n.resolve(String(t||""));if(!r||r===n.parse(r).root)throw new Error("refused unsafe demo-project root");try{if(e.lstatSync(r).isSymbolicLink())throw new Error("refused symlink demo-project root")}catch(e){if("ENOENT"!==e?.code)throw e}return r})(s);e.mkdirSync(i,{recursive:!0,mode:448});const a=e.realpathSync(i),l=n.resolve(i,"demo-project");if(l===i||!l.startsWith(i+n.sep))throw new Error("refused unsafe demo-project path");let c=null;try{c=e.lstatSync(l)}catch(e){if("ENOENT"!==e?.code)throw e}if(c){const e=c;if(e.isSymbolicLink()||!e.isDirectory())throw new Error("refused non-directory demo-project target")}else e.mkdirSync(l,{mode:448});if(!e.realpathSync(l).startsWith(a+n.sep))throw new Error("refused demo-project path outside bridge state");const d=[],m=[];for(const[t,s]of Object.entries(r)){const r=n.join(l,t);o(r)?m.push(t):(e.writeFileSync(r,s,{flag:"wx",mode:384}),d.push(t))}return{project:l,created:d,preserved:m}}export function formatDemoProjectResult({project:e,created:t,preserved:n}){return[""," thinkpool DISPOSABLE DEMO PROJECT",` ${e}`,` ${t.length?`created ${t.length} files`:"already ready"}.${n.length?` Existing files were preserved (${n.length}).`:""}`,""," Start the bridge from this directory:",` cd ${JSON.stringify(e)}`," npx thinkpool-pair@latest",""," In the room, keep the first terminal in Plan and try:"," “Read the project, explain the smallest safe change, then add a way to hide completed items. Run the tests before showing the result.”",""].join("\n")}export function runDemoProject(e){const t=createDemoProject(e);return process.stdout.write(formatDemoProjectResult(t)),t}
|