thinkpool-pair 0.7.431 → 0.7.433

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/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 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
+ 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 b}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as w}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 T,STANDARD_REASONING_EFFORT_IDS as M}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 E=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 F=e=>[...new Set((Array.isArray(e)?e:[]).map(e=>normalizeCodexServiceTier("string"==typeof e?e:e?.id)).filter(Boolean))],O=Object.freeze([Object.freeze({value:"gpt-6-astra",displayName:"GPT-6 Astra",description:"OpenAI’s most intelligent model for complex agentic work. Availability depends on your account.",reasoningEfforts:Object.freeze([...M]),serviceTiers:Object.freeze([])})]),N=e=>{const t=Array.isArray(e)?e:[],r=new Set(t.map(e=>e?.value).filter(Boolean));return[...O.filter(e=>!r.has(e.value)),...t].map(e=>({...e,reasoningEfforts:[...e.reasoningEfforts||[]],serviceTiers:[...e.serviceTiers||[]]}))};export function codexModelSupportsFastMode({models:e=[],model:t}={}){const r=(Array.isArray(e)?e:[]).find(e=>e?.value===t);return F(r?.serviceTiers).includes("fast")}const A=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")),n=(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:T(e.supported_reasoning_levels),serviceTiers:F(e.service_tiers||e.additional_speed_tiers)}));return N(n)}catch{return N([])}}const R=new Map;function P(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=R.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 R.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=P(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=P(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}),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=[...w(),...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 D=/\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:D.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 j(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:w="high",resume:T,env:M,sandbox:I,mode:F="default",onEvent:O,providerConfig:N,terminalRolePrompt:R,rolePrompt:P,roomContext:D,mcpServers:U,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[F]?F:"default",K=codexConfigForMode(J);I=normalizeCodexSandbox(I||K.sandbox);const V=codexPublishAccess({cwd:e,env:{...process.env,...M||{}}}),Y=V.env,Q=T||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(D);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,be=!1,we=!0,Se=0,_e=0,Ce=null,ke=d||null,Te=normalizeCodexServiceTier(h);"fast"!==Te||codexModelSupportsFastMode({models:p,model:ke})||(Te="default");let Me=k(w,{models:p,model:ke}),Ie=ee?readCodexThreadUsage(ee):null,Ee=null;const Fe=q({env:Y});let Oe=!Fe.enabled,Ne=null,Ae=!1;const Re=new WeakSet;let Pe=null,De=null,je=!1;const Ue=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,be=!1,we=!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(Ue.add(t);Ue.size>64;)Ue.delete(Ue.values().next().value)},tt=(e={})=>{const t=String(e.turnId||"");return!(!t||ie||ae||Ue.has(t)||t!==String(Pe||""))};async function rt(){const e=U?.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||je)&&Be();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ie=e,je){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&&E(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:j(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(Oe)return!1;if(!1===Ne?.alive)return Ae=!1,Oe=!0,!1;if(Ne&&Ae)return!0;let t;try{t=await rt()}catch(e){Ze(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ne){const r=z({cwd:e,env:Y,args:a({providerConfig:N,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(J,I,V.writableDirs),serviceTier:Te}),onNotification:nt,onServerRequest:ot,codexHost:{...W,log:Ze},onClose:e=>{ie||Oe||Re.has(r)||(Ae=!1,Oe=!0,Ze(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ne=r,await Ne.start(),Be()}return ee=await Ne.startThread({threadId:te>0?ee:null,cwd:e,model:ke,serviceTier:A(Te),sandbox:I,approvalPolicy:K.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Be(),t?.url&&(await Ne.waitForMcpServer({threadId:ee,name:"thinkpool"}),Be()),Ae=!0,Ve({type:"thread.started",thread_id:ee}),!0}catch{Oe=!0;try{Ne?.end()}catch{}return Ne=null,Ae=!1,!1}}function at({force:e=!1}={}){const t=Ne,r=Pe;if(t&&e){et(r),Oe=!0,Ae=!1;try{t.end()}catch{}Ne===t&&(Ne=null)}else if(t&&r)et(r),Promise.resolve(t.interrupt({threadId:ee,turnId:r})).catch(()=>{Oe=!0,Ae=!1;try{t.end()}catch{}Ne===t&&(Ne=null)});else if(t){Oe=!0,Ae=!1;try{t.end()}catch{}Ne===t&&(Ne=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||be)return!0;if(!await st())return!1;if(ie||ae||be)return!0;Je.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{if(we=!1,Pe=await Ne.startTurn({threadId:ee,input:e,images:Array.isArray(t.images)?t.images:[],model:ke,serviceTier:A(Te),effort:_(Me),approvalPolicy:K.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(ke,Me)}),Be(),ae||ie){et(Pe);try{await Ne.interrupt({threadId:ee,turnId:Pe})}catch{}return!0}const r=await Ne.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||be)||("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){Oe=!0,Ae=!1,et(Pe);try{Ne?.end()}catch{}Ne=null,ie||ve||be||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||be)return!0;if(!await st())return!1;if(ie||ae||be)return!0;Je.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{we=!1;const t=await Ne.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 Ne.interrupt({threadId:ee,turnId:Pe})}catch{}return!0}const r=await Ne.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||be)||("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){Oe=!0,Ae=!1,et(Pe);try{Ne?.end()}catch{}Ne=null,ie||ve||be||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||be)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:Te,effort:Me,sandbox:I,approvalPolicy:K.approvalPolicy,providerConfig:N,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(J,I,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,we=!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&&E(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&&!be)return{preTurnInitFailure:!0,message:t.message};(!ve&&!be||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:R,rolePrompt:P,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&&!be;){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||be)){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||be||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(Me)?fe:me,stalledSent:ge,stallRetried:ye});if("none"===t)return;if("retry"===t&&!we)return be=!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=b(t,e);if(r&&He(r),"status"!==t){if("retry"===t)return ye=!0,ve=!0,xe=ue(),ge=!1,void at();be=!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||je},get canSteer(){return!(!Ne||!1===Ne.alive||!Ae||Oe)},get started(){return le||je||!!se||!(!Ne||!1===Ne.alive)},sendTurn(t,r={}){if(ie)return!1;if(!le&&0===Xe.length&&!Ae&&!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&&Ne&&Pe){Be();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:R,rolePrompt:P,roomContext:()=>oe({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Pe;return ze=ze.then(()=>Ne.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||je||(ae=!1,Qe({recoveryAttempt:0})),1!==Xe.length||je||ct(),!0},async park(){if(ie||le||je||Xe.length)return!1;const e=Ge;try{await e}catch{}try{await ze}catch{}if(ie||le||je||Xe.length||se||Pe)return!1;const t=Ne;if(t){Re.add(t),Ne=null,Ae=!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,be=!1,Xe.length=0,et(Pe);let r=Promise.resolve();if(Ne&&Pe&&(r=Ne.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{Ne?.end()}catch{}Ee?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||le)return!1;ke=String(e),Je.setModel(ke);const t=k(Me,{models:p,model:ke});return t!==Me&&(Me=t,We({kind:"effort",level:Me})),"fast"!==Te||codexModelSupportsFastMode({models:p,model:ke})||(Te="default"),!0},setServiceTier(e){if(le||je||Xe.length)return!1;const t=normalizeCodexServiceTier(e);if(!t)return!1;if("fast"===t&&!codexModelSupportsFastMode({models:p,model:ke}))return!1;if(t===Te)return!0;Te=t;const r=Ne;if(r){Re.add(r),Ne=null,Ae=!1,$e.clear(),qe.clear();try{r.end()}catch{}}return!0},setMode:e=>!(le||!CODEX_MODE_CONFIG[e]||(J=e,K=codexConfigForMode(J),I=K.sandbox,ee=null,te=0,Ie=null,Ae=!1,0)),setEffort:e=>!(le||!C({models:p,model:ke}).includes(e)||(Me=e,We({kind:"effort",level:Me}),0)),get serviceTier(){return Te},get supportsFastMode(){return codexModelSupportsFastMode({models:p,model:ke})},clearContext:()=>!le&&(ee=null,te=0,Ie=null,Ae=!1,!0),async compactContext(){if(le||je)return!1;ae=!1,je=!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 Ne.compact({threadId:ee}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ne.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,je=!1,Xe.length&&ct()}},async accountUsage(){if(!await st())return null;const[e,t]=await Promise.all([Ne.accountUsage().catch(()=>null),Ne.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.431",
3
+ "version": "0.7.433",
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,13 +1,13 @@
1
1
  {
2
2
  "format": 1,
3
3
  "package": "thinkpool-pair",
4
- "version": "0.7.431",
4
+ "version": "0.7.433",
5
5
  "kind": "minified",
6
6
  "minifiedModules": 143,
7
- "sourceModuleBytes": 1911023,
8
- "artifactModuleBytes": 877264,
7
+ "sourceModuleBytes": 1918769,
8
+ "artifactModuleBytes": 881559,
9
9
  "files": {
10
- "package.json": "28dbe5240cf7f117c034f79a6574c9ffbc3a420eb400418a9cd6848327628ee2",
10
+ "package.json": "e5f467009af4c0250ccb4d42cb65124368db7f0d36c2fd086a8897f1a927cecd",
11
11
  "LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
12
12
  "bridge.mjs": "3ad4b84f77c864f48be1d297fd34e583271f13262a7c18493f3472388d2436c5",
13
13
  "bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
@@ -29,7 +29,7 @@
29
29
  "code-automations.mjs": "03c70058bc6d5a0b245f3fb8f5e2cb629c5ef7c1c6cd7d7fa018c40523b91036",
30
30
  "terminal-name.mjs": "f39bb8d1e148ab9a74cbb96438d31e524d6597b8035f00d7a906c28c306c0cf8",
31
31
  "claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
32
- "codex-session.mjs": "78458dbcf0f4e4cf2b5c88dac86030f40e95d28248b0a1a9bbc5208367d2fc5c",
32
+ "codex-session.mjs": "7aab399e11155266d72164e905b2cd4c0ceb850fb1e4c17bb9f3d9483d4505db",
33
33
  "codex-host-heal.mjs": "14ea191ed2072203be2edd40a41272f176aafc044753919f65b1071ce48f060f",
34
34
  "question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
35
35
  "thinkpool-room-prompt.mjs": "b8cc82ccf4262f6431c2047bbff31ac29c47a0d6c68a64640a2c24d94df4a746",
@@ -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",
package/service.mjs CHANGED
@@ -1 +1 @@
1
- import e from"node:os";import t from"node:fs";import r from"node:path";import{execSync as n}from"node:child_process";import{hostMemoryAdmission as i}from"./host-memory.mjs";import{readSupervisorReady as o,supervisorReadyMatches as s}from"./supervisor-ready.mjs";import{pairCli as a}from"./command-guidance.mjs";import{loadKeepAwakePreference as l}from"./keep-awake.mjs";import{scheduledRunsEnabled as d}from"./scheduled-runs.mjs";const c=/^[A-Za-z0-9]{2,16}$/,p=e=>{if(e&&!c.test(e))throw new Error(`invalid room code: ${JSON.stringify(e)} (expected 2–16 alphanumerics)`);return e?`io.thinkpool.pair.${e.toLowerCase()}`:"io.thinkpool.pair.account"},u=e=>e||"account",f=e=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),$=e=>`'${String(e).replace(/'/g,"'\\''")}'`;export function sanitizeServicePath(e,t=process.platform){const r="win32"===t?";":":";return String(e||"").split(r).filter(e=>e&&!e.includes("/.npm/_npx/")).join(r)}export function darwinServiceRunning(e){const t=String(e||"");return/(^|\n)[ \t]*state = running[ \t]*($|\n)/.test(t)&&/(^|\n)[ \t]*pid = \d+[ \t]*($|\n)/.test(t)}const h=e=>`rm -rf ${$(r.join(e,"_npx"))}/* 2>/dev/null`,m=(()=>{try{return JSON.parse(t.readFileSync(new URL("./package.json",import.meta.url),"utf8")).version}catch{return"latest"}})();function g(e){try{const n=JSON.parse(t.readFileSync(r.join(r.dirname(e),"package.json"),"utf8"));return"string"==typeof n?.version?n.version:null}catch{return null}}export function provisionRuntime(o,{exec:s=n,root:a=r.join(e.homedir(),".thinkpool-pair","runtimes"),admit:l=i}={}){if(!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(o)))throw new Error(`invalid runtime version: ${o}`);const d=r.join(a,String(o)),c=r.join(d,"node_modules","thinkpool-pair","bridge.mjs");if(t.existsSync(c)&&g(c)===String(o))return c;const p=l("stage a bridge runtime update");if(!p?.ok)throw new Error(p?.reason||"host memory is critically low");const u=`${d}.install-${process.pid}-${Date.now()}`;t.rmSync(u,{recursive:!0,force:!0}),t.mkdirSync(u,{recursive:!0});const f=`https://registry.npmjs.org/thinkpool-pair/-/thinkpool-pair-${o}.tgz`,h=r.join(u,"node_modules","thinkpool-pair","bridge.mjs");try{s(`${$(function(e=process.platform){const n=r.dirname(process.execPath),i=r.join(n,"win32"===e?"npm.cmd":"npm");try{return t.accessSync(i),i}catch{return"win32"===e?"npm.cmd":"npm"}}())} install --prefix ${$(u)} ${$(f)} --prefer-online --no-audit --no-fund`,{stdio:"inherit",shell:"/bin/bash"});const e=g(h);if(!t.existsSync(h)||e!==String(o))throw new Error(`runtime verification failed: requested ${o}, installed ${e||"no valid package"}`);t.rmSync(d,{recursive:!0,force:!0}),t.renameSync(u,d)}catch(e){throw t.rmSync(u,{recursive:!0,force:!0}),e}return c}export function buildArtifact(n,{room:i=null,cmdArgs:o=[],autoUpdate:s=!1,version:a=m,staleProof:l=!1,runtimeEntry:c=null,scheduledRuns:g=d()}={}){const v=function(e){const n=r.dirname(process.execPath),i=r.join(n,"win32"===e?"npx.cmd":"npx");try{return t.accessSync(i),i}catch{return"win32"===e?"npx.cmd":"npx"}}(n),y=i?process.cwd():e.homedir(),k=u(i),b=r.join(e.homedir(),".thinkpool-pair"),w=r.join(b,`${k}.log`),S=sanitizeServicePath(process.env.PATH||"",n),_=r.join(e.homedir(),".thinkpool-pair","npm-cache"),x=o.length?["--",...o]:[],A=i?`bridge (${i})`:"account bridge",E=s?"thinkpool-pair@latest":`thinkpool-pair@${a}`,P=s||l?["--prefer-online"]:[],j=i?["-y",...P,E,i]:["-y",...P,E],D=!0===g?{darwin:"<key>TP_SCHEDULED_RUNS_ENABLED</key><string>1</string>",linux:"Environment=TP_SCHEDULED_RUNS_ENABLED=1\n",win32:'set "TP_SCHEDULED_RUNS_ENABLED=1"\r\n'}:{darwin:"",linux:"",win32:""};if("darwin"===n){const t=c?[process.execPath,c,...i?[i]:[],...x]:l?["/bin/bash","-lc",`${h(_)}; export NPM_CONFIG_CACHE=${$(_)}; exec ${[v,...j,...x].map($).join(" ")}`]:[v,...j,...x];return{file:r.join(e.homedir(),"Library","LaunchAgents",`${p(i)}.plist`),content:`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n <key>Label</key><string>${p(i)}</string>\n <key>ProgramArguments</key><array>${t.map(e=>`<string>${f(e)}</string>`).join("")}</array>\n <key>RunAtLoad</key><true/>\n \x3c!-- KeepAlive only on UNSUCCESSFUL exit. A bridge that exits 0 has intentionally\n stood down — told to stop, or another bridge already owns this machine's\n singleton lock — and must NOT be relaunched, or launchd thrashes it every\n few seconds forever (the 2026-06-28 account-bridge restart loop). A crash\n or an update-restart exits non-zero and IS relaunched. Mirrors the bridge's\n own exit-code contract (stop()=0, restart-requested=non-zero). --\x3e\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>WorkingDirectory</key><string>${f(y)}</string>\n <key>StandardOutPath</key><string>${f(w)}</string>\n <key>StandardErrorPath</key><string>${f(w)}</string>\n <key>EnvironmentVariables</key><dict><key>PATH</key><string>${f(S)}</string>${c?"":`<key>NPM_CONFIG_CACHE</key><string>${f(_)}</string>`}${s?"<key>THINKPOOL_PAIR_AUTOUPDATE</key><string>1</string>":""}${D.darwin}</dict>\n</dict></plist>\n`,logDir:b,post:[],expectedRuntime:c||E,note:"Loaded as a LaunchAgent — starts at login, restarts on crash."}}if("linux"===n){const t=c?[process.execPath,c,...i?[i]:[],...x]:[v,...j,...x];return{file:r.join(e.homedir(),".config","systemd","user",`${p(i)}.service`),content:`[Unit]\nDescription=thinkpool Code ${A}\nAfter=network-online.target\n\n[Service]\nExecStart=${c?t.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" "):l?`/bin/bash -lc "${h(_)}; export NPM_CONFIG_CACHE=${$(_)}; exec ${[v,...j,...x].join(" ")}"`:t.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" ")}\nRestart=always\nRestartSec=2\n# Don't relaunch a clean stand-down. exit 0 = the bridge intentionally stopped —\n# told to stop, or another bridge already holds this machine's singleton lock —\n# so restarting it just thrashes every RestartSec forever (the 2026-06-28\n# account-bridge restart loop). Crashes + update-restarts exit non-zero and are\n# still restarted. Matches the bridge's exit-code contract (stop()=0, restart=non-zero).\nRestartPreventExitStatus=0\nWorkingDirectory=${y}\nEnvironment=PATH=${S}\n${c?"":`Environment=NPM_CONFIG_CACHE=${_}\n`}${s?"Environment=THINKPOOL_PAIR_AUTOUPDATE=1\n":""}${D.linux}StandardOutput=append:${w}\nStandardError=append:${w}\n\n[Install]\nWantedBy=default.target\n`,logDir:b,post:["systemctl --user daemon-reload",`systemctl --user enable ${p(i)}.service`,`systemctl --user restart ${p(i)}.service`],expectedRuntime:c||E,note:"Enabled as a systemd --user service. Run `loginctl enable-linger $USER` once to keep it running after logout."}}if("win32"===n){const t=r.join(process.env.APPDATA||r.join(e.homedir(),"AppData","Roaming"),"Microsoft","Windows","Start Menu","Programs","Startup"),n=r.join(t,`thinkpool-pair-${k}.cmd`),o=c?[process.execPath,c,...i?[i,"--supervise"]:[],...x].map(e=>`"${e}"`).join(" "):i?["npx","-y",...P,E,i,"--supervise",...x].join(" "):["npx","-y",...P,E].join(" ");return{file:n,content:`@echo off\r\ntitle thinkpool-pair ${k}\r\n${c?"":`set "NPM_CONFIG_CACHE=${_}"\r\n`}${D.win32}${o}\r\n`,logDir:b,post:[],note:"Installed to the Startup folder — runs at login"+(i?" with --supervise (auto-restart on crash).":" (account mode).")+" Start it now without rebooting by double-clicking the .cmd, or run it from a terminal."}}throw new Error(`unsupported platform: ${n}`)}export function artifactScheduledRunsEnabled(e,t){const r=String(t||"");return"darwin"===e?/<key>\s*TP_SCHEDULED_RUNS_ENABLED\s*<\/key>\s*<string>\s*1\s*<\/string>/.test(r):"linux"===e?/^Environment=TP_SCHEDULED_RUNS_ENABLED=1\r?$/m.test(r):"win32"===e&&/^set "TP_SCHEDULED_RUNS_ENABLED=1"\r?$/m.test(r)}export function installedScheduledRunsEnabled(e,{platform:r=process.platform,readFile:n=e=>t.readFileSync(e,"utf8")}={}){try{return artifactScheduledRunsEnabled(r,n(buildArtifact(r,{room:e,scheduledRuns:!1}).file))}catch{return!1}}export function parseRunningPairVersions(e){const t=String(e).match(/(?:thinkpool-pair@|runtimes[\\/])(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/g)||[];return new Set(t.map(e=>e.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0]).filter(Boolean))}export function serviceRuntimeSnapshot(e,{platform:t=process.platform,exec:r=n}={}){const i=e=>{const t=parseRunningPairVersions(e);return 1===t.size?[...t][0]:null},o=p(e);try{if("darwin"===t){const e=r(`launchctl print gui/$(id -u)/${o}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],shell:"/bin/bash"}),t=String(e).match(/(^|\n)[ \t]*pid = (\d+)[ \t]*($|\n)/)?.[2];return darwinServiceRunning(e)?{version:i(e),pid:t||null}:null}if("linux"===t){const e=r(`systemctl --user show ${o}.service --property=ActiveState --property=MainPID --property=ExecStart`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]}),t=String(e).match(/^MainPID=([1-9]\d*)$/m)?.[1];return/^ActiveState=active$/m.test(String(e))&&t?{version:i(e),pid:t}:null}}catch{}return null}export function serviceRuntimeVersion(e,t){return serviceRuntimeSnapshot(e,t)?.version||null}export function buildDarwinReloadHandoff({targetLabel:t,targetFile:n,stagedFile:i,expectedRuntime:o,version:s,nonce:a=`${process.pid}-${Date.now()}`}){if(!/^io\.thinkpool\.pair\.[A-Za-z0-9_.-]+$/.test(String(t)))throw new Error(`invalid service label: ${t}`);if(!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(s)))throw new Error(`invalid runtime version: ${s}`);const l=`io.thinkpool.pair.update.${String(a).replace(/[^A-Za-z0-9-]/g,"")||String(process.pid)}`,d=r.join(e.homedir(),"Library","LaunchAgents",`${l}.plist`),c=`${i}.previous`,u=r.join(e.homedir(),".thinkpool-pair","update-status.json"),h=r.join(e.homedir(),".thinkpool-pair","update.log"),m=r.join(e.homedir(),".thinkpool-pair","account-ready.json"),g=t===p(null),v=["set -u",'dom="gui/$(id -u)"',`target=${$(t)}`,`helper=${$(l)}`,`target_file=${$(n)}`,`staged_file=${$(i)}`,`backup_file=${$(c)}`,`helper_file=${$(d)}`,`expected=${$(o)}`,`version=${$(s)}`,`status=${$(u)}`,`ready=${$(m)}`,"requires_ready="+(g?"1":"0"),'tmp="$status.tmp.$$"',"ok=0",'live_pid=""',"stable=0","had_previous=0",'if [ -f "$target_file" ]; then cp "$target_file" "$backup_file" && had_previous=1; fi','launchctl bootout "$dom/$target" 2>/dev/null || true','for i in $(seq 1 40); do launchctl print "$dom/$target" >/dev/null 2>&1 || break; sleep 0.2; done','if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi','cp "$staged_file" "$target_file"','launchctl enable "$dom/$target" 2>/dev/null || true','for i in $(seq 1 30); do launchctl bootstrap "$dom" "$target_file" 2>/dev/null && break; sleep 0.3; done','printf "update_handoff_start target=%s version=%s timeout_s=90\\n" "$target" "$version"','for i in $(seq 1 180); do snapshot="$(launchctl print "$dom/$target" 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^[[:space:]]*pid = \\([0-9][0-9]*\\)[[:space:]]*$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && grep -F "\\"version\\":\\"$version\\"" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F -- "$expected" >/dev/null && printf "%s\\n" "$snapshot" | grep -F "state = running" >/dev/null; then if [ "$pid" = "$live_pid" ]; then stable=$((stable+1)); else live_pid="$pid"; stable=1; fi; if [ "$stable" -ge 5 ]; then ok=1; break; fi; else live_pid=""; stable=0; fi; sleep 0.5; done','if [ "$ok" != 1 ]; then ready_state=missing; [ -f "$ready" ] && ready_state=present; printf "update_handoff_verify_timeout target=%s version=%s last_pid=%s ready=%s\\n" "$target" "$version" "$live_pid" "$ready_state"; fi',`if [ "$ok" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!0,version:String(s),target:String(t)})}' > "$tmp"; else rollback=0; if [ "$had_previous" = 1 ]; then launchctl bootout "$dom/$target" 2>/dev/null || true; for i in $(seq 1 40); do launchctl print "$dom/$target" >/dev/null 2>&1 || break; sleep 0.2; done; if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi; cp "$backup_file" "$target_file"; launchctl enable "$dom/$target" 2>/dev/null || true; bootstrapped=0; for i in $(seq 1 30); do if launchctl bootstrap "$dom" "$target_file" 2>/dev/null; then bootstrapped=1; break; fi; sleep 0.3; done; if [ "$bootstrapped" = 1 ]; then rollback_pid=""; rollback_stable=0; for i in $(seq 1 180); do snapshot="$(launchctl print "$dom/$target" 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^[[:space:]]*pid = \\([0-9][0-9]*\\)[[:space:]]*$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "state = running" >/dev/null; then if [ "$pid" = "$rollback_pid" ]; then rollback_stable=$((rollback_stable+1)); else rollback_pid="$pid"; rollback_stable=1; fi; if [ "$rollback_stable" -ge 5 ]; then rollback=1; break; fi; else rollback_pid=""; rollback_stable=0; fi; sleep 0.5; done; fi; else launchctl bootout "$dom/$target" 2>/dev/null || true; rm -f "$target_file"; fi; if [ "$rollback" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!1,version:String(s),target:String(t),error:"new runtime not confirmed within 90s; previous service restored and ready",rolledBack:!0})}' > "$tmp"; else printf '%s\\n' '${JSON.stringify({ok:!1,version:String(s),target:String(t),error:"new runtime not confirmed within 90s and ready rollback failed",rolledBack:!1})}' > "$tmp"; fi; fi`,'printf "update_handoff_complete target=%s version=%s ok=%s\\n" "$target" "$version" "$ok"','mv "$tmp" "$status"','rm -f "$staged_file" "$backup_file" "$helper_file"','launchctl bootout "$dom/$helper" >/dev/null 2>&1 || true','exit "$((1-ok))"'].join("; ");return{helperLabel:l,helperFile:d,backupFile:c,statusFile:u,logFile:h,script:v,content:`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n <key>Label</key><string>${f(l)}</string>\n <key>ProgramArguments</key><array><string>/bin/bash</string><string>-lc</string><string>${f(v)}</string></array>\n <key>RunAtLoad</key><true/>\n <key>StandardOutPath</key><string>${f(h)}</string>\n <key>StandardErrorPath</key><string>${f(h)}</string>\n</dict></plist>\n`}}export function buildLinuxReloadHandoff({targetLabel:t,targetFile:n,stagedFile:i,expectedRuntime:o,version:s,nonce:a=`${process.pid}-${Date.now()}`}){if(!/^io\.thinkpool\.pair\.[A-Za-z0-9_.-]+$/.test(String(t)))throw new Error(`invalid service label: ${t}`);if(!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(s)))throw new Error(`invalid runtime version: ${s}`);const l=`io.thinkpool.pair.update.${String(a).replace(/[^A-Za-z0-9-]/g,"")||String(process.pid)}`,d=`${i}.previous`,c=r.join(e.homedir(),".thinkpool-pair","update-status.json"),u=r.join(e.homedir(),".thinkpool-pair","update.log"),f=r.join(e.homedir(),".thinkpool-pair","account-ready.json"),h=t===p(null);return{helperLabel:l,backupFile:d,statusFile:c,logFile:u,script:["set -u",`target=${$(t)}`,'unit="$target.service"',`target_file=${$(n)}`,`staged_file=${$(i)}`,`backup_file=${$(d)}`,`expected=${$(o)}`,`version=${$(s)}`,`status=${$(c)}`,`ready=${$(f)}`,"requires_ready="+(h?"1":"0"),'tmp="$status.tmp.$$"',"ok=0","had_previous=0",'if [ -f "$target_file" ]; then cp "$target_file" "$backup_file" && had_previous=1; fi','systemctl --user stop "$unit" 2>/dev/null || true','if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi','cp "$staged_file" "$target_file"',"systemctl --user daemon-reload",'systemctl --user enable "$unit" >/dev/null 2>&1 || true','systemctl --user restart "$unit" >/dev/null 2>&1 || true','live_pid=""',"stable=0",'printf "update_handoff_start target=%s version=%s timeout_s=90\\n" "$target" "$version"','for i in $(seq 1 180); do snapshot="$(systemctl --user show "$unit" --property=ActiveState --property=MainPID --property=ExecStart 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^MainPID=\\([1-9][0-9]*\\)$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && grep -F "\\"version\\":\\"$version\\"" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "ActiveState=active" >/dev/null && printf "%s\\n" "$snapshot" | grep -F -- "$expected" >/dev/null; then if [ "$pid" = "$live_pid" ]; then stable=$((stable+1)); else live_pid="$pid"; stable=1; fi; if [ "$stable" -ge 5 ]; then ok=1; break; fi; else live_pid=""; stable=0; fi; sleep 0.5; done','if [ "$ok" != 1 ]; then ready_state=missing; [ -f "$ready" ] && ready_state=present; printf "update_handoff_verify_timeout target=%s version=%s last_pid=%s ready=%s\\n" "$target" "$version" "$live_pid" "$ready_state"; fi',`if [ "$ok" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!0,version:String(s),target:String(t)})}' > "$tmp"; else rollback=0; if [ "$had_previous" = 1 ]; then systemctl --user stop "$unit" 2>/dev/null || true; if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi; cp "$backup_file" "$target_file"; systemctl --user daemon-reload; systemctl --user enable "$unit" >/dev/null 2>&1 || true; systemctl --user restart "$unit" >/dev/null 2>&1 || true; rollback_pid=""; rollback_stable=0; for i in $(seq 1 180); do snapshot="$(systemctl --user show "$unit" --property=ActiveState --property=MainPID 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^MainPID=\\([1-9][0-9]*\\)$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "ActiveState=active" >/dev/null; then if [ "$pid" = "$rollback_pid" ]; then rollback_stable=$((rollback_stable+1)); else rollback_pid="$pid"; rollback_stable=1; fi; if [ "$rollback_stable" -ge 5 ]; then rollback=1; break; fi; else rollback_pid=""; rollback_stable=0; fi; sleep 0.5; done; else systemctl --user stop "$unit" 2>/dev/null || true; rm -f "$target_file"; systemctl --user daemon-reload; fi; if [ "$rollback" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!1,version:String(s),target:String(t),error:"new runtime not confirmed within 90s; previous service restored and ready",rolledBack:!0})}' > "$tmp"; else printf '%s\\n' '${JSON.stringify({ok:!1,version:String(s),target:String(t),error:"new runtime not confirmed within 90s and ready rollback failed",rolledBack:!1})}' > "$tmp"; fi; fi`,'printf "update_handoff_complete target=%s version=%s ok=%s\\n" "$target" "$version" "$ok"','mv "$tmp" "$status"','rm -f "$staged_file" "$backup_file"','exit "$((1-ok))"'].join("; ")}}const v=()=>r.join(e.homedir(),".thinkpool-pair","account.lock"),y=e=>{try{return process.kill(e,0),!0}catch(e){return"EPERM"===e?.code}};export async function retireUnmanagedBridgeLock({lockFile:e=v(),readLock:r=()=>t.readFileSync(e,"utf8"),removeLock:i=()=>t.rmSync(e,{force:!0}),commandForPid:o=e=>String(n(`ps -p ${e} -o command=`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]})),alive:s=y,signal:a=(e,t)=>process.kill(e,t),sleep:l=e=>new Promise(t=>setTimeout(t,e)),waitMs:d=2500,pollMs:c=100,stderr:p=process.stderr}={}){let u=0;try{u=parseInt(String(r()).trim(),10)||0}catch{return!0}const f=()=>{if(s(u))return!1;try{(parseInt(String(r()).trim(),10)||0)===u&&i()}catch{}return!0};if(!u||u===process.pid||f())return!0;let $="";try{$=o(u)}catch{return f()}if(!/(?:node_modules[\\/]thinkpool-pair|\.npm[\\/]_npx|\.thinkpool-pair[\\/]runtimes)[\\/][^\n]*bridge\.mjs(?:\s|$)/.test($))return p.write(` ⚠ account.lock belongs to PID ${u}, but it is not a verified thinkpool-pair bridge; refusing to signal an unrelated process.\n`),!1;p.write(` ◆ stopping previous thinkpool-pair bridge (PID ${u}) before installing the background service…\n`);try{a(u,"SIGTERM")}catch{if(f())return!0}const h=Math.max(1,Math.ceil(Math.max(0,Number(d)||0)/Math.max(1,Number(c)||1)));for(let e=0;e<h;e++){if(f())return!0;await l(Math.max(1,Number(c)||1))}try{a(u,"SIGKILL")}catch{}for(let e=0;e<5;e++){if(f())return!0;await l(Math.max(1,Number(c)||1))}return p.write(` ⚠ previous thinkpool-pair bridge PID ${u} did not stop; background install aborted to avoid two competing bridges.\n`),!1}function k(e){const t=r.basename(e).match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_.-]+)\.(?:plist|service)$/);if(!t)throw new Error(`cannot derive service label from ${e}`);return t[1]}export function existingServiceLabels(i=process.platform,{fsMod:o=t}={}){const s=new Set;try{if("darwin"===i){try{const e=n("launchctl print gui/$(id -u) 2>/dev/null | grep -oE 'io\\.thinkpool\\.pair\\.[A-Za-z0-9_]+' | sort -u",{encoding:"utf8",shell:"/bin/bash"});for(const t of e.split("\n")){const e=t.trim();e&&s.add(e)}}catch{}try{const t=r.join(e.homedir(),"Library","LaunchAgents");for(const e of o.readdirSync(t)){const t=e.match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_]+)\.plist$/);t&&s.add(t[1])}}catch{}}else if("linux"===i){try{const e=n("systemctl --user list-unit-files 'io.thinkpool.pair.*' --no-legend 2>/dev/null | awk '{print $1}'",{encoding:"utf8",shell:"/bin/bash"});for(const t of e.split("\n")){const e=t.trim().replace(/\.service$/,"");e&&s.add(e)}}catch{}try{const t=r.join(e.homedir(),".config","systemd","user");for(const e of o.readdirSync(t)){const t=e.match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_]+)\.service$/);t&&s.add(t[1])}}catch{}}}catch{}return[...s]}export function sweepExistingServices(i,o=process.platform){for(const s of existingServiceLabels(o))if(s!==i)try{if("darwin"===o){n(`launchctl bootout gui/$(id -u)/${s} 2>/dev/null || true`,{stdio:"ignore",shell:"/bin/bash"});try{t.rmSync(r.join(e.homedir(),"Library","LaunchAgents",`${s}.plist`),{force:!0})}catch{}}else"linux"===o&&n(`systemctl --user disable --now ${s}.service 2>/dev/null || true`,{stdio:"ignore",shell:"/bin/bash"});process.stderr.write(` ◆ swept stale service ${s}\n`)}catch{}if("win32"!==o)try{n("pkill -9 -f 'thinkpool-pair/bridge.mjs [A-Z0-9]' 2>/dev/null || true",{stdio:"ignore",shell:"/bin/bash"})}catch{}}export function installService(e,i=[],{autoUpdate:o=!1,version:s=m,staleProof:c=!0}={}){let f=null;if(!o)try{f=provisionRuntime(s)}catch(e){return process.stderr.write(` ⚠ runtime install failed; service left unchanged: ${e.message}\n`),!1}const h=d()||installedScheduledRunsEnabled(e),g=buildArtifact(process.platform,{room:e,cmdArgs:i,autoUpdate:o,version:s,staleProof:c,runtimeEntry:f,scheduledRuns:h});try{sweepExistingServices(p(e),process.platform)}catch{}if(t.mkdirSync(g.logDir,{recursive:!0}),t.mkdirSync(r.dirname(g.file),{recursive:!0}),"darwin"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${g.file}\n`),!function(e,i){const o=r.join(e.logDir,`service-update-${process.pid}-${Date.now()}.plist`),s=buildDarwinReloadHandoff({targetLabel:k(e.file),targetFile:e.file,stagedFile:o,expectedRuntime:e.expectedRuntime,version:i});t.mkdirSync(r.dirname(s.helperFile),{recursive:!0}),t.mkdirSync(r.dirname(s.statusFile),{recursive:!0}),t.rmSync(s.statusFile,{force:!0}),t.writeFileSync(o,e.content),t.writeFileSync(s.helperFile,s.content);try{return n(`launchctl bootstrap gui/$(id -u) ${$(s.helperFile)}`,{stdio:"inherit",shell:"/bin/bash"}),process.stderr.write(` ◆ update handoff armed outside the bridge process tree — completion is recorded in ${s.statusFile}.\n`),!0}catch(e){return t.rmSync(o,{force:!0}),t.rmSync(s.helperFile,{force:!0}),process.stderr.write(` ⚠ could not arm the launchd update handoff; existing service left running: ${e.message}\n`),!1}}(g,s))return!1}else if("linux"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${g.file}\n`),!function(e,i){const o=r.join(e.logDir,`service-update-${process.pid}-${Date.now()}.service`),s=buildLinuxReloadHandoff({targetLabel:k(e.file),targetFile:e.file,stagedFile:o,expectedRuntime:e.expectedRuntime,version:i});t.mkdirSync(r.dirname(s.statusFile),{recursive:!0}),t.rmSync(s.statusFile,{force:!0}),t.writeFileSync(o,e.content);try{return n(`systemd-run --user --unit=${$(s.helperLabel)} --collect --property=StandardOutput=append:${$(s.logFile)} --property=StandardError=append:${$(s.logFile)} /bin/bash -lc ${$(s.script)}`,{stdio:"inherit",shell:"/bin/bash"}),process.stderr.write(` ◆ update handoff armed outside the bridge service cgroup — completion is recorded in ${s.statusFile}.\n`),!0}catch(e){return t.rmSync(o,{force:!0}),process.stderr.write(` ⚠ could not arm the systemd update handoff; existing service left running: ${e.message}\n`),!1}}(g,s))return!1}else{t.writeFileSync(g.file,g.content),process.stderr.write(`\n ◆ wrote ${g.file}\n`);for(const e of g.post)try{n(e,{stdio:"inherit"})}catch(t){return process.stderr.write(` ⚠ "${e}" failed; service update NOT confirmed: ${t.message}\n`),!1}}const v=Math.max(10,parseInt(process.env.THINKPOOL_PAIR_UPDATE_IDLE,10)||90),y=o?"win32"===process.platform?"auto-update ON: a new thinkpool-pair@latest applies on the next login/restart (Windows has no in-process self-update).":`auto-update ON: checks npm ~every 30 min and restarts to apply a new @latest once sessions are idle (≥${v}s quiet; they resume in place).`:`PINNED to ${s} (stable local runtime; restarts do not depend on npx or the network). To update: use "Restart & update the bridge" (or run ${a("install-service",e||void 0)}). Pass --auto-update to track @latest instead.`,b=e?` ${e}`:"",w=e?`room ${e}`:"your account (auto-serves every session)",S=l()?" ◆ Keep-awake on: system sleep is inhibited while the bridge runs (higher battery use); the display may still dim, lock, and turn off.\n":"";return process.stderr.write(` ◆ ${w}\n ◆ ${"darwin"===process.platform?"Launchd is completing and verifying the reload independently.":g.note}\n ◆ Prerequisite: Claude Code, Codex, or Hermes must be installed on this machine. BYOK supplies model credentials; it does not install an agent.\n${S} ◆ ${y}\n ◆ logs: ${r.join(g.logDir,`${u(e)}.log`)}\n ◆ remove with: ${a("uninstall-service")}${b}\n\n`),!0}export async function installAndConfirmService(i,a=[],{platform:l=process.platform,version:d=m,staleProof:c=!0,install:u=installService,exec:f=n,snapshot:$=serviceRuntimeSnapshot,prepareAccountHandoff:h=retireUnmanagedBridgeLock,readReady:g=()=>o(),readStatus:v=()=>{try{return JSON.parse(t.readFileSync(r.join(e.homedir(),".thinkpool-pair","update-status.json"),"utf8"))}catch{return null}},now:y=Date.now,sleep:k=e=>new Promise(t=>setTimeout(t,e)),timeoutMs:b=2e5,pollMs:w=250,stderr:S=process.stderr}={}){const _=$(i,{platform:l,exec:f});if(!i&&"win32"!==l&&!_&&!await h())return S.write(" ⚠ background install aborted: the previous bridge could not be handed off safely.\n"),!1;const x=y();if(!1===u(i,a,{version:d,staleProof:c}))return!1;const A=()=>{const e=$(i,{platform:l,exec:f}),t=!!i||"win32"===l||s(g(),e,d,{notBefore:x});return!(e?.version!==d||!t||(S.write(` ✓ bridge install confirmed — running v${d}.\n`),0))};if("win32"===l)return!0;const E=y()+Math.max(0,Number(b)||0),P=p(i);for(;;){const e=v();if(e&&"object"==typeof e&&e.target===P&&e.version===d){if(!1===e.ok)return S.write(` ⚠ install v${d} was not confirmed: ${e.error||"the launchd handoff failed"}.\n`),!1;if(!0===e.ok&&A())return!0}if(y()>=E)return S.write(` ⚠ install v${d} timed out waiting for supervisor readiness; no connected account bridge was proven.\n`),!1;await k(Math.max(1,Number(w)||1))}}export function restartService(e){const t=process.platform;if(!isServiceInstalled(e))return process.stderr.write(" ⚠ no background service installed to restart — install one first.\n"),!1;const r=p(e);try{if("darwin"===t)n(`launchctl kickstart -k gui/$(id -u)/${r}`,{stdio:"inherit"});else{if("linux"!==t)return process.stderr.write(" ⚠ Windows has no managed daemon — close the running thinkpool-pair window and re-launch the Startup .cmd (or reboot) to restart.\n"),!1;n(`systemctl --user restart ${r}.service`,{stdio:"inherit"})}return process.stderr.write(` ◆ restarted ${r} — any active session resumes in place.\n`),!0}catch(e){return process.stderr.write(` ⚠ restart failed: ${e.message}\n`),!1}}function b(e=n){let t="";try{t=String(e("npm view thinkpool-pair version",{encoding:"utf8",stdio:["ignore","pipe","ignore"]})).trim()}catch{}return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(t)?t:null}export function stageServiceUpdate(e,{exec:t=n,active:r=isServiceInstalled,provision:i=provisionRuntime}={}){if(!r(e))return process.stderr.write(" ⚠ no background service installed to update — install one first.\n"),null;const o=b(t);if(!o)return process.stderr.write(" ⚠ couldn't reach npm or resolve the published version — bridge left unchanged; NO update or restart was applied.\n"),null;process.stderr.write(`\n ◆ preparing bridge v${o} in the background; active sessions keep running…\n`);try{return i(o),o}catch(e){return process.stderr.write(` ⚠ couldn't stage bridge v${o}: ${e?.message||e}\n`),null}}export function applyStagedServiceUpdate(e,t,{install:r=installService,active:n=isServiceInstalled}={}){return!(!n(e)||!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(t||"")))&&(process.stderr.write(`\n ◆ updating the background service to v${t} and restarting…\n`),!1!==r(e,[],{version:t,staleProof:!0}))}export function updateService(e,{exec:t=n,install:r=installService,active:i=isServiceInstalled}={}){if(!i(e))return process.stderr.write(" ⚠ no background service installed to update — install one first.\n"),!1;const o=b(t);return o?(process.stderr.write(`\n ◆ updating the background service to v${o} and restarting…\n`),!1!==r(e,[],{version:o,staleProof:!0})):(process.stderr.write(" ⚠ couldn't reach npm or resolve the published version — bridge left unchanged; NO update or restart was applied.\n"),!1)}export async function updateAndConfirmService(i,{platform:a=process.platform,exec:l=n,update:d=updateService,install:c=installService,active:u=isServiceInstalled,snapshot:f=serviceRuntimeSnapshot,readReady:$=()=>o(),readStatus:h=()=>{try{return JSON.parse(t.readFileSync(r.join(e.homedir(),".thinkpool-pair","update-status.json"),"utf8"))}catch{return null}},now:m=Date.now,sleep:g=e=>new Promise(t=>setTimeout(t,e)),timeoutMs:v=2e5,pollMs:y=250,stderr:k=process.stderr}={}){const b=f(i,{platform:a,exec:l}),w=m();let S=null;if(!1===d(i,{exec:(...e)=>{const t=l(...e),r=String(t).trim();return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(r)&&(S=r),t},install:c,active:u})||!S)return k.write(" ⚠ update was not confirmed: staging the published runtime failed; the existing service was left unchanged.\n"),!1;const _=()=>{const e=f(i,{platform:a,exec:l}),t=!!b&&b.version!==S,r=!!b?.pid&&!!e?.pid&&b.pid!==e.pid,n=!!i||"win32"===a||s($(),e,S,{notBefore:w});return e?.version===S&&(t||r)&&n?(k.write(` ✓ bridge restart confirmed — running v${S}.\n`),{ok:!0,live:e}):{ok:!1,live:e}},x=({live:e}={})=>{e?.version&&e.version!==S?k.write(` ⚠ update v${S} was not confirmed: live service is still v${e.version}.\n`):e?.version===S?k.write(` ⚠ update v${S} was not confirmed: the service process did not restart.\n`):k.write(` ⚠ update v${S} was not confirmed: the service manager cannot prove the new runtime is running.\n`)};if("win32"===a)return!0;const A=m()+Math.max(0,Number(v)||0),E=p(i);let P=null;for(;;){const e=h();if(e&&"object"==typeof e&&e.target===E){if(!1===e.ok&&e.version===S)return k.write(` ⚠ update v${S} was not confirmed: ${e.error||"the launchd handoff failed"}.\n`),!1;if(!0===e.ok&&e.version===S&&(P=_(),P.ok))return!0}if(m()>=A)return P?x(P):k.write(` ⚠ update v${S} timed out waiting for launchd confirmation; the service may have rolled back.\n`),!1;await g(Math.max(1,Number(y)||1))}}export function serviceActive(e,{platform:t=process.platform,exec:r=n}={}){const i=t,o=p(e);try{return"darwin"===i?darwinServiceRunning(r(`launchctl print gui/$(id -u)/${o}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]})):"linux"===i&&(r(`systemctl --user is-active --quiet ${o}.service`),!0)}catch{return!1}}export function isServiceInstalled(e){if("win32"===process.platform)try{return t.existsSync(buildArtifact("win32",{room:e}).file)}catch{return!1}return serviceActive(e)}export function uninstallService(e){const r=process.platform;let i;try{i=buildArtifact(r,{room:e})}catch(e){return void process.stderr.write(` ⚠ ${e.message}\n`)}if("darwin"===r)try{n(`launchctl bootout gui/$(id -u)/${p(e)} 2>/dev/null`)}catch{}if("linux"===r)try{n(`systemctl --user disable --now ${p(e)}.service 2>/dev/null`)}catch{}try{t.rmSync(i.file,{force:!0}),process.stderr.write(` ◆ removed ${i.file}\n`)}catch(e){process.stderr.write(` ⚠ ${e.message}\n`)}}
1
+ import e from"node:os";import t from"node:fs";import r from"node:path";import{execSync as n}from"node:child_process";import{hostMemoryAdmission as i}from"./host-memory.mjs";import{readSupervisorReady as s,supervisorReadyMatches as o}from"./supervisor-ready.mjs";import{pairCli as a}from"./command-guidance.mjs";import{loadKeepAwakePreference as l}from"./keep-awake.mjs";import{scheduledRunsEnabled as c}from"./scheduled-runs.mjs";const d=/^[A-Za-z0-9]{2,16}$/,p=e=>{if(e&&!d.test(e))throw new Error(`invalid room code: ${JSON.stringify(e)} (expected 2–16 alphanumerics)`);return e?`io.thinkpool.pair.${e.toLowerCase()}`:"io.thinkpool.pair.account"},u=e=>e||"account",f=e=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),m=e=>`'${String(e).replace(/'/g,"'\\''")}'`;export function sanitizeServicePath(e,t=process.platform){const r="win32"===t?";":":";return String(e||"").split(r).filter(e=>e&&!e.includes("/.npm/_npx/")).join(r)}export function darwinServiceRunning(e){const t=String(e||"");return/(^|\n)[ \t]*state = running[ \t]*($|\n)/.test(t)&&/(^|\n)[ \t]*pid = \d+[ \t]*($|\n)/.test(t)}const h=e=>`rm -rf ${m(r.join(e,"_npx"))}/* 2>/dev/null`,$=(()=>{try{return JSON.parse(t.readFileSync(new URL("./package.json",import.meta.url),"utf8")).version}catch{return"latest"}})(),g=/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/,v=/^(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\.install-(\d+)-(\d+)$/,y=36e5;function b(e){try{const n=JSON.parse(t.readFileSync(r.join(r.dirname(e),"package.json"),"utf8"));return"string"==typeof n?.version?n.version:null}catch{return null}}function k(e,t=process.kill){if(!Number.isSafeInteger(e)||e<1)return!1;try{return t(e,0),!0}catch(e){return"ESRCH"!==e?.code}}export function runtimeDiskDecision(e,{env:t=process.env}={}){if("1"===t.TP_RUNTIME_DISK_GUARD_OFF)return{ok:!0,source:"disabled",availableBytes:e};const r=Number(t.TP_RUNTIME_DISK_MIN_MIB),n=1024*Math.max(512,Number.isFinite(r)?r:4096)*1024;return Number.isFinite(e)?{ok:e>=n,source:"statfs",availableBytes:e,minimumBytes:n}:{ok:!0,source:"unavailable",availableBytes:null,minimumBytes:n}}export function hostRuntimeDiskAdmission(e,r,{statfs:n=t.statfsSync,env:i=process.env}={}){try{const t=n(e),s=runtimeDiskDecision(Number(t.bavail)*Number(t.bsize),{env:i});if(s.ok)return s;const o=Math.max(0,s.availableBytes/1073741824).toFixed(1),a=(s.minimumBytes/1073741824).toFixed(1);return{...s,reason:`Host disk space is critically low (${o} GiB available; ${a} GiB required). thinkpool did not ${r}.`}}catch{return{ok:!0,source:"unavailable",availableBytes:null}}}export function runtimeRetentionPlan(e,{protectedVersions:n=[],retainRecent:i=2,now:s=Date.now(),signal:o=process.kill}={}){let a;try{a=t.readdirSync(e)}catch(e){if("ENOENT"===e?.code)return[];throw e}const l=new Set([...n].filter(e=>g.test(String(e)))),c=[],d=[];for(const n of a){const i=r.join(e,n);let a;try{a=t.lstatSync(i)}catch{continue}if(!a.isDirectory()||a.isSymbolicLink())continue;if(g.test(n)){b(r.join(i,"node_modules","thinkpool-pair","bridge.mjs"))===n?c.push({name:n,mtimeMs:a.mtimeMs}):s-a.mtimeMs>=y&&!l.has(n)&&d.push(n);continue}const p=n.match(v);!p||s-a.mtimeMs<y||k(Number(p[2]),o)||d.push(n)}c.sort((e,t)=>t.mtimeMs-e.mtimeMs||t.name.localeCompare(e.name));for(const e of c.slice(0,Math.max(0,Number(i)||0)))l.add(e.name);return[...c.filter(e=>!l.has(e.name)).map(e=>e.name),...d].sort()}export function pruneProvisionedRuntimes(e,n={}){const i=[];for(const s of runtimeRetentionPlan(e,n)){const n=r.join(e,s),o=t.lstatSync(n);o.isDirectory()&&!o.isSymbolicLink()&&r.dirname(t.realpathSync(n))===t.realpathSync(e)&&(t.rmSync(n,{recursive:!0,force:!1}),i.push(s))}return i}export function provisionRuntime(s,{exec:o=n,root:a=r.join(e.homedir(),".thinkpool-pair","runtimes"),admit:l=i,disk:c=hostRuntimeDiskAdmission,protectedVersions:d=[]}={}){if(!g.test(String(s)))throw new Error(`invalid runtime version: ${s}`);t.mkdirSync(a,{recursive:!0});const p=function(e,{now:n=Date.now,signal:i=process.kill}={}){const s=r.join(e,".provision.lock"),o=`${process.pid}-${n()}-${Math.random().toString(16).slice(2)}`,a=()=>{const e=t.openSync(s,"wx",384);try{t.writeFileSync(e,JSON.stringify({pid:process.pid,createdAt:n(),token:o}))}catch(r){t.closeSync(e);try{t.unlinkSync(s)}catch{}throw r}return e};let l;try{l=a()}catch(e){if("EEXIST"!==e?.code)throw e;let r=!1;try{const e=JSON.parse(t.readFileSync(s,"utf8")),o=Number.isFinite(Number(e.createdAt))?Number(e.createdAt):t.lstatSync(s).mtimeMs;r=n()-o>=y&&!k(Number(e.pid),i)}catch{try{r=n()-t.lstatSync(s).mtimeMs>=y}catch{r=!1}}if(!r)throw new Error("another bridge runtime update is already in progress");t.unlinkSync(s),l=a()}return()=>{try{t.closeSync(l)}catch{}try{JSON.parse(t.readFileSync(s,"utf8")).token===o&&t.unlinkSync(s)}catch{}}}(a);try{const e=pruneProvisionedRuntimes(a,{protectedVersions:[...d,$,String(s)]});e.length&&process.stderr.write(` ◆ removed ${e.length} obsolete bridge runtime${1===e.length?"":"s"}\n`);const n=r.join(a,String(s)),i=r.join(n,"node_modules","thinkpool-pair","bridge.mjs");if(t.existsSync(i)&&b(i)===String(s))return i;const p=c(a,"stage a bridge runtime update");if(!p?.ok)throw new Error(p?.reason||"host disk space is critically low");const u=l("stage a bridge runtime update");if(!u?.ok)throw new Error(u?.reason||"host memory is critically low");const f=`${n}.install-${process.pid}-${Date.now()}`;t.rmSync(f,{recursive:!0,force:!0}),t.mkdirSync(f,{recursive:!0});const h=`https://registry.npmjs.org/thinkpool-pair/-/thinkpool-pair-${s}.tgz`,g=r.join(f,"node_modules","thinkpool-pair","bridge.mjs");try{o(`${m(function(e=process.platform){const n=r.dirname(process.execPath),i=r.join(n,"win32"===e?"npm.cmd":"npm");try{return t.accessSync(i),i}catch{return"win32"===e?"npm.cmd":"npm"}}())} install --prefix ${m(f)} ${m(h)} --prefer-online --no-audit --no-fund`,{stdio:"inherit",shell:"/bin/bash"});const e=b(g);if(!t.existsSync(g)||e!==String(s))throw new Error(`runtime verification failed: requested ${s}, installed ${e||"no valid package"}`);t.rmSync(n,{recursive:!0,force:!0}),t.renameSync(f,n)}catch(e){throw t.rmSync(f,{recursive:!0,force:!0}),e}return i}finally{p()}}export function buildArtifact(n,{room:i=null,cmdArgs:s=[],autoUpdate:o=!1,version:a=$,staleProof:l=!1,runtimeEntry:d=null,scheduledRuns:g=c()}={}){const v=function(e){const n=r.dirname(process.execPath),i=r.join(n,"win32"===e?"npx.cmd":"npx");try{return t.accessSync(i),i}catch{return"win32"===e?"npx.cmd":"npx"}}(n),y=i?process.cwd():e.homedir(),b=u(i),k=r.join(e.homedir(),".thinkpool-pair"),S=r.join(k,`${b}.log`),w=sanitizeServicePath(process.env.PATH||"",n),_=r.join(e.homedir(),".thinkpool-pair","npm-cache"),x=s.length?["--",...s]:[],E=i?`bridge (${i})`:"account bridge",A=o?"thinkpool-pair@latest":`thinkpool-pair@${a}`,P=o||l?["--prefer-online"]:[],D=i?["-y",...P,A,i]:["-y",...P,A],N=!0===g?{darwin:"<key>TP_SCHEDULED_RUNS_ENABLED</key><string>1</string>",linux:"Environment=TP_SCHEDULED_RUNS_ENABLED=1\n",win32:'set "TP_SCHEDULED_RUNS_ENABLED=1"\r\n'}:{darwin:"",linux:"",win32:""};if("darwin"===n){const t=d?[process.execPath,d,...i?[i]:[],...x]:l?["/bin/bash","-lc",`${h(_)}; export NPM_CONFIG_CACHE=${m(_)}; exec ${[v,...D,...x].map(m).join(" ")}`]:[v,...D,...x];return{file:r.join(e.homedir(),"Library","LaunchAgents",`${p(i)}.plist`),content:`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n <key>Label</key><string>${p(i)}</string>\n <key>ProgramArguments</key><array>${t.map(e=>`<string>${f(e)}</string>`).join("")}</array>\n <key>RunAtLoad</key><true/>\n \x3c!-- KeepAlive only on UNSUCCESSFUL exit. A bridge that exits 0 has intentionally\n stood down — told to stop, or another bridge already owns this machine's\n singleton lock — and must NOT be relaunched, or launchd thrashes it every\n few seconds forever (the 2026-06-28 account-bridge restart loop). A crash\n or an update-restart exits non-zero and IS relaunched. Mirrors the bridge's\n own exit-code contract (stop()=0, restart-requested=non-zero). --\x3e\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>WorkingDirectory</key><string>${f(y)}</string>\n <key>StandardOutPath</key><string>${f(S)}</string>\n <key>StandardErrorPath</key><string>${f(S)}</string>\n <key>EnvironmentVariables</key><dict><key>PATH</key><string>${f(w)}</string>${d?"":`<key>NPM_CONFIG_CACHE</key><string>${f(_)}</string>`}${o?"<key>THINKPOOL_PAIR_AUTOUPDATE</key><string>1</string>":""}${N.darwin}</dict>\n</dict></plist>\n`,logDir:k,post:[],expectedRuntime:d||A,note:"Loaded as a LaunchAgent — starts at login, restarts on crash."}}if("linux"===n){const t=d?[process.execPath,d,...i?[i]:[],...x]:[v,...D,...x];return{file:r.join(e.homedir(),".config","systemd","user",`${p(i)}.service`),content:`[Unit]\nDescription=thinkpool Code ${E}\nAfter=network-online.target\n\n[Service]\nExecStart=${d?t.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" "):l?`/bin/bash -lc "${h(_)}; export NPM_CONFIG_CACHE=${m(_)}; exec ${[v,...D,...x].join(" ")}"`:t.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" ")}\nRestart=always\nRestartSec=2\n# Don't relaunch a clean stand-down. exit 0 = the bridge intentionally stopped —\n# told to stop, or another bridge already holds this machine's singleton lock —\n# so restarting it just thrashes every RestartSec forever (the 2026-06-28\n# account-bridge restart loop). Crashes + update-restarts exit non-zero and are\n# still restarted. Matches the bridge's exit-code contract (stop()=0, restart=non-zero).\nRestartPreventExitStatus=0\nWorkingDirectory=${y}\nEnvironment=PATH=${w}\n${d?"":`Environment=NPM_CONFIG_CACHE=${_}\n`}${o?"Environment=THINKPOOL_PAIR_AUTOUPDATE=1\n":""}${N.linux}StandardOutput=append:${S}\nStandardError=append:${S}\n\n[Install]\nWantedBy=default.target\n`,logDir:k,post:["systemctl --user daemon-reload",`systemctl --user enable ${p(i)}.service`,`systemctl --user restart ${p(i)}.service`],expectedRuntime:d||A,note:"Enabled as a systemd --user service. Run `loginctl enable-linger $USER` once to keep it running after logout."}}if("win32"===n){const t=r.join(process.env.APPDATA||r.join(e.homedir(),"AppData","Roaming"),"Microsoft","Windows","Start Menu","Programs","Startup"),n=r.join(t,`thinkpool-pair-${b}.cmd`),s=d?[process.execPath,d,...i?[i,"--supervise"]:[],...x].map(e=>`"${e}"`).join(" "):i?["npx","-y",...P,A,i,"--supervise",...x].join(" "):["npx","-y",...P,A].join(" ");return{file:n,content:`@echo off\r\ntitle thinkpool-pair ${b}\r\n${d?"":`set "NPM_CONFIG_CACHE=${_}"\r\n`}${N.win32}${s}\r\n`,logDir:k,post:[],note:"Installed to the Startup folder — runs at login"+(i?" with --supervise (auto-restart on crash).":" (account mode).")+" Start it now without rebooting by double-clicking the .cmd, or run it from a terminal."}}throw new Error(`unsupported platform: ${n}`)}export function artifactScheduledRunsEnabled(e,t){const r=String(t||"");return"darwin"===e?/<key>\s*TP_SCHEDULED_RUNS_ENABLED\s*<\/key>\s*<string>\s*1\s*<\/string>/.test(r):"linux"===e?/^Environment=TP_SCHEDULED_RUNS_ENABLED=1\r?$/m.test(r):"win32"===e&&/^set "TP_SCHEDULED_RUNS_ENABLED=1"\r?$/m.test(r)}export function installedScheduledRunsEnabled(e,{platform:r=process.platform,readFile:n=e=>t.readFileSync(e,"utf8")}={}){try{return artifactScheduledRunsEnabled(r,n(buildArtifact(r,{room:e,scheduledRuns:!1}).file))}catch{return!1}}export function installedServiceRuntimeVersion(e,{platform:r=process.platform,readFile:n=e=>t.readFileSync(e,"utf8")}={}){try{const t=parseRunningPairVersions(n(buildArtifact(r,{room:e}).file));return 1===t.size?[...t][0]:null}catch{return null}}export function parseRunningPairVersions(e){const t=String(e).match(/(?:thinkpool-pair@|runtimes[\\/])(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/g)||[];return new Set(t.map(e=>e.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0]).filter(Boolean))}export function serviceRuntimeSnapshot(e,{platform:t=process.platform,exec:r=n}={}){const i=e=>{const t=parseRunningPairVersions(e);return 1===t.size?[...t][0]:null},s=p(e);try{if("darwin"===t){const e=r(`launchctl print gui/$(id -u)/${s}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],shell:"/bin/bash"}),t=String(e).match(/(^|\n)[ \t]*pid = (\d+)[ \t]*($|\n)/)?.[2];return darwinServiceRunning(e)?{version:i(e),pid:t||null}:null}if("linux"===t){const e=r(`systemctl --user show ${s}.service --property=ActiveState --property=MainPID --property=ExecStart`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]}),t=String(e).match(/^MainPID=([1-9]\d*)$/m)?.[1];return/^ActiveState=active$/m.test(String(e))&&t?{version:i(e),pid:t}:null}}catch{}return null}export function serviceRuntimeVersion(e,t){return serviceRuntimeSnapshot(e,t)?.version||null}export function buildDarwinReloadHandoff({targetLabel:t,targetFile:n,stagedFile:i,expectedRuntime:s,version:o,nonce:a=`${process.pid}-${Date.now()}`}){if(!/^io\.thinkpool\.pair\.[A-Za-z0-9_.-]+$/.test(String(t)))throw new Error(`invalid service label: ${t}`);if(!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(o)))throw new Error(`invalid runtime version: ${o}`);const l=`io.thinkpool.pair.update.${String(a).replace(/[^A-Za-z0-9-]/g,"")||String(process.pid)}`,c=r.join(e.homedir(),"Library","LaunchAgents",`${l}.plist`),d=`${i}.previous`,u=r.join(e.homedir(),".thinkpool-pair","update-status.json"),h=r.join(e.homedir(),".thinkpool-pair","update.log"),$=r.join(e.homedir(),".thinkpool-pair","account-ready.json"),g=t===p(null),v=["set -u",'dom="gui/$(id -u)"',`target=${m(t)}`,`helper=${m(l)}`,`target_file=${m(n)}`,`staged_file=${m(i)}`,`backup_file=${m(d)}`,`helper_file=${m(c)}`,`expected=${m(s)}`,`version=${m(o)}`,`status=${m(u)}`,`ready=${m($)}`,"requires_ready="+(g?"1":"0"),'tmp="$status.tmp.$$"',"ok=0",'live_pid=""',"stable=0","had_previous=0",'if [ -f "$target_file" ]; then cp "$target_file" "$backup_file" && had_previous=1; fi','launchctl bootout "$dom/$target" 2>/dev/null || true','for i in $(seq 1 40); do launchctl print "$dom/$target" >/dev/null 2>&1 || break; sleep 0.2; done','if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi','cp "$staged_file" "$target_file"','launchctl enable "$dom/$target" 2>/dev/null || true','for i in $(seq 1 30); do launchctl bootstrap "$dom" "$target_file" 2>/dev/null && break; sleep 0.3; done','printf "update_handoff_start target=%s version=%s timeout_s=90\\n" "$target" "$version"','for i in $(seq 1 180); do snapshot="$(launchctl print "$dom/$target" 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^[[:space:]]*pid = \\([0-9][0-9]*\\)[[:space:]]*$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && grep -F "\\"version\\":\\"$version\\"" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F -- "$expected" >/dev/null && printf "%s\\n" "$snapshot" | grep -F "state = running" >/dev/null; then if [ "$pid" = "$live_pid" ]; then stable=$((stable+1)); else live_pid="$pid"; stable=1; fi; if [ "$stable" -ge 5 ]; then ok=1; break; fi; else live_pid=""; stable=0; fi; sleep 0.5; done','if [ "$ok" != 1 ]; then ready_state=missing; [ -f "$ready" ] && ready_state=present; printf "update_handoff_verify_timeout target=%s version=%s last_pid=%s ready=%s\\n" "$target" "$version" "$live_pid" "$ready_state"; fi',`if [ "$ok" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!0,version:String(o),target:String(t)})}' > "$tmp"; else rollback=0; if [ "$had_previous" = 1 ]; then launchctl bootout "$dom/$target" 2>/dev/null || true; for i in $(seq 1 40); do launchctl print "$dom/$target" >/dev/null 2>&1 || break; sleep 0.2; done; if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi; cp "$backup_file" "$target_file"; launchctl enable "$dom/$target" 2>/dev/null || true; bootstrapped=0; for i in $(seq 1 30); do if launchctl bootstrap "$dom" "$target_file" 2>/dev/null; then bootstrapped=1; break; fi; sleep 0.3; done; if [ "$bootstrapped" = 1 ]; then rollback_pid=""; rollback_stable=0; for i in $(seq 1 180); do snapshot="$(launchctl print "$dom/$target" 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^[[:space:]]*pid = \\([0-9][0-9]*\\)[[:space:]]*$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "state = running" >/dev/null; then if [ "$pid" = "$rollback_pid" ]; then rollback_stable=$((rollback_stable+1)); else rollback_pid="$pid"; rollback_stable=1; fi; if [ "$rollback_stable" -ge 5 ]; then rollback=1; break; fi; else rollback_pid=""; rollback_stable=0; fi; sleep 0.5; done; fi; else launchctl bootout "$dom/$target" 2>/dev/null || true; rm -f "$target_file"; fi; if [ "$rollback" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!1,version:String(o),target:String(t),error:"new runtime not confirmed within 90s; previous service restored and ready",rolledBack:!0})}' > "$tmp"; else printf '%s\\n' '${JSON.stringify({ok:!1,version:String(o),target:String(t),error:"new runtime not confirmed within 90s and ready rollback failed",rolledBack:!1})}' > "$tmp"; fi; fi`,'printf "update_handoff_complete target=%s version=%s ok=%s\\n" "$target" "$version" "$ok"','mv "$tmp" "$status"','rm -f "$staged_file" "$backup_file" "$helper_file"','launchctl bootout "$dom/$helper" >/dev/null 2>&1 || true','exit "$((1-ok))"'].join("; ");return{helperLabel:l,helperFile:c,backupFile:d,statusFile:u,logFile:h,script:v,content:`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n <key>Label</key><string>${f(l)}</string>\n <key>ProgramArguments</key><array><string>/bin/bash</string><string>-lc</string><string>${f(v)}</string></array>\n <key>RunAtLoad</key><true/>\n <key>StandardOutPath</key><string>${f(h)}</string>\n <key>StandardErrorPath</key><string>${f(h)}</string>\n</dict></plist>\n`}}export function buildLinuxReloadHandoff({targetLabel:t,targetFile:n,stagedFile:i,expectedRuntime:s,version:o,nonce:a=`${process.pid}-${Date.now()}`}){if(!/^io\.thinkpool\.pair\.[A-Za-z0-9_.-]+$/.test(String(t)))throw new Error(`invalid service label: ${t}`);if(!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(o)))throw new Error(`invalid runtime version: ${o}`);const l=`io.thinkpool.pair.update.${String(a).replace(/[^A-Za-z0-9-]/g,"")||String(process.pid)}`,c=`${i}.previous`,d=r.join(e.homedir(),".thinkpool-pair","update-status.json"),u=r.join(e.homedir(),".thinkpool-pair","update.log"),f=r.join(e.homedir(),".thinkpool-pair","account-ready.json"),h=t===p(null);return{helperLabel:l,backupFile:c,statusFile:d,logFile:u,script:["set -u",`target=${m(t)}`,'unit="$target.service"',`target_file=${m(n)}`,`staged_file=${m(i)}`,`backup_file=${m(c)}`,`expected=${m(s)}`,`version=${m(o)}`,`status=${m(d)}`,`ready=${m(f)}`,"requires_ready="+(h?"1":"0"),'tmp="$status.tmp.$$"',"ok=0","had_previous=0",'if [ -f "$target_file" ]; then cp "$target_file" "$backup_file" && had_previous=1; fi','systemctl --user stop "$unit" 2>/dev/null || true','if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi','cp "$staged_file" "$target_file"',"systemctl --user daemon-reload",'systemctl --user enable "$unit" >/dev/null 2>&1 || true','systemctl --user restart "$unit" >/dev/null 2>&1 || true','live_pid=""',"stable=0",'printf "update_handoff_start target=%s version=%s timeout_s=90\\n" "$target" "$version"','for i in $(seq 1 180); do snapshot="$(systemctl --user show "$unit" --property=ActiveState --property=MainPID --property=ExecStart 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^MainPID=\\([1-9][0-9]*\\)$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && grep -F "\\"version\\":\\"$version\\"" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "ActiveState=active" >/dev/null && printf "%s\\n" "$snapshot" | grep -F -- "$expected" >/dev/null; then if [ "$pid" = "$live_pid" ]; then stable=$((stable+1)); else live_pid="$pid"; stable=1; fi; if [ "$stable" -ge 5 ]; then ok=1; break; fi; else live_pid=""; stable=0; fi; sleep 0.5; done','if [ "$ok" != 1 ]; then ready_state=missing; [ -f "$ready" ] && ready_state=present; printf "update_handoff_verify_timeout target=%s version=%s last_pid=%s ready=%s\\n" "$target" "$version" "$live_pid" "$ready_state"; fi',`if [ "$ok" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!0,version:String(o),target:String(t)})}' > "$tmp"; else rollback=0; if [ "$had_previous" = 1 ]; then systemctl --user stop "$unit" 2>/dev/null || true; if [ "$requires_ready" = 1 ]; then rm -f "$ready"; fi; cp "$backup_file" "$target_file"; systemctl --user daemon-reload; systemctl --user enable "$unit" >/dev/null 2>&1 || true; systemctl --user restart "$unit" >/dev/null 2>&1 || true; rollback_pid=""; rollback_stable=0; for i in $(seq 1 180); do snapshot="$(systemctl --user show "$unit" --property=ActiveState --property=MainPID 2>/dev/null || true)"; pid="$(printf "%s\\n" "$snapshot" | sed -n "s/^MainPID=\\([1-9][0-9]*\\)$/\\1/p" | head -1)"; ready_ok=1; if [ "$requires_ready" = 1 ]; then ready_ok=0; [ -f "$ready" ] && grep -F "\\"pid\\":$pid" "$ready" >/dev/null 2>&1 && ready_ok=1; fi; if [ -n "$pid" ] && [ "$ready_ok" = 1 ] && printf "%s\\n" "$snapshot" | grep -F "ActiveState=active" >/dev/null; then if [ "$pid" = "$rollback_pid" ]; then rollback_stable=$((rollback_stable+1)); else rollback_pid="$pid"; rollback_stable=1; fi; if [ "$rollback_stable" -ge 5 ]; then rollback=1; break; fi; else rollback_pid=""; rollback_stable=0; fi; sleep 0.5; done; else systemctl --user stop "$unit" 2>/dev/null || true; rm -f "$target_file"; systemctl --user daemon-reload; fi; if [ "$rollback" = 1 ]; then printf '%s\\n' '${JSON.stringify({ok:!1,version:String(o),target:String(t),error:"new runtime not confirmed within 90s; previous service restored and ready",rolledBack:!0})}' > "$tmp"; else printf '%s\\n' '${JSON.stringify({ok:!1,version:String(o),target:String(t),error:"new runtime not confirmed within 90s and ready rollback failed",rolledBack:!1})}' > "$tmp"; fi; fi`,'printf "update_handoff_complete target=%s version=%s ok=%s\\n" "$target" "$version" "$ok"','mv "$tmp" "$status"','rm -f "$staged_file" "$backup_file"','exit "$((1-ok))"'].join("; ")}}const S=()=>r.join(e.homedir(),".thinkpool-pair","account.lock"),w=e=>{try{return process.kill(e,0),!0}catch(e){return"EPERM"===e?.code}};export async function retireUnmanagedBridgeLock({lockFile:e=S(),readLock:r=()=>t.readFileSync(e,"utf8"),removeLock:i=()=>t.rmSync(e,{force:!0}),commandForPid:s=e=>String(n(`ps -p ${e} -o command=`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]})),alive:o=w,signal:a=(e,t)=>process.kill(e,t),sleep:l=e=>new Promise(t=>setTimeout(t,e)),waitMs:c=2500,pollMs:d=100,stderr:p=process.stderr}={}){let u=0;try{u=parseInt(String(r()).trim(),10)||0}catch{return!0}const f=()=>{if(o(u))return!1;try{(parseInt(String(r()).trim(),10)||0)===u&&i()}catch{}return!0};if(!u||u===process.pid||f())return!0;let m="";try{m=s(u)}catch{return f()}if(!/(?:node_modules[\\/]thinkpool-pair|\.npm[\\/]_npx|\.thinkpool-pair[\\/]runtimes)[\\/][^\n]*bridge\.mjs(?:\s|$)/.test(m))return p.write(` ⚠ account.lock belongs to PID ${u}, but it is not a verified thinkpool-pair bridge; refusing to signal an unrelated process.\n`),!1;p.write(` ◆ stopping previous thinkpool-pair bridge (PID ${u}) before installing the background service…\n`);try{a(u,"SIGTERM")}catch{if(f())return!0}const h=Math.max(1,Math.ceil(Math.max(0,Number(c)||0)/Math.max(1,Number(d)||1)));for(let e=0;e<h;e++){if(f())return!0;await l(Math.max(1,Number(d)||1))}try{a(u,"SIGKILL")}catch{}for(let e=0;e<5;e++){if(f())return!0;await l(Math.max(1,Number(d)||1))}return p.write(` ⚠ previous thinkpool-pair bridge PID ${u} did not stop; background install aborted to avoid two competing bridges.\n`),!1}function _(e){const t=r.basename(e).match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_.-]+)\.(?:plist|service)$/);if(!t)throw new Error(`cannot derive service label from ${e}`);return t[1]}export function existingServiceLabels(i=process.platform,{fsMod:s=t}={}){const o=new Set;try{if("darwin"===i){try{const e=n("launchctl print gui/$(id -u) 2>/dev/null | grep -oE 'io\\.thinkpool\\.pair\\.[A-Za-z0-9_]+' | sort -u",{encoding:"utf8",shell:"/bin/bash"});for(const t of e.split("\n")){const e=t.trim();e&&o.add(e)}}catch{}try{const t=r.join(e.homedir(),"Library","LaunchAgents");for(const e of s.readdirSync(t)){const t=e.match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_]+)\.plist$/);t&&o.add(t[1])}}catch{}}else if("linux"===i){try{const e=n("systemctl --user list-unit-files 'io.thinkpool.pair.*' --no-legend 2>/dev/null | awk '{print $1}'",{encoding:"utf8",shell:"/bin/bash"});for(const t of e.split("\n")){const e=t.trim().replace(/\.service$/,"");e&&o.add(e)}}catch{}try{const t=r.join(e.homedir(),".config","systemd","user");for(const e of s.readdirSync(t)){const t=e.match(/^(io\.thinkpool\.pair\.[A-Za-z0-9_]+)\.service$/);t&&o.add(t[1])}}catch{}}}catch{}return[...o]}export function sweepExistingServices(i,s=process.platform){for(const o of existingServiceLabels(s))if(o!==i)try{if("darwin"===s){n(`launchctl bootout gui/$(id -u)/${o} 2>/dev/null || true`,{stdio:"ignore",shell:"/bin/bash"});try{t.rmSync(r.join(e.homedir(),"Library","LaunchAgents",`${o}.plist`),{force:!0})}catch{}}else"linux"===s&&n(`systemctl --user disable --now ${o}.service 2>/dev/null || true`,{stdio:"ignore",shell:"/bin/bash"});process.stderr.write(` ◆ swept stale service ${o}\n`)}catch{}if("win32"!==s)try{n("pkill -9 -f 'thinkpool-pair/bridge.mjs [A-Z0-9]' 2>/dev/null || true",{stdio:"ignore",shell:"/bin/bash"})}catch{}}export function installService(e,i=[],{autoUpdate:s=!1,version:o=$,staleProof:d=!0}={}){let f=null;if(!s){const t=installedServiceRuntimeVersion(e),r=serviceRuntimeVersion(e);try{f=provisionRuntime(o,{protectedVersions:[t,r]})}catch(e){return process.stderr.write(` ⚠ runtime install failed; service left unchanged: ${e.message}\n`),!1}}const h=c()||installedScheduledRunsEnabled(e),g=buildArtifact(process.platform,{room:e,cmdArgs:i,autoUpdate:s,version:o,staleProof:d,runtimeEntry:f,scheduledRuns:h});try{sweepExistingServices(p(e),process.platform)}catch{}if(t.mkdirSync(g.logDir,{recursive:!0}),t.mkdirSync(r.dirname(g.file),{recursive:!0}),"darwin"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${g.file}\n`),!function(e,i){const s=r.join(e.logDir,`service-update-${process.pid}-${Date.now()}.plist`),o=buildDarwinReloadHandoff({targetLabel:_(e.file),targetFile:e.file,stagedFile:s,expectedRuntime:e.expectedRuntime,version:i});t.mkdirSync(r.dirname(o.helperFile),{recursive:!0}),t.mkdirSync(r.dirname(o.statusFile),{recursive:!0}),t.rmSync(o.statusFile,{force:!0}),t.writeFileSync(s,e.content),t.writeFileSync(o.helperFile,o.content);try{return n(`launchctl bootstrap gui/$(id -u) ${m(o.helperFile)}`,{stdio:"inherit",shell:"/bin/bash"}),process.stderr.write(` ◆ update handoff armed outside the bridge process tree — completion is recorded in ${o.statusFile}.\n`),!0}catch(e){return t.rmSync(s,{force:!0}),t.rmSync(o.helperFile,{force:!0}),process.stderr.write(` ⚠ could not arm the launchd update handoff; existing service left running: ${e.message}\n`),!1}}(g,o))return!1}else if("linux"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${g.file}\n`),!function(e,i){const s=r.join(e.logDir,`service-update-${process.pid}-${Date.now()}.service`),o=buildLinuxReloadHandoff({targetLabel:_(e.file),targetFile:e.file,stagedFile:s,expectedRuntime:e.expectedRuntime,version:i});t.mkdirSync(r.dirname(o.statusFile),{recursive:!0}),t.rmSync(o.statusFile,{force:!0}),t.writeFileSync(s,e.content);try{return n(`systemd-run --user --unit=${m(o.helperLabel)} --collect --property=StandardOutput=append:${m(o.logFile)} --property=StandardError=append:${m(o.logFile)} /bin/bash -lc ${m(o.script)}`,{stdio:"inherit",shell:"/bin/bash"}),process.stderr.write(` ◆ update handoff armed outside the bridge service cgroup — completion is recorded in ${o.statusFile}.\n`),!0}catch(e){return t.rmSync(s,{force:!0}),process.stderr.write(` ⚠ could not arm the systemd update handoff; existing service left running: ${e.message}\n`),!1}}(g,o))return!1}else{t.writeFileSync(g.file,g.content),process.stderr.write(`\n ◆ wrote ${g.file}\n`);for(const e of g.post)try{n(e,{stdio:"inherit"})}catch(t){return process.stderr.write(` ⚠ "${e}" failed; service update NOT confirmed: ${t.message}\n`),!1}}const v=Math.max(10,parseInt(process.env.THINKPOOL_PAIR_UPDATE_IDLE,10)||90),y=s?"win32"===process.platform?"auto-update ON: a new thinkpool-pair@latest applies on the next login/restart (Windows has no in-process self-update).":`auto-update ON: checks npm ~every 30 min and restarts to apply a new @latest once sessions are idle (≥${v}s quiet; they resume in place).`:`PINNED to ${o} (stable local runtime; restarts do not depend on npx or the network). To update: use "Restart & update the bridge" (or run ${a("install-service",e||void 0)}). Pass --auto-update to track @latest instead.`,b=e?` ${e}`:"",k=e?`room ${e}`:"your account (auto-serves every session)",S=l()?" ◆ Keep-awake on: system sleep is inhibited while the bridge runs (higher battery use); the display may still dim, lock, and turn off.\n":"";return process.stderr.write(` ◆ ${k}\n ◆ ${"darwin"===process.platform?"Launchd is completing and verifying the reload independently.":g.note}\n ◆ Prerequisite: Claude Code, Codex, or Hermes must be installed on this machine. BYOK supplies model credentials; it does not install an agent.\n${S} ◆ ${y}\n ◆ logs: ${r.join(g.logDir,`${u(e)}.log`)}\n ◆ remove with: ${a("uninstall-service")}${b}\n\n`),!0}export async function installAndConfirmService(i,a=[],{platform:l=process.platform,version:c=$,staleProof:d=!0,install:u=installService,exec:f=n,snapshot:m=serviceRuntimeSnapshot,prepareAccountHandoff:h=retireUnmanagedBridgeLock,readReady:g=()=>s(),readStatus:v=()=>{try{return JSON.parse(t.readFileSync(r.join(e.homedir(),".thinkpool-pair","update-status.json"),"utf8"))}catch{return null}},now:y=Date.now,sleep:b=e=>new Promise(t=>setTimeout(t,e)),timeoutMs:k=2e5,pollMs:S=250,stderr:w=process.stderr}={}){const _=m(i,{platform:l,exec:f});if(!i&&"win32"!==l&&!_&&!await h())return w.write(" ⚠ background install aborted: the previous bridge could not be handed off safely.\n"),!1;const x=y();if(!1===u(i,a,{version:c,staleProof:d}))return!1;const E=()=>{const e=m(i,{platform:l,exec:f}),t=!!i||"win32"===l||o(g(),e,c,{notBefore:x});return!(e?.version!==c||!t||(w.write(` ✓ bridge install confirmed — running v${c}.\n`),0))};if("win32"===l)return!0;const A=y()+Math.max(0,Number(k)||0),P=p(i);for(;;){const e=v();if(e&&"object"==typeof e&&e.target===P&&e.version===c){if(!1===e.ok)return w.write(` ⚠ install v${c} was not confirmed: ${e.error||"the launchd handoff failed"}.\n`),!1;if(!0===e.ok&&E())return!0}if(y()>=A)return w.write(` ⚠ install v${c} timed out waiting for supervisor readiness; no connected account bridge was proven.\n`),!1;await b(Math.max(1,Number(S)||1))}}export function restartService(e){const t=process.platform;if(!isServiceInstalled(e))return process.stderr.write(" ⚠ no background service installed to restart — install one first.\n"),!1;const r=p(e);try{if("darwin"===t)n(`launchctl kickstart -k gui/$(id -u)/${r}`,{stdio:"inherit"});else{if("linux"!==t)return process.stderr.write(" ⚠ Windows has no managed daemon — close the running thinkpool-pair window and re-launch the Startup .cmd (or reboot) to restart.\n"),!1;n(`systemctl --user restart ${r}.service`,{stdio:"inherit"})}return process.stderr.write(` ◆ restarted ${r} — any active session resumes in place.\n`),!0}catch(e){return process.stderr.write(` ⚠ restart failed: ${e.message}\n`),!1}}function x(e=n){let t="";try{t=String(e("npm view thinkpool-pair version",{encoding:"utf8",stdio:["ignore","pipe","ignore"]})).trim()}catch{}return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(t)?t:null}export function stageServiceUpdate(e,{exec:t=n,active:r=isServiceInstalled,provision:i=provisionRuntime}={}){if(!r(e))return process.stderr.write(" ⚠ no background service installed to update — install one first.\n"),null;const s=x(t);if(!s)return process.stderr.write(" ⚠ couldn't reach npm or resolve the published version — bridge left unchanged; NO update or restart was applied.\n"),null;process.stderr.write(`\n ◆ preparing bridge v${s} in the background; active sessions keep running…\n`);try{return i(s),s}catch(e){return process.stderr.write(` ⚠ couldn't stage bridge v${s}: ${e?.message||e}\n`),null}}export function applyStagedServiceUpdate(e,t,{install:r=installService,active:n=isServiceInstalled}={}){return!(!n(e)||!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(t||"")))&&(process.stderr.write(`\n ◆ updating the background service to v${t} and restarting…\n`),!1!==r(e,[],{version:t,staleProof:!0}))}export function updateService(e,{exec:t=n,install:r=installService,active:i=isServiceInstalled}={}){if(!i(e))return process.stderr.write(" ⚠ no background service installed to update — install one first.\n"),!1;const s=x(t);return s?(process.stderr.write(`\n ◆ updating the background service to v${s} and restarting…\n`),!1!==r(e,[],{version:s,staleProof:!0})):(process.stderr.write(" ⚠ couldn't reach npm or resolve the published version — bridge left unchanged; NO update or restart was applied.\n"),!1)}export async function updateAndConfirmService(i,{platform:a=process.platform,exec:l=n,update:c=updateService,install:d=installService,active:u=isServiceInstalled,snapshot:f=serviceRuntimeSnapshot,readReady:m=()=>s(),readStatus:h=()=>{try{return JSON.parse(t.readFileSync(r.join(e.homedir(),".thinkpool-pair","update-status.json"),"utf8"))}catch{return null}},now:$=Date.now,sleep:g=e=>new Promise(t=>setTimeout(t,e)),timeoutMs:v=2e5,pollMs:y=250,stderr:b=process.stderr}={}){const k=f(i,{platform:a,exec:l}),S=$();let w=null;if(!1===c(i,{exec:(...e)=>{const t=l(...e),r=String(t).trim();return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(r)&&(w=r),t},install:d,active:u})||!w)return b.write(" ⚠ update was not confirmed: staging the published runtime failed; the existing service was left unchanged.\n"),!1;const _=()=>{const e=f(i,{platform:a,exec:l}),t=!!k&&k.version!==w,r=!!k?.pid&&!!e?.pid&&k.pid!==e.pid,n=!!i||"win32"===a||o(m(),e,w,{notBefore:S});return e?.version===w&&(t||r)&&n?(b.write(` ✓ bridge restart confirmed — running v${w}.\n`),{ok:!0,live:e}):{ok:!1,live:e}},x=({live:e}={})=>{e?.version&&e.version!==w?b.write(` ⚠ update v${w} was not confirmed: live service is still v${e.version}.\n`):e?.version===w?b.write(` ⚠ update v${w} was not confirmed: the service process did not restart.\n`):b.write(` ⚠ update v${w} was not confirmed: the service manager cannot prove the new runtime is running.\n`)};if("win32"===a)return!0;const E=$()+Math.max(0,Number(v)||0),A=p(i);let P=null;for(;;){const e=h();if(e&&"object"==typeof e&&e.target===A){if(!1===e.ok&&e.version===w)return b.write(` ⚠ update v${w} was not confirmed: ${e.error||"the launchd handoff failed"}.\n`),!1;if(!0===e.ok&&e.version===w&&(P=_(),P.ok))return!0}if($()>=E)return P?x(P):b.write(` ⚠ update v${w} timed out waiting for launchd confirmation; the service may have rolled back.\n`),!1;await g(Math.max(1,Number(y)||1))}}export function serviceActive(e,{platform:t=process.platform,exec:r=n}={}){const i=t,s=p(e);try{return"darwin"===i?darwinServiceRunning(r(`launchctl print gui/$(id -u)/${s}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"]})):"linux"===i&&(r(`systemctl --user is-active --quiet ${s}.service`),!0)}catch{return!1}}export function isServiceInstalled(e){if("win32"===process.platform)try{return t.existsSync(buildArtifact("win32",{room:e}).file)}catch{return!1}return serviceActive(e)}export function uninstallService(e){const r=process.platform;let i;try{i=buildArtifact(r,{room:e})}catch(e){return void process.stderr.write(` ⚠ ${e.message}\n`)}if("darwin"===r)try{n(`launchctl bootout gui/$(id -u)/${p(e)} 2>/dev/null`)}catch{}if("linux"===r)try{n(`systemctl --user disable --now ${p(e)}.service 2>/dev/null`)}catch{}try{t.rmSync(i.file,{force:!0}),process.stderr.write(` ◆ removed ${i.file}\n`)}catch(e){process.stderr.write(` ⚠ ${e.message}\n`)}}