thinkpool-pair 0.7.424 → 0.7.426
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/account.mjs +1 -1
- package/bridge.mjs +1 -1
- package/codex-session.mjs +1 -1
- package/package.json +1 -2
- package/publish-manifest.json +10 -11
- package/scheduled-runs.mjs +1 -1
- package/service.mjs +1 -1
- package/host-auth.mjs +0 -1
package/codex-session.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:fs";import n from"node:os";import o from"node:path";import s from"node:readline";import{buildCodexAppServerArgs as a,codexAppServerGate as i,createCodexAppServer as l}from"./codex-app-server.mjs";import{CodexEventMapper as u}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as c}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as d,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as w}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as b}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as k}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as _,codexReasoningEfforts as S,normalizeCodexReasoningEffort as C,normalizeReasoningEffortList as I}from"./reasoning-effort.mjs";const M=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const T=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=_(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:s,timeoutMs:a=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:c=r,spawnImpl:d=t,codexHost:p={}}={}){const m=String(e||"").trim();if(!m)throw new Error("codex one-shot requires a prompt");const f=!l,h=l||c.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let x=null;try{i&&(x=o.join(h,"output-schema.json"),c.writeFileSync(x,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...x?["--output-schema",x]:[],m];return await k(()=>new Promise((t,r)=>{let n,o,s="",i="",l=!1;const u=(e,n)=>{l||(l=!0,o&&clearTimeout(o),e?r(e):t(n))};try{n=d("codex",e,{cwd:h,env:process.env,stdio:["ignore","pipe","pipe"]})}catch(e){return void u(e)}n.stdout.on("data",e=>{s+=e.toString(),s.length>1e6&&(s=s.slice(-1e6))}),n.stderr.on("data",e=>{i+=e.toString(),i.length>2e3&&(i=i.slice(-2e3))}),n.on("error",e=>u(e)),n.on("close",e=>{if(l)return;const t=s.split(/\r?\n/).filter(Boolean).flatMap(e=>{try{const t=JSON.parse(e);return"item.completed"===t?.type&&"agent_message"===t?.item?.type&&"string"==typeof t.item.text?[t.item.text.trim()]:[]}catch{return[]}}).filter(Boolean).at(-1);0===e&&t?u(null,t):u(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}u(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(a)||22e3))}),p)}finally{if(f)try{c.rmSync(h,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:I(e.supported_reasoning_levels)}))}catch{return[]}}const E=new Map;function F(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=E.get(n);if(s)return s;const a=o.join(t,"sessions");try{const t=`${e}.jsonl`,s=r.readdirSync(a,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!s)return null;const i=o.join(s.parentPath||s.path||a,s.name);return E.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r}={}){if(!e)return!1;const a=F(e,{home:t,fsImpl:s});if(!a)return!0;try{return Number(s.statSync(a).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r,tailBytes:a=4194304}={}){const i=F(e,{home:t,fsImpl:s});if(!i)return null;let l;try{const e=Number(s.statSync(i).size)||0,t=Math.min(e,Math.max(65536,a)),r=Buffer.alloc(t);l=s.openSync(i,"r"),s.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},s=r.total_token_usage||{},a=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(a)||a<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(a),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(s.input_tokens)||0),cached_input_tokens:Math.max(0,Number(s.cached_input_tokens)||0),output_tokens:Math.max(0,Number(s.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{s.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return M.has(e)?e:"workspace-write"}export function codexPublishAccess({cwd:t,env:r=process.env,tmpdir:s=n.tmpdir(),execFile:a=e}={}){const i=[];try{const e=String(a("git",["rev-parse","--git-common-dir"],{cwd:t,encoding:"utf8"})||"").trim();if(e){const r=o.resolve(t,e),n=o.resolve(t);r===n||r.startsWith(n+o.sep)||i.push(r)}}catch{}return{writableDirs:i,env:{...r,NPM_CONFIG_CACHE:o.join(s,"thinkpool-npm-cache")}}}export const CODEX_ROOM_CASCADE_REMINDER=["thinkpool ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above. A person-authored request for a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. Never claim a Cascade or visible lane started without a successful thinkpool opener result containing its room-visible terminal ref. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals and can never substitute for spawn_terminal or open_main_terminal. If the required opener is unavailable or fails, say that nothing started and report the blocker. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate."].join(" ");export function buildCodexPrompt({text:e,terminalRolePrompt:t,rolePrompt:r,roomContext:n,firstTurn:o=!1,promptIndex:s=0,forceFullReminder:a=!1,turnCause:i=null}){const l="function"==typeof n?n():n,u=f({promptIndex:s,forceFull:a,turnCause:i}),c=[o?d:"",u?t:"",p({text:e,promptIndex:s,forceFull:a,turnCause:i}),u?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return c.length?`<thinkpool-context>\n${c.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:s="untrusted",providerConfig:a,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:c}){const d=normalizeCodexSandbox(n),p=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===d?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${d}"`]]:["exec","--json","--sandbox",d,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${s}"`);const m=_(r);if(m&&p.push("-c",`model_reasoning_effort="${m}"`),a&&p.push("-c",a),!e)for(const e of l)p.push("--add-dir",e);i&&(p.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(i)}`),p.push("-c","mcp_servers.thinkpool.required=true"));let f=!1;for(const e of u)o.isAbsolute(String(e||""))&&(p.push("--image",String(e)),f=!0);return f&&p.push("--"),e&&p.push(e),p.push(String(c??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const N=/\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i;export function codexApprovalCard(e,t={},r=null){const n=String(t.approvalId||t.itemId||`${t.turnId||"turn"}:${e}`);if("item/tool/requestUserInput"===e){const e=Array.isArray(t.questions)?t.questions:[];return{id:n,toolName:"AskUserQuestion",input:{questions:e},risk:"ask",questions:e,asker:"Codex",answerFormat:"codex",...Number.isFinite(t.autoResolutionMs)&&t.autoResolutionMs>0?{autoResolutionMs:t.autoResolutionMs}:{}}}if("item/commandExecution/requestApproval"===e){const e=t.command||r?.command||"";return{id:n,toolName:"Bash",input:{command:e,cwd:t.cwd||r?.cwd||"",...t.reason?{reason:t.reason}:{}},risk:N.test(e)?"high":"medium"}}if("item/fileChange/requestApproval"===e){const e=Array.isArray(r?.changes)?r.changes:[],o=e[0]||{};return{id:n,toolName:e.length>1?"MultiEdit":"add"===o.kind?"Write":"Edit",input:e.length>1?{edits:e.map(e=>({file_path:e.path||"",path:e.path||"",kind:e.kind||"update"})),...t.reason?{reason:t.reason}:{}}:{file_path:o.path||t.grantRoot||"",path:o.path||t.grantRoot||"",kind:o.kind||"update",...t.reason?{reason:t.reason}:{}},risk:"medium"}}return null}export function codexApprovalResponse(e){return{decision:"always"===e?"acceptForSession":"allow"===e?"accept":"decline"}}export function codexUserInputResponse(e,t=[]){return h(e,t)}function R(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:d,models:p=[],effort:h="high",resume:b,env:I,sandbox:M,mode:E="default",onEvent:F,providerConfig:N,terminalRolePrompt:O,rolePrompt:A,roomContext:P,mcpServers:D,prepareCwd:U=null,requestPermission:j,appServerGate:$=i,appServerFactory:L=l,mcpHttpFactory:q=c,spawnImpl:G=t,admitStart:X=null,stallOptions:H={},codexHost:z={}}){let B=CODEX_MODE_CONFIG[E]?E:"default",W=codexConfigForMode(B);M=normalizeCodexSandbox(M||W.sandbox);const J=codexPublishAccess({cwd:e,env:{...process.env,...I||{}}}),K=J.env,V=b||null,Y=!V||codexThreadCanResume(V,{home:K.CODEX_HOME||o.join(n.homedir(),".codex")});let Q=Y?V:null,Z=Q?1:0,ee=0,te=!0;const re=m(P);let ne=null,oe=!1,se=!1,ae=!1;const ie="function"==typeof H.now?H.now:Date.now,le="function"==typeof H.setInterval?H.setInterval:setInterval,ue="function"==typeof H.clearInterval?H.clearInterval:clearInterval,ce=Number.isFinite(H.stallMs)?Math.max(1,Number(H.stallMs)):Math.max(3e4,parseInt(K.TP_STALL_MS,10)||9e4),de=Number.isFinite(H.forceStopMs)?Math.max(ce,Number(H.forceStopMs)):Math.max(3*ce,parseInt(K.TP_FORCE_STOP_MS,10)||3e5),pe=Number.isFinite(H.longReasoningForceStopMs)?Math.max(de,Number(H.longReasoningForceStopMs)):Math.max(de,parseInt(K.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),me=Number.isFinite(H.intervalMs)?Math.max(1,Number(H.intervalMs)):5e3;let fe=ie(),he=!1,xe=!1,ge=!1,ye=!1,ve=!0,we=0,be=0,ke=null,_e=d||null,Se=C(h,{models:p,model:_e}),Ce=Q?readCodexThreadUsage(Q):null,Ie=null;const Me=$({env:K});let Te=!Me.enabled,Ee=null,Fe=!1;const Ne=new WeakSet;let Re=null,Oe=null,Ae=!1;const Pe=new Set,De=new Map,Ue=new Set,je=new Map;let $e=Promise.resolve(),Le=Promise.resolve();const qe=[],Ge=y(e=>{try{F?.(e)}catch{}}),Xe=()=>{fe=ie(),he=!1},He=e=>{Xe(),Ge(e)};He.cancel=()=>Ge.cancel();const ze=new u({onEvent:He,model:d||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Ce=t),t||Ce}}),Be=()=>{const e=[...je.values()];je.clear();for(const t of e)t.text&&He({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},We=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Be(),ae=!1),ze.push(e)},Je=e=>{Be(),ae=!1,He(e)},Ke=({retry:e=!1,recoveryAttempt:t=0}={})=>{ae=!0,fe=ie(),he=!1,ge=!1,ye=!1,ve=!0,we=Math.max(0,Number(t)||0),e||(xe=!1)},Ve=e=>He({kind:"note",text:e}),Ye=(e=Re)=>{const t=String(e||"");if(t)for(Pe.add(t);Pe.size>64;)Pe.delete(Pe.values().next().value)},Qe=(e={})=>{const t=String(e.turnId||"");return!(!t||se||oe||Pe.has(t)||t!==String(Re||""))};async function Ze(){const e=D?.thinkpool;return e?(Ie||(Ie=q({sdkServer:e})),Ie):null}function et(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&Q&&String(t.threadId)!==String(Q))return;(ae||Ae)&&Xe();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ce=e,Ae){const{used:t,max:r}=e.context;He({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:_e}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(se||oe)return void Ye(e);if(Oe){const t=Oe;return Oe=null,void t.resolve(e)}return Re||(Re=e),void Xe()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||Qe(t)){if(Xe(),"turn/plan/updated"===e){const e=(Array.isArray(t.plan)?t.plan:[]).map(({step:e,status:t})=>({content:e||"",activeForm:e||"",status:"inProgress"===t?"in_progress":"completed"===t?"completed":"pending"})).filter(e=>e.content);return void He({kind:"assistant",blocks:[{type:"tool_use",id:`codex-plan:${t.turnId||"active"}`,name:"TodoWrite",input:{todos:e}}],parentToolUseId:null})}if("item/agentMessage/delta"===e){if(!t.delta)return;const e=t.itemId||`turn:${t.turnId||Re||"active"}`,r=je.get(e)||{cid:`codex-stream:${Q||"thread"}:${e}`,text:""};return r.text+=String(t.delta),je.set(e,r),void He({kind:"assistant_stream",cid:r.cid,text:r.text,itemId:e,streaming:!0})}if("item/started"===e||"item/completed"===e){const r=t.item;if(!r)return;if("item/started"===e&&(De.set(r.id,r),r.id&&T(r)&&Ue.add(r.id)),"item/completed"===e&&(r.id&&Ue.delete(r.id),De.set(r.id,r),"agentMessage"===r.type&&je.has(r.id))){const e=je.get(r.id);return je.delete(r.id),void He({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void We({type:"item/started"===e?"item.started":"item.completed",item:R(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";We({type:"error",message:e})}}}async function tt(e,t={}){if(!Qe(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,De.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";be++;try{n=await(j?.(r))}catch{}finally{be=Math.max(0,be-1),Xe()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function rt(){if(Te)return!1;if(!1===Ee?.alive)return Fe=!1,Te=!0,!1;if(Ee&&Fe)return!0;let t;try{t=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ee){const r=L({cwd:e,env:K,args:a({providerConfig:N,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs)}),onNotification:et,onServerRequest:tt,codexHost:{...z,log:Ve},onClose:e=>{se||Te||Ne.has(r)||(Fe=!1,Te=!0,Ve(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ee=r,await Ee.start(),Xe()}return Q=await Ee.startThread({threadId:Z>0?Q:null,cwd:e,model:_e,sandbox:M,approvalPolicy:W.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Xe(),t?.url&&(await Ee.waitForMcpServer({threadId:Q,name:"thinkpool"}),Xe()),Fe=!0,We({type:"thread.started",thread_id:Q}),!0}catch{Te=!0;try{Ee?.end()}catch{}return Ee=null,Fe=!1,!1}}function nt({force:e=!1}={}){const t=Ee,r=Re;if(t&&e){Ye(r),Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}else if(t&&r)Ye(r),Promise.resolve(t.interrupt({threadId:Q,turnId:r})).catch(()=>{Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)});else if(t){Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}if(ne){const e=ne;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(ne===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function ot(e,t={}){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{if(ve=!1,Re=await Ee.startTurn({threadId:Q,input:e,images:Array.isArray(t.images)?t.images:[],model:_e,effort:_(Se),approvalPolicy:W.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(_e,Se)}),Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function st(e){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{ve=!1;const t=await Ee.startReview({threadId:Q,target:x(e)});if(Re=t?.turn?.id||t?.turnId,!Re)throw new Error("Codex review/start returned no turn id");if(Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function at(t,n={}){if(se||oe||ye)return;let o;try{o=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Ve(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===Z?null:Q,model:_e,effort:Se,sandbox:M,approvalPolicy:W.approvalPolicy,providerConfig:N,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:K,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),ae=!0,ve=!1;try{ne=G("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return ae=!1,e.codexExecFailure={spawnFailure:!0,hostRetrySafe:!0,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},void n(e)}if(null!=o)try{r.closeSync(o)}catch{}const l=ne,u=[];s.createInterface({input:l.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}Xe(),u.push(t.type),"item.started"===t.type&&t.item?.id&&T(t.item)&&Ue.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Ue.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(Q=Q||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(d=!0,ge=!1),We(t)});let c="",d=!1,p=!1;l.stderr.on("data",e=>{c+=e.toString(),c.length>800&&(c=c.slice(-800))}),l.on("error",e=>{p||(p=!0,ne===l&&(ne=null),ae=!1,Ue.clear(),e.codexExecFailure={spawnFailure:!0,hostRetrySafe:0===u.length,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},n(e))}),l.on("close",e=>{if(p)return;p=!0,ne===l&&(ne=null),ae=!1,Ue.clear();const r=`codex exec exited ${e}${c?": "+c.trim().slice(-300):""}`,o=0!==e&&null!=e&&!d&&codexExecFailedBeforeTurn({stderr:c,eventTypes:u});if(!d&&0!==e&&null!=e){const e=new Error(r);return e.codexExecFailure={preTurnInitFailure:o,hostRetrySafe:0===u.length,message:r,sawTerminalResult:!1},void n(e)}oe&&!d&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await k(i,{...z,log:Ve,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe})}catch(e){ne=null,ae=!1,Ue.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)Je({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!oe&&!ye)return{preTurnInitFailure:!0,message:t.message};(!ge&&!ye||oe)&&Je(oe?{kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}:{kind:"error",message:t.message||e?.message||"codex failed to start",recoverable:!0})}return{preTurnInitFailure:!1}}}function it(){if(se)return;const e=qe.shift();e&&(Le=Le.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);Ke({recoveryAttempt:t}),t>0&&Ge({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:n}),firstTurn:0===Z,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!se&&!oe&&!ye;){if(!(s?await st(e.text):await ot(o,e.options)))if(s)ge=!1,Je({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{ge&&(ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await at(o,e.options);if(r?.preTurnInitFailure){if(!(i||se||oe||ye)){i=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Je({kind:"error",message:r.message,recoverable:!0})}}if(!ge||a||ye||se||oe)break;ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"})}Z++,it()}))}return V&&!Y&&queueMicrotask(()=>Ve("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(K.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Me.enabled||queueMicrotask(()=>Ve(`Codex App Server disabled: ${Me.reason}`)),ke=le(()=>{const e=ie()-fe,t=v({turnActive:ae,awaitingUser:be,quietMs:e,stallMs:ce,forceStopMs:["max","ultra"].includes(Se)?pe:de,stalledSent:he,stallRetried:xe});if("none"===t)return;if("retry"===t&&!ve)return ye=!0,ge=!1,we>0?(Ge({kind:"error",recoverable:!0,message:"The safe recovery continuation also stopped responding. The original prompt was not replayed again. Switch to another model or provider, then continue from the current workspace state."}),Ge({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Ue.size>0?Ge({kind:"error",recoverable:!0,message:"Codex stopped responding while a tool was still running. The transport was stopped, but no automatic continuation was started because that could overlap an uncertain side effect. Inspect the current state before continuing manually."}):(qe.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:ee++,forceFullReminder:!0}),Ge({kind:"error",recoverable:!0,message:"Codex stopped responding after the turn began. The transport was stopped. A new continuation turn will inspect the current state; the original prompt was not replayed."})),Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void nt({force:!0});const r=w(t,e);if(r&&Ge(r),"status"!==t){if("retry"===t)return xe=!0,ge=!0,fe=ie(),he=!1,void nt();ye=!0,ge=!1,qe.length=0,Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),nt({force:!0})}else he=!0},me),ke.unref?.(),{get sessionId(){return Q},get usageSnapshot(){return Ce},get turnActive(){return ae||Ae},get canSteer(){return!(!Ee||!1===Ee.alive||!Fe||Te)},get started(){return ae||Ae||!!ne||!(!Ee||!1===Ee.alive)},sendTurn(t,r={}){if(se)return!1;if(!ae&&0===qe.length&&!Fe&&!ne){const e="function"==typeof X?X():{ok:!0};if(!1===e?.ok)return He({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=ee++,o=te;if(te=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!ae&&0===Z&&U)try{e=U()||e}catch{}if(ae&&Ee&&Re){Xe();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Re;return $e=$e.then(()=>Ee.steer({threadId:Q,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||1!==qe.length||it()):He({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||Ae||(oe=!1,Ke({recoveryAttempt:0})),1!==qe.length||Ae||it(),!0},async park(){if(se||ae||Ae||qe.length)return!1;const e=Le;try{await e}catch{}try{await $e}catch{}if(se||ae||Ae||qe.length||ne||Re)return!1;const t=Ee;if(t){Ne.add(t),Ee=null,Fe=!1,De.clear(),je.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Le,t=ae&&!Re&&!ne;oe=!0,ge=!1,ye=!1,qe.length=0,Ye(Re);let r=Promise.resolve();if(Ee&&Re&&(r=Ee.interrupt({threadId:Q,turnId:Re}).catch(()=>{})),ne){try{ne.kill("SIGTERM")}catch{}setTimeout(()=>{try{ne?.kill("SIGKILL")}catch{}},1500)}else t&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(se=!0,ae=!1,ke&&ue(ke),He.cancel(),qe.length=0,Ye(Re),ne)try{ne.kill()}catch{}try{Ee?.end()}catch{}Ie?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||ae)return!1;_e=String(e),ze.setModel(_e);const t=C(Se,{models:p,model:_e});return t!==Se&&(Se=t,He({kind:"effort",level:Se})),!0},setMode:e=>!(ae||!CODEX_MODE_CONFIG[e]||(B=e,W=codexConfigForMode(B),M=W.sandbox,Q=null,Z=0,Ce=null,Fe=!1,0)),setEffort:e=>!(ae||!S({models:p,model:_e}).includes(e)||(Se=e,He({kind:"effort",level:Se}),0)),clearContext:()=>!ae&&(Q=null,Z=0,Ce=null,Fe=!1,!0),async compactContext(){if(ae||Ae)return!1;oe=!1,Ae=!0;let e=null,t=!1;try{if(!await rt()||ae)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Oe=null,r(new Error("Codex did not start the compaction turn"))},5e3),Oe={resolve:t,reject:r}});await Ee.compact({threadId:Q}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ee.waitForTurn(n);Ye(o?.turn?.id||n);const s=o?.turn?.status;if("completed"===s)return!0;throw new Error(o?.turn?.error?.message||`compaction ${s||"failed"}`)}catch(e){return t||!["rejected","not_sent"].includes(e?.delivery)?(Ve(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Ve(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Oe=null,Ae=!1,qe.length&&it()}},async accountUsage(){if(!await rt())return null;const[e,t]=await Promise.all([Ee.accountUsage().catch(()=>null),Ee.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
|
|
1
|
+
import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:fs";import n from"node:os";import o from"node:path";import s from"node:readline";import{buildCodexAppServerArgs as a,codexAppServerGate as i,createCodexAppServer as l}from"./codex-app-server.mjs";import{CodexEventMapper as u}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as c}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as d,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as w}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as b}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as k}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as _,codexReasoningEfforts as S,normalizeCodexReasoningEffort as I,normalizeReasoningEffortList as C}from"./reasoning-effort.mjs";const M=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const T=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=_(t);return{mode:"default",settings:{model:String(e),reasoning_effort:r,developer_instructions:null}}}export function defaultModeForRuntime(e){return"codex"===e?"bypassPermissions":"default"}export async function codexOneShot({prompt:e,model:s,timeoutMs:a=22e3,outputSchema:i,cwd:l,tmpDir:u=n.tmpdir(),fsImpl:c=r,spawnImpl:d=t,codexHost:p={}}={}){const m=String(e||"").trim();if(!m)throw new Error("codex one-shot requires a prompt");const f=!l,h=l||c.mkdtempSync(o.join(u,"thinkpool-codex-one-shot-"));let x=null;try{i&&(x=o.join(h,"output-schema.json"),c.writeFileSync(x,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...x?["--output-schema",x]:[],m];return await k(()=>new Promise((t,r)=>{let n,o,s="",i="",l=!1;const u=(e,n)=>{l||(l=!0,o&&clearTimeout(o),e?r(e):t(n))};try{n=d("codex",e,{cwd:h,env:process.env,stdio:["ignore","pipe","pipe"]})}catch(e){return void u(e)}n.stdout.on("data",e=>{s+=e.toString(),s.length>1e6&&(s=s.slice(-1e6))}),n.stderr.on("data",e=>{i+=e.toString(),i.length>2e3&&(i=i.slice(-2e3))}),n.on("error",e=>u(e)),n.on("close",e=>{if(l)return;const t=s.split(/\r?\n/).filter(Boolean).flatMap(e=>{try{const t=JSON.parse(e);return"item.completed"===t?.type&&"agent_message"===t?.item?.type&&"string"==typeof t.item.text?[t.item.text.trim()]:[]}catch{return[]}}).filter(Boolean).at(-1);0===e&&t?u(null,t):u(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}u(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(a)||22e3))}),p)}finally{if(f)try{c.rmSync(h,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:C(e.supported_reasoning_levels)}))}catch{return[]}}const E=new Map;function F(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=E.get(n);if(s)return s;const a=o.join(t,"sessions");try{const t=`${e}.jsonl`,s=r.readdirSync(a,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!s)return null;const i=o.join(s.parentPath||s.path||a,s.name);return E.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r}={}){if(!e)return!1;const a=F(e,{home:t,fsImpl:s});if(!a)return!0;try{return Number(s.statSync(a).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r,tailBytes:a=4194304}={}){const i=F(e,{home:t,fsImpl:s});if(!i)return null;let l;try{const e=Number(s.statSync(i).size)||0,t=Math.min(e,Math.max(65536,a)),r=Buffer.alloc(t);l=s.openSync(i,"r"),s.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},s=r.total_token_usage||{},a=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(a)||a<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(a),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(s.input_tokens)||0),cached_input_tokens:Math.max(0,Number(s.cached_input_tokens)||0),output_tokens:Math.max(0,Number(s.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{s.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return M.has(e)?e:"workspace-write"}export function codexPublishAccess({cwd:t,env:r=process.env,tmpdir:s=n.tmpdir(),execFile:a=e}={}){const i=[];try{const e=String(a("git",["rev-parse","--git-common-dir"],{cwd:t,encoding:"utf8"})||"").trim();if(e){const r=o.resolve(t,e),n=o.resolve(t);r===n||r.startsWith(n+o.sep)||i.push(r)}}catch{}return{writableDirs:i,env:{...r,NPM_CONFIG_CACHE:o.join(s,"thinkpool-npm-cache")}}}export const CODEX_ROOM_CASCADE_REMINDER=["thinkpool ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above. A person-authored request for a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. Never claim a Cascade or visible lane started without a successful thinkpool opener result containing its room-visible terminal ref. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals and can never substitute for spawn_terminal or open_main_terminal. If the required opener is unavailable or fails, say that nothing started and report the blocker. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate."].join(" ");export function buildCodexPrompt({text:e,terminalRolePrompt:t,rolePrompt:r,roomContext:n,firstTurn:o=!1,promptIndex:s=0,forceFullReminder:a=!1,turnCause:i=null}){const l="function"==typeof n?n():n,u=f({promptIndex:s,forceFull:a,turnCause:i}),c=[o?d:"",u?t:"",p({text:e,promptIndex:s,forceFull:a,turnCause:i}),u?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return c.length?`<thinkpool-context>\n${c.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,effort:r,sandbox:n,approvalPolicy:s="untrusted",providerConfig:a,mcpUrl:i,writableDirs:l=[],images:u=[],prompt:c}){const d=normalizeCodexSandbox(n),p=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===d?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${d}"`]]:["exec","--json","--sandbox",d,"--skip-git-repo-check"]];t&&p.push("-m",t),p.push("-c",`approval_policy="${s}"`);const m=_(r);if(m&&p.push("-c",`model_reasoning_effort="${m}"`),a&&p.push("-c",a),!e)for(const e of l)p.push("--add-dir",e);i&&(p.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(i)}`),p.push("-c","mcp_servers.thinkpool.required=true"));let f=!1;for(const e of u)o.isAbsolute(String(e||""))&&(p.push("--image",String(e)),f=!0);return f&&p.push("--"),e&&p.push(e),p.push(String(c??"")),p}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const N=/\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i;export function codexApprovalCard(e,t={},r=null){const n=String(t.approvalId||t.itemId||`${t.turnId||"turn"}:${e}`);if("item/tool/requestUserInput"===e){const e=Array.isArray(t.questions)?t.questions:[];return{id:n,toolName:"AskUserQuestion",input:{questions:e},risk:"ask",questions:e,asker:"Codex",answerFormat:"codex",...Number.isFinite(t.autoResolutionMs)&&t.autoResolutionMs>0?{autoResolutionMs:t.autoResolutionMs}:{}}}if("item/commandExecution/requestApproval"===e){const e=t.command||r?.command||"";return{id:n,toolName:"Bash",input:{command:e,cwd:t.cwd||r?.cwd||"",...t.reason?{reason:t.reason}:{}},risk:N.test(e)?"high":"medium"}}if("item/fileChange/requestApproval"===e){const e=Array.isArray(r?.changes)?r.changes:[],o=e[0]||{};return{id:n,toolName:e.length>1?"MultiEdit":"add"===o.kind?"Write":"Edit",input:e.length>1?{edits:e.map(e=>({file_path:e.path||"",path:e.path||"",kind:e.kind||"update"})),...t.reason?{reason:t.reason}:{}}:{file_path:o.path||t.grantRoot||"",path:o.path||t.grantRoot||"",kind:o.kind||"update",...t.reason?{reason:t.reason}:{}},risk:"medium"}}return null}export function codexApprovalResponse(e){return{decision:"always"===e?"acceptForSession":"allow"===e?"accept":"decline"}}export function codexUserInputResponse(e,t=[]){return h(e,t)}function R(e){if(!e||"object"!=typeof e)return e;const t={...e};switch(e.type){case"agentMessage":return{...t,type:"agent_message"};case"commandExecution":return{...t,type:"command_execution",aggregated_output:e.aggregatedOutput||"",exit_code:e.exitCode};case"fileChange":return{...t,type:"file_change"};case"webSearch":return{...t,type:"web_search"};case"mcpToolCall":return{...t,type:"mcp_tool_call",arguments:e.arguments||{},result:e.result,error:e.error};case"reasoning":return{...t,type:"reasoning",text:[...e.summary||[],...e.content||[]].join("\n")};default:return t}}export function startCodexSession({cwd:e,model:d,models:p=[],effort:h="high",resume:b,env:C,sandbox:M,mode:E="default",onEvent:F,providerConfig:N,terminalRolePrompt:O,rolePrompt:A,roomContext:P,mcpServers:D,prepareCwd:U=null,requestPermission:j,appServerGate:$=i,appServerFactory:L=l,mcpHttpFactory:q=c,spawnImpl:G=t,admitStart:X=null,stallOptions:H={},codexHost:z={}}){let B=CODEX_MODE_CONFIG[E]?E:"default",W=codexConfigForMode(B);M=normalizeCodexSandbox(M||W.sandbox);const J=codexPublishAccess({cwd:e,env:{...process.env,...C||{}}}),K=J.env,V=b||null,Y=!V||codexThreadCanResume(V,{home:K.CODEX_HOME||o.join(n.homedir(),".codex")});let Q=Y?V:null,Z=Q?1:0,ee=0,te=!0;const re=m(P);let ne=null,oe=!1,se=!1,ae=!1;const ie="function"==typeof H.now?H.now:Date.now,le="function"==typeof H.setInterval?H.setInterval:setInterval,ue="function"==typeof H.clearInterval?H.clearInterval:clearInterval,ce=Number.isFinite(H.stallMs)?Math.max(1,Number(H.stallMs)):Math.max(3e4,parseInt(K.TP_STALL_MS,10)||9e4),de=Number.isFinite(H.forceStopMs)?Math.max(ce,Number(H.forceStopMs)):Math.max(3*ce,parseInt(K.TP_FORCE_STOP_MS,10)||3e5),pe=Number.isFinite(H.longReasoningForceStopMs)?Math.max(de,Number(H.longReasoningForceStopMs)):Math.max(de,parseInt(K.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),me=Number.isFinite(H.intervalMs)?Math.max(1,Number(H.intervalMs)):5e3;let fe=ie(),he=!1,xe=!1,ge=!1,ye=!1,ve=!0,we=0,be=0,ke=null,_e=d||null,Se=I(h,{models:p,model:_e}),Ie=Q?readCodexThreadUsage(Q):null,Ce=null;const Me=$({env:K});let Te=!Me.enabled,Ee=null,Fe=!1;const Ne=new WeakSet;let Re=null,Oe=null,Ae=!1;const Pe=new Set,De=new Map,Ue=new Set,je=new Map;let $e=Promise.resolve(),Le=Promise.resolve();const qe=[],Ge=y(e=>{try{F?.(e)}catch{}}),Xe=()=>{fe=ie(),he=!1},He=e=>{Xe(),Ge(e)};He.cancel=()=>Ge.cancel();const ze=new u({onEvent:He,model:d||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Ie=t),t||Ie}}),Be=()=>{const e=[...je.values()];je.clear();for(const t of e)t.text&&He({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},We=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Be(),ae=!1),ze.push(e)},Je=e=>{Be(),ae=!1,He(e)},Ke=({retry:e=!1,recoveryAttempt:t=0}={})=>{ae=!0,fe=ie(),he=!1,ge=!1,ye=!1,ve=!0,we=Math.max(0,Number(t)||0),e||(xe=!1)},Ve=e=>He({kind:"note",text:e}),Ye=(e=Re)=>{const t=String(e||"");if(t)for(Pe.add(t);Pe.size>64;)Pe.delete(Pe.values().next().value)},Qe=(e={})=>{const t=String(e.turnId||"");return!(!t||se||oe||Pe.has(t)||t!==String(Re||""))};async function Ze(){const e=D?.thinkpool;return e?(Ce||(Ce=q({sdkServer:e})),Ce):null}function et(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&Q&&String(t.threadId)!==String(Q))return;(ae||Ae)&&Xe();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ie=e,Ae){const{used:t,max:r}=e.context;He({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:_e}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(se||oe)return void Ye(e);if(Oe){const t=Oe;return Oe=null,void t.resolve(e)}return Re||(Re=e),void Xe()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||Qe(t)){if(Xe(),"turn/plan/updated"===e){const e=(Array.isArray(t.plan)?t.plan:[]).map(({step:e,status:t})=>({content:e||"",activeForm:e||"",status:"inProgress"===t?"in_progress":"completed"===t?"completed":"pending"})).filter(e=>e.content);return void He({kind:"assistant",blocks:[{type:"tool_use",id:`codex-plan:${t.turnId||"active"}`,name:"TodoWrite",input:{todos:e}}],parentToolUseId:null})}if("item/agentMessage/delta"===e){if(!t.delta)return;const e=t.itemId||`turn:${t.turnId||Re||"active"}`,r=je.get(e)||{cid:`codex-stream:${Q||"thread"}:${e}`,text:""};return r.text+=String(t.delta),je.set(e,r),void He({kind:"assistant_stream",cid:r.cid,text:r.text,itemId:e,streaming:!0})}if("item/started"===e||"item/completed"===e){const r=t.item;if(!r)return;if("item/started"===e&&(De.set(r.id,r),r.id&&T(r)&&Ue.add(r.id)),"item/completed"===e&&(r.id&&Ue.delete(r.id),De.set(r.id,r),"agentMessage"===r.type&&je.has(r.id))){const e=je.get(r.id);return je.delete(r.id),void He({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void We({type:"item/started"===e?"item.started":"item.completed",item:R(r)})}if("error"===e){const e=t.error?.message||t.message||"codex app-server error";We({type:"error",message:e})}}}async function tt(e,t={}){if(!Qe(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,De.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";be++;try{n=await(j?.(r))}catch{}finally{be=Math.max(0,be-1),Xe()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function rt(){if(Te)return!1;if(!1===Ee?.alive)return Fe=!1,Te=!0,!1;if(Ee&&Fe)return!0;let t;try{t=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ee){const r=L({cwd:e,env:K,args:a({providerConfig:N,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs)}),onNotification:et,onServerRequest:tt,codexHost:{...z,log:Ve},onClose:e=>{se||Te||Ne.has(r)||(Fe=!1,Te=!0,Ve(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ee=r,await Ee.start(),Xe()}return Q=await Ee.startThread({threadId:Z>0?Q:null,cwd:e,model:_e,sandbox:M,approvalPolicy:W.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Xe(),t?.url&&(await Ee.waitForMcpServer({threadId:Q,name:"thinkpool"}),Xe()),Fe=!0,We({type:"thread.started",thread_id:Q}),!0}catch{Te=!0;try{Ee?.end()}catch{}return Ee=null,Fe=!1,!1}}function nt({force:e=!1}={}){const t=Ee,r=Re;if(t&&e){Ye(r),Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}else if(t&&r)Ye(r),Promise.resolve(t.interrupt({threadId:Q,turnId:r})).catch(()=>{Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)});else if(t){Te=!0,Fe=!1;try{t.end()}catch{}Ee===t&&(Ee=null)}if(ne){const e=ne;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(ne===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function ot(e,t={}){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{if(ve=!1,Re=await Ee.startTurn({threadId:Q,input:e,images:Array.isArray(t.images)?t.images:[],model:_e,effort:_(Se),approvalPolicy:W.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(_e,Se)}),Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function st(e){if(se||oe||ye)return!0;if(!await rt())return!1;if(se||oe||ye)return!0;ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null),ae=!0;try{ve=!1;const t=await Ee.startReview({threadId:Q,target:x(e)});if(Re=t?.turn?.id||t?.turnId,!Re)throw new Error("Codex review/start returned no turn id");if(Xe(),oe||se){Ye(Re);try{await Ee.interrupt({threadId:Q,turnId:Re})}catch{}return!0}const r=await Ee.waitForTurn(Re);Ye(r?.turn?.id||Re);const n=r?.turn?.status;oe?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ge||ye)||("interrupted"===n?Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ge=!1,We({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(ge=!1,We({type:"turn.completed"})))}catch(e){Te=!0,Fe=!1,Ye(Re);try{Ee?.end()}catch{}Ee=null,se||ge||ye||We({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Be(),Re=null,ae=!1,Ue.clear(),De.clear(),je.clear()}return!0}async function at(t,n={}){if(se||oe||ye)return;let o;try{o=await Ze()}catch(e){Ve(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){Ve(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===Z?null:Q,model:_e,effort:Se,sandbox:M,approvalPolicy:W.approvalPolicy,providerConfig:N,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(B,M,J.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});ze.setUsageBaseline(Q?readCodexThreadUsage(Q):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:K,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),ae=!0,ve=!1;try{ne=G("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return ae=!1,e.codexExecFailure={spawnFailure:!0,hostRetrySafe:!0,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},void n(e)}if(null!=o)try{r.closeSync(o)}catch{}const l=ne,u=[];s.createInterface({input:l.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}Xe(),u.push(t.type),"item.started"===t.type&&t.item?.id&&T(t.item)&&Ue.add(t.item.id),"item.completed"===t.type&&t.item?.id&&Ue.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(Q=Q||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(d=!0,ge=!1),We(t)});let c="",d=!1,p=!1;l.stderr.on("data",e=>{c+=e.toString(),c.length>800&&(c=c.slice(-800))}),l.on("error",e=>{p||(p=!0,ne===l&&(ne=null),ae=!1,Ue.clear(),e.codexExecFailure={spawnFailure:!0,hostRetrySafe:0===u.length,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},n(e))}),l.on("close",e=>{if(p)return;p=!0,ne===l&&(ne=null),ae=!1,Ue.clear();const r=`codex exec exited ${e}${c?": "+c.trim().slice(-300):""}`,o=0!==e&&null!=e&&!d&&codexExecFailedBeforeTurn({stderr:c,eventTypes:u});if(!d&&0!==e&&null!=e){const e=new Error(r);return e.codexExecFailure={preTurnInitFailure:o,hostRetrySafe:0===u.length,message:r,sawTerminalResult:!1},void n(e)}oe&&!d&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await k(i,{...z,log:Ve,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe&&!0!==e?.codexExecFailure?.preTurnInitFailure})}catch(e){ne=null,ae=!1,Ue.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)Je({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!oe&&!ye)return{preTurnInitFailure:!0,message:t.message};(!ge&&!ye||oe)&&Je(oe?{kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}:{kind:"error",message:t.message||e?.message||"codex failed to start",recoverable:!0})}return{preTurnInitFailure:!1}}}function it(){if(se)return;const e=qe.shift();e&&(Le=Le.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);Ke({recoveryAttempt:t}),t>0&&Ge({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:n}),firstTurn:0===Z,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!se&&!oe&&!ye;){if(!(s?await st(e.text):await ot(o,e.options)))if(s)ge=!1,Je({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{ge&&(ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await at(o,e.options);if(r?.preTurnInitFailure){if(!(i||se||oe||ye)){i=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Je({kind:"error",message:r.message,recoverable:!0})}}if(!ge||a||ye||se||oe)break;ge=!1,a=!0,Ke({retry:!0,recoveryAttempt:t}),Ge({kind:"note",text:"retrying the stalled turn on a fresh connection"})}Z++,it()}))}return V&&!Y&&queueMicrotask(()=>Ve("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(K.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Me.enabled||queueMicrotask(()=>Ve(`Codex App Server disabled: ${Me.reason}`)),ke=le(()=>{const e=ie()-fe,t=v({turnActive:ae,awaitingUser:be,quietMs:e,stallMs:ce,forceStopMs:["max","ultra"].includes(Se)?pe:de,stalledSent:he,stallRetried:xe});if("none"===t)return;if("retry"===t&&!ve)return ye=!0,ge=!1,we>0?(Ge({kind:"error",recoverable:!0,message:"The safe recovery continuation also stopped responding. The original prompt was not replayed again. Switch to another model or provider, then continue from the current workspace state."}),Ge({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):Ue.size>0?Ge({kind:"error",recoverable:!0,message:"Codex stopped responding while a tool was still running. The transport was stopped, but no automatic continuation was started because that could overlap an uncertain side effect. Inspect the current state before continuing manually."}):(qe.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:ee++,forceFullReminder:!0}),Ge({kind:"error",recoverable:!0,message:"Codex stopped responding after the turn began. The transport was stopped. A new continuation turn will inspect the current state; the original prompt was not replayed."})),Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void nt({force:!0});const r=w(t,e);if(r&&Ge(r),"status"!==t){if("retry"===t)return xe=!0,ge=!0,fe=ie(),he=!1,void nt();ye=!0,ge=!1,qe.length=0,Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),nt({force:!0})}else he=!0},me),ke.unref?.(),{get sessionId(){return Q},get usageSnapshot(){return Ie},get turnActive(){return ae||Ae},get canSteer(){return!(!Ee||!1===Ee.alive||!Fe||Te)},get started(){return ae||Ae||!!ne||!(!Ee||!1===Ee.alive)},sendTurn(t,r={}){if(se)return!1;if(!ae&&0===qe.length&&!Fe&&!ne){const e="function"==typeof X?X():{ok:!0};if(!1===e?.ok)return He({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=ee++,o=te;if(te=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!ae&&0===Z&&U)try{e=U()||e}catch{}if(ae&&Ee&&Re){Xe();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:O,rolePrompt:A,roomContext:()=>re({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Re;return $e=$e.then(()=>Ee.steer({threadId:Q,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||1!==qe.length||it()):He({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return qe.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),ae||Ae||(oe=!1,Ke({recoveryAttempt:0})),1!==qe.length||Ae||it(),!0},async park(){if(se||ae||Ae||qe.length)return!1;const e=Le;try{await e}catch{}try{await $e}catch{}if(se||ae||Ae||qe.length||ne||Re)return!1;const t=Ee;if(t){Ne.add(t),Ee=null,Fe=!1,De.clear(),je.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Le,t=ae&&!Re&&!ne;oe=!0,ge=!1,ye=!1,qe.length=0,Ye(Re);let r=Promise.resolve();if(Ee&&Re&&(r=Ee.interrupt({threadId:Q,turnId:Re}).catch(()=>{})),ne){try{ne.kill("SIGTERM")}catch{}setTimeout(()=>{try{ne?.kill("SIGKILL")}catch{}},1500)}else t&&Je({kind:"result",subtype:"aborted",sessionId:Q,model:_e,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(se=!0,ae=!1,ke&&ue(ke),He.cancel(),qe.length=0,Ye(Re),ne)try{ne.kill()}catch{}try{Ee?.end()}catch{}Ce?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||ae)return!1;_e=String(e),ze.setModel(_e);const t=I(Se,{models:p,model:_e});return t!==Se&&(Se=t,He({kind:"effort",level:Se})),!0},setMode:e=>!(ae||!CODEX_MODE_CONFIG[e]||(B=e,W=codexConfigForMode(B),M=W.sandbox,Q=null,Z=0,Ie=null,Fe=!1,0)),setEffort:e=>!(ae||!S({models:p,model:_e}).includes(e)||(Se=e,He({kind:"effort",level:Se}),0)),clearContext:()=>!ae&&(Q=null,Z=0,Ie=null,Fe=!1,!0),async compactContext(){if(ae||Ae)return!1;oe=!1,Ae=!0;let e=null,t=!1;try{if(!await rt()||ae)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Oe=null,r(new Error("Codex did not start the compaction turn"))},5e3),Oe={resolve:t,reject:r}});await Ee.compact({threadId:Q}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ee.waitForTurn(n);Ye(o?.turn?.id||n);const s=o?.turn?.status;if("completed"===s)return!0;throw new Error(o?.turn?.error?.message||`compaction ${s||"failed"}`)}catch(e){return t||!["rejected","not_sent"].includes(e?.delivery)?(Ve(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(Ve(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Oe=null,Ae=!1,qe.length&&it()}},async accountUsage(){if(!await rt())return null;const[e,t]=await Promise.all([Ee.accountUsage().catch(()=>null),Ee.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.426",
|
|
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",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"turn-receipt.mjs",
|
|
17
17
|
"host-memory.mjs",
|
|
18
18
|
"host-capacity.mjs",
|
|
19
|
-
"host-auth.mjs",
|
|
20
19
|
"keep-awake.mjs",
|
|
21
20
|
"sdk-smoke.mjs",
|
|
22
21
|
"sdk-admission.mjs",
|
package/publish-manifest.json
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"package": "thinkpool-pair",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.426",
|
|
5
5
|
"kind": "minified",
|
|
6
|
-
"minifiedModules":
|
|
7
|
-
"sourceModuleBytes":
|
|
8
|
-
"artifactModuleBytes":
|
|
6
|
+
"minifiedModules": 142,
|
|
7
|
+
"sourceModuleBytes": 1876901,
|
|
8
|
+
"artifactModuleBytes": 857089,
|
|
9
9
|
"files": {
|
|
10
|
-
"package.json": "
|
|
10
|
+
"package.json": "e7ff9befab29f8ad72b0f2bdcf50dfc33683960eb1c9cc9a9192909738a2337a",
|
|
11
11
|
"LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
|
|
12
|
-
"bridge.mjs": "
|
|
12
|
+
"bridge.mjs": "a6d285aff7378d62484c9a5ddd63f5bda7ae3b790a7973bb4709a72ff9137550",
|
|
13
13
|
"bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
|
|
14
14
|
"command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
|
|
15
15
|
"abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
|
|
16
16
|
"turn-receipt.mjs": "4945f32699112aeb86bd2ca371e61c6534b7135b395e957697852cf4fef61989",
|
|
17
17
|
"host-memory.mjs": "44dab373e0a11b5f9ed1e5f1105643b51b1ce417781efb207a569d991865b4ae",
|
|
18
18
|
"host-capacity.mjs": "13f127951dc9d5adffc6b8da0617977e6c4aaefdf0e72d92c7fdb84ee8ad9a12",
|
|
19
|
-
"host-auth.mjs": "8f7ce9af058f7d844494c0f870b8d4b8f762d8c88ff6e49090a5e61431637cdd",
|
|
20
19
|
"keep-awake.mjs": "6c0a51409321f2564082b414b00696d22a740ca98180b1f628828c5fe19995c7",
|
|
21
20
|
"sdk-smoke.mjs": "dfe509863ede8a12780914939740f7f9fee36d3980f8fcb57803a33593ac4056",
|
|
22
21
|
"sdk-admission.mjs": "eb33725cc33347b900c75289eae7917f6ce3526203711515aecd48d43b3c895d",
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
"claude-session.mjs": "62af55078e681c94cff4847d7741049630211679c5dd46fee1b60c4811daf852",
|
|
30
29
|
"terminal-name.mjs": "f39bb8d1e148ab9a74cbb96438d31e524d6597b8035f00d7a906c28c306c0cf8",
|
|
31
30
|
"claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
|
|
32
|
-
"codex-session.mjs": "
|
|
31
|
+
"codex-session.mjs": "31684859879c1d796bef4c03b0d6d52e969403b9bcae25e656a2d5e159a83b1d",
|
|
33
32
|
"codex-host-heal.mjs": "14ea191ed2072203be2edd40a41272f176aafc044753919f65b1071ce48f060f",
|
|
34
33
|
"question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
|
|
35
34
|
"thinkpool-room-prompt.mjs": "b8cc82ccf4262f6431c2047bbff31ac29c47a0d6c68a64640a2c24d94df4a746",
|
|
@@ -89,7 +88,7 @@
|
|
|
89
88
|
"error-recovery.mjs": "0ace80add28fa72b49a3f3a716f247e3ef9816671c09749b3863867700beae32",
|
|
90
89
|
"pair-control-authority.mjs": "7a95f101153dcc6dd0633b5a01595bf2979cd0c4bf50cfc6cd44bb7612ef40a5",
|
|
91
90
|
"pair-access.mjs": "953d32528a08cde1bfeef58caafa0ce97274df8f0251d8a3be2bd190e382b5cc",
|
|
92
|
-
"scheduled-runs.mjs": "
|
|
91
|
+
"scheduled-runs.mjs": "3c776e316d87f8f3fd2513e992ac04030898fb70ed47fb49542cbe6ec0fea20f",
|
|
93
92
|
"scheduled-run-admission.mjs": "3a2098d904c3aff571bf314e0182d5636d25d242cef52207e70f9dc1618c94f5",
|
|
94
93
|
"event-id.mjs": "09c75e7fc8da282fc00651add58e5d2f82d3020e0c4dddb9921250cf9a6fa8c0",
|
|
95
94
|
"event-bounds.mjs": "aa8eb8517c4aa81b3378f71401ed39c550cdc80e28e3a57ab46d0a5ef5c7c9c1",
|
|
@@ -144,10 +143,10 @@
|
|
|
144
143
|
"terminal-row-reconcile.mjs": "7992fb6a1e854fb3a5fbb1978ef972f691ad2298803fdb416c3b8e83e5f2cdb4",
|
|
145
144
|
"terminal-close-auth.mjs": "ea3d3614a113c38e62a11902aa0d4dbb441e2869aa395cdb6e9b9649160d109c",
|
|
146
145
|
"switch-provider.mjs": "9805d59df49e1272ffd5ef7e3d2260f6e93c76d32265b78e461287ef500f5b71",
|
|
147
|
-
"service.mjs": "
|
|
146
|
+
"service.mjs": "f1bfabfbbb6dde5d28f8a623692d284b36a6883da3e8aad89bf6205fb4896b61",
|
|
148
147
|
"supervisor-ready.mjs": "7d5e4ebe3c7a93e0888dd335fb4556a670e55a80a89b861c8a44feac159cd840",
|
|
149
148
|
"presence.mjs": "2516ebb4474a8f45dd881530335c2174af9cde44be8eed6896ff8c62497b6e2b",
|
|
150
|
-
"account.mjs": "
|
|
149
|
+
"account.mjs": "baf751dfb696d85035f00214390148ad4f7418636ef3ea84e86197f09dedc33d",
|
|
151
150
|
"auth-store.mjs": "c4d41ee7cdc34c2002c6dd50d32e53ace0a3f03a116ffa930ed94a437e247625",
|
|
152
151
|
"key-shape.mjs": "a4113293d52eeba39b05e70a3e0345abc1ddc181b4f236b413bde0ae11a3f646",
|
|
153
152
|
"supabase-key.mjs": "041bde6cf50b15f6426daaf738374abc9647861d8c1dd3d85cedc45684271888",
|
package/scheduled-runs.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{SPAWN as t,spawnDecision as n}from"./cross-terminal.mjs";const i=new Set(["once","daily","weekly"]),r=new Set(["claude","codex","hermes"]),d=new Set(["delivered","needs_decision","failed","canceled"]),o=new Set(["artifact","preview","commit","test","flow_task","control_item"]),c=/(?:sk-[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9]{20,}|github_pat_[a-z0-9_]{20,}|xox[baprs]-[a-z0-9-]{10,}|AKIA[0-9A-Z]{16}|sbp_[a-z0-9]{20,}|eyJ[a-z0-9_-]{16,}\.[a-z0-9_-]{16,}\.[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,}|(?:token|secret|api[_-]?key|authorization)\s*=)/i,a=/(?:^|\s)(?:~\/|\/Users\/|\/home\/|\/private\/|\/tmp\/|[A-Za-z]:\\|\.\.[\\/])/i,u=/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,159}$/,s=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/,l=new Map,_=new Set([15,30,60]);export const scheduledRunsEnabled=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>"1"===e;export const scheduledRunsCapability=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>Object.freeze({enabled:scheduledRunsEnabled(e)});const p=(e,t)=>"string"==typeof e&&e.trim().length>0&&e.length<=t&&!c.test(e)&&!a.test(e),m=e=>e instanceof Date?e:new Date(e),f=e=>!Number.isNaN(m(e).getTime()),h=(e,t)=>{let n=l.get(e);n||(n=new Intl.DateTimeFormat("en-CA",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),l.set(e,n));const i=Object.fromEntries(n.formatToParts(t).filter(e=>"literal"!==e.type).map(e=>[e.type,e.value]));return{weekday:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(i.weekday),time:`${i.hour}:${i.minute}`}};export function scheduleValidationError(e={}){if(!e||"object"!=typeof e||!i.has(e.kind))return"invalid_kind";if(!p(e.title,120)||!p(e.prompt,12e3))return"unsafe_or_invalid_text";if(!r.has(e.runtime))return"invalid_runtime";if(!(null==e.provider||p(e.provider,80)&&u.test(e.provider)))return"invalid_provider";if(!(null==e.model||p(e.model,160)&&u.test(e.model)))return"invalid_model";if(!Number.isInteger(e.maxRuntimeMinutes)||e.maxRuntimeMinutes<1||e.maxRuntimeMinutes>30)return"invalid_max_runtime";try{new Intl.DateTimeFormat("en",{timeZone:e.timezone})}catch{return"invalid_timezone"}return"once"!==e.kind||f(e.at)?null==e.intervalMinutes||_.has(e.intervalMinutes)?"once"!==e.kind&&!/^\d{2}:\d{2}$/.test(e.time)||"once"!==e.kind&&(Number(e.time.slice(0,2))>23||Number(e.time.slice(3))>59)?"invalid_local_time":"weekly"===e.kind&&(!Number.isInteger(e.weekday)||e.weekday<0||e.weekday>6)?"invalid_weekday":null:"invalid_interval":"invalid_once_time"}export function normalizeSchedule(e={},t=new Date){const n=scheduleValidationError(e);if(n)return Object.freeze({ok:!1,code:n});const i={kind:e.kind,title:e.title.trim(),prompt:e.prompt.trim(),runtime:e.runtime,provider:e.provider||null,model:e.model||null,timezone:e.timezone,maxRuntimeMinutes:e.maxRuntimeMinutes,..."once"===e.kind?{at:m(e.at).toISOString()}:{time:e.time,..."weekly"===e.kind?{weekday:e.weekday}:{},...e.intervalMinutes?{interval_minutes:e.intervalMinutes}:{}}},r=nextScheduledAt(i,t);return r?Object.freeze({ok:!0,value:Object.freeze(i),nextRunAt:r.toISOString()}):Object.freeze({ok:!1,code:"schedule_exhausted"})}export function nextScheduledAt(e,t=new Date){if("once"===e?.kind){const n=m(e.at);return f(n)&&n.getTime()>m(t).getTime()?n:null}const n=Number(e?.interval_minutes);if(_.has(n))return new Date(m(t).getTime()+6e4*n);const i=6e4*Math.floor(m(t).getTime()/6e4)+6e4;for(let t=i,n=i+3456e7;t<n;t+=6e4){const n=h(e.timezone,new Date(t));if(n.time===e.time&&("daily"===e.kind||n.weekday===e.weekday))return new Date(t)}return null}export function classifyRunOutcome({result:e=null,evidenceRefs:t=[],pendingControls:n=[],runId:i=null,canceled:r=!1}={}){if("runtime_limit"===e?.subtype)return Object.freeze({outcome:"failed",code:"runtime_limit"});if(r||["aborted","canceled","cancelled","interrupted"].includes(e?.subtype))return Object.freeze({outcome:"canceled",code:"canceled"});const d=(Array.isArray(t)?t:[]).filter(e=>e&&o.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16);if(d.length)return Object.freeze({outcome:"delivered",code:"evidence_delivered",evidence:d});const c=(Array.isArray(n)?n:[]).find(e=>"pending"===e?.status&&e?.id&&e?.request_context?.run_id&&(!i||e.request_context.run_id===i)&&Number.isSafeInteger(Number(e.source_event_from_seq)));return c?Object.freeze({outcome:"needs_decision",code:"pending_pair_control",controlItemId:c.id}):"success"===e?.subtype?Object.freeze({outcome:"failed",code:"missing_deliverable"}):Object.freeze({outcome:"failed",code:e?.subtype||"runtime_failed"})}export function sanitizeScheduledOutcomeIntent({runId:e,result:t=null,evidenceRefs:n=[],canceled:i=!1}={}){if("string"!=typeof e||!u.test(e))return null;const r="string"==typeof t?.subtype&&s.test(t.subtype)?t.subtype:"runtime_failed";return Object.freeze({runId:e,result:Object.freeze({subtype:r}),evidenceRefs:Object.freeze((d=n,(Array.isArray(d)?d:[]).filter(e=>e&&o.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16).map(e=>Object.freeze({type:e.type,id:e.id})))),canceled:!0===i});var d}export function scheduledRunDeadlineRequired({runId:e,deadlineAt:t,outcomeRecorded:n=!1,outcomePending:i=null}={}){return Boolean(e&&Number.isFinite(t)&&!n&&!i)}export function scheduledOutcomeReconciliationComplete(e=[]){return Array.isArray(e)&&0===e.length}export function scheduledRunSpawnAdmission({spawnTimes:e=[],now:i=Date.now(),totalLive:r=0,disabled:d=!1}={},o=t){const c={...t,...o||{}},a=(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>i-c.windowMs).slice(-(c.perWindowCap||1)),u=n({hop:0,spawnTimes:a,now:i,spawnedLive:0,totalLive:r,plan:"free",disabled:d},c);return Object.freeze({...u,spawnTimes:Object.freeze(u.ok?[...a,i].slice(-c.perWindowCap):a)})}export function preflightScheduledRun({schedule:e,occurrence:t,ownerId:n,bridgeId:i,bridgeFencing:r,providerAvailable:d,capsOk:o=!0,now:c=new Date}={}){return e?.enabled?t?.id&&t.schedule_revision===e.revision?!n||!i||!Number.isSafeInteger(r)||r<1||t.owner_id!==n||t.bridge_id!==i||t.bridge_fencing!==r?Object.freeze({ok:!1,code:"owner_bridge_mismatch"}):o?"function"==typeof d&&d(e)?!t.lease_expires_at||m(t.lease_expires_at).getTime()<=m(c).getTime()?Object.freeze({ok:!1,code:"lease_expired"}):Object.freeze({ok:!0,code:"admitted"}):Object.freeze({ok:!1,code:"provider_unavailable"}):Object.freeze({ok:!1,code:"runtime_cap_exceeded"}):Object.freeze({ok:!1,code:"stale_schedule_revision"}):Object.freeze({ok:!1,code:"schedule_disabled"})}const b=async({fetchImpl:e,supabaseUrl:t,headers:n,name:i,body:r})=>{const d=await e(`${String(t).replace(/\/$/,"")}/rest/v1/rpc/${i}`,{method:"POST",headers:n,body:JSON.stringify(r)});if(!d?.ok)throw new Error(`${i}_failed`);return d.json()};export async function pollDueScheduledRuns({enabled:t=!1,fetchImpl:n=globalThis.fetch,supabaseUrl:i,anonKey:r,token:d,roomCode:o,bridgeId:c,bridgeFencing:a,ownerId:s,providerAvailable:l,openTerminal:_,admitRun:p,releaseRun:f,makeTerminalId:h=e,capsOk:y=()=>!0,now:g=new Date}={}){if(!t)return Object.freeze({ok:!1,code:"feature_disabled",runs:[]});if(!("function"==typeof n&&i&&r&&d&&o&&c&&Number.isSafeInteger(a))||a<1||!s||"function"!=typeof _||"function"!=typeof p||"function"!=typeof f||"function"!=typeof h)return Object.freeze({ok:!1,code:"runner_unavailable",runs:[]});const w={apikey:r,Authorization:`Bearer ${d}`,"Content-Type":"application/json"},k=String(o).trim().toUpperCase();try{const e=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"expire_code_scheduled_runs",body:{p_room_code:k,p_bridge_id:c,p_bridge_fencing:a,p_limit:24}});if(!e?.ok)throw new Error("expire_code_scheduled_runs_failed");const t=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"list_due_code_schedules",body:{p_room_code:k,p_limit:12}}),r=[];for(const e of Array.isArray(t)?t:[]){if(String(e?.session_code||"").trim().toUpperCase()!==k){r.push({scheduleId:e?.id,code:"room_scope_mismatch"});continue}if(!y(e)){r.push({scheduleId:e.id,code:"runtime_cap_exceeded"});continue}if("function"!=typeof l||!l(e)){r.push({scheduleId:e.id,code:"provider_unavailable"});continue}const t=h();if("string"!=typeof t||!u.test(t)){r.push({scheduleId:e.id,code:"terminal_id_unavailable"});continue}const d={...e.schedule_spec||{},kind:e.schedule_kind||e.kind,timezone:e.timezone},o="once"===d.kind?new Date("9999-12-31T23:59:00.000Z"):nextScheduledAt(d,new Date(e.next_run_at));if(!o){r.push({scheduleId:e.id,code:"next_run_invalid"});continue}const v=`${e.id}:${m(e.next_run_at).toISOString()}`,z=Math.min(1860,Math.max(60,60*e.max_runtime_minutes+60)),I=m(g).getTime()+1e3*z;let x=null;try{x=await p({schedule:e,runId:v,terminalId:t,deadlineAt:I})}catch{x=null}if(!x?.ok){r.push({scheduleId:e.id,code:x?.code||"spawn_not_admitted"});continue}let O=!1;try{const d=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"claim_due_code_schedule",body:{p_schedule_id:e.id,p_expected_revision:e.revision,p_bridge_id:c,p_bridge_fencing:a,p_occurrence_key:v,p_lease_seconds:z}});if(!d?.ok||!d?.run){r.push({scheduleId:e.id,code:d?.code||"claim_failed"});continue}const u=preflightScheduledRun({schedule:e,occurrence:d.run,ownerId:s,bridgeId:c,bridgeFencing:a,providerAvailable:l,capsOk:!0,now:g});if(!u.ok){r.push({scheduleId:e.id,runId:d.run.id,code:u.code});continue}const p=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"open_code_scheduled_run",body:{p_run_id:d.run.id,p_bridge_id:c,p_bridge_fencing:a,p_terminal_id:t,p_next_run_at:o.toISOString()}});if(!p?.ok){r.push({scheduleId:e.id,runId:d.run.id,code:p?.code||"open_not_persisted"});continue}const m=p.run||d.run;let f=null;try{f=await _({schedule:e,run:m,terminalId:t,admission:x.lease})}catch{f=null}if(f){O=!0,r.push({scheduleId:e.id,runId:d.run.id,code:"opened"});continue}const h=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"finish_code_scheduled_run",body:{p_run_id:d.run.id,p_bridge_id:c,p_bridge_fencing:a,p_outcome:"failed",p_outcome_code:"terminal_open_failed",p_evidence_refs:[],p_decision_item_id:null}});r.push({scheduleId:e.id,runId:d.run.id,code:h?.ok?"open_failed":h?.code||"open_failed_unrecorded"})}finally{if(!O)try{await f(x.lease)}catch{}}}return Object.freeze({ok:!0,code:"polled",runs:Object.freeze(r)})}catch{return Object.freeze({ok:!1,code:"runner_read_failed",runs:[]})}}export async function finishScheduledRun({fetchImpl:e=globalThis.fetch,supabaseUrl:t,anonKey:n,token:i,roomCode:r,runId:d,bridgeId:o,bridgeFencing:c,result:a,evidenceRefs:u,canceled:s=!1}={}){if(!(t&&n&&i&&r&&d&&o&&Number.isSafeInteger(c))||c<1)return Object.freeze({ok:!1,code:"outcome_unavailable"});const l={apikey:n,Authorization:`Bearer ${i}`,"Content-Type":"application/json"};try{const n=classifyRunOutcome({result:a,evidenceRefs:u,pendingControls:await b({fetchImpl:e,supabaseUrl:t,headers:l,name:"list_code_pair_controls",body:{p_session_code:String(r).toUpperCase(),p_status:"pending",p_limit:100}}),runId:d,canceled:s}),i=await b({fetchImpl:e,supabaseUrl:t,headers:l,name:"finish_code_scheduled_run",body:{p_run_id:d,p_outcome:n.outcome,p_outcome_code:n.code,p_bridge_id:o,p_bridge_fencing:c,p_evidence_refs:n.evidence||[],p_decision_item_id:n.controlItemId||null}});return Object.freeze({ok:Boolean(i?.ok),code:i?.code||"outcome_write_failed",outcome:n})}catch{return Object.freeze({ok:!1,code:"outcome_write_failed"})}}export async function finishScheduledRunWithRetry(e={},{attempts:t=3,delays:n=[100,400],sleep:i=e=>new Promise(t=>setTimeout(t,e))}={}){const r=Math.max(1,Math.min(5,Number.isInteger(t)?t:3));let d=Object.freeze({ok:!1,code:"outcome_write_failed"});for(let t=0;t<r;t++){if(d=await finishScheduledRun(e),d.ok)return d;t+1<r&&await i(Math.max(0,Math.min(5e3,Number(n[t]??n.at(-1)??0)||0)))}return d}export{d as OUTCOMES};
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{SPAWN as t,spawnDecision as n}from"./cross-terminal.mjs";const i=new Set(["once","daily","weekly"]),r=new Set(["claude","codex","hermes"]),o=new Set(["delivered","needs_decision","failed","canceled"]),d=new Set(["artifact","preview","commit","test","flow_task","control_item"]),a=/(?:sk-[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9]{20,}|github_pat_[a-z0-9_]{20,}|xox[baprs]-[a-z0-9-]{10,}|AKIA[0-9A-Z]{16}|sbp_[a-z0-9]{20,}|eyJ[a-z0-9_-]{16,}\.[a-z0-9_-]{16,}\.[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{16,}|(?:token|secret|api[_-]?key|authorization)\s*=)/i,c=/(?:^|\s)(?:~\/|\/Users\/|\/home\/|\/private\/|\/tmp\/|[A-Za-z]:\\|\.\.[\\/])/i,u=/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,159}$/,l=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/,s=new Map,_=new Set([15,30,60]);export const scheduledRunsEnabled=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>"1"===e;export const scheduledRunsCapability=(e=process.env.TP_SCHEDULED_RUNS_ENABLED)=>Object.freeze({enabled:scheduledRunsEnabled(e)});export async function admitScheduledRunExecution({terminalId:e,localAdmission:t,memoryAdmission:n,acquireHost:i,acquireScheduled:r,releaseHost:o}={}){if("string"!=typeof e||!u.test(e))return{ok:!1,code:"terminal_id_unavailable"};if("function"!=typeof t||"function"!=typeof n||"function"!=typeof i||"function"!=typeof r||"function"!=typeof o)return{ok:!1,code:"spawn_admission_unavailable"};let d=null;try{d=await t()}catch{d=null}if(!d?.ok)return d||{ok:!1,code:"spawn_not_admitted"};let a=null;try{a=await n()}catch{a=null}if(!a?.ok)return a||{ok:!1,code:"host_memory_unavailable"};let c=null;try{c=await i()}catch{c=null}if(!c?.ok)return c||{ok:!1,code:"capacity_authority_unavailable"};let l=null;try{l=await r()}catch{l=null}if(!l?.ok||!l.lease){try{await o(e)}catch{}return l||{ok:!1,code:"scheduled_admission_unavailable"}}return{ok:!0,code:"admitted",lease:{terminalId:e,scheduled:l.lease}}}export async function releaseScheduledRunExecution(e,{releaseScheduled:t,releaseHost:n}={}){if(!e?.scheduled||"string"!=typeof e.terminalId||"function"!=typeof t||"function"!=typeof n)return{ok:!1,code:"invalid_execution_lease"};let i=null;try{i=await t(e.scheduled)}catch{i=null}try{await n(e.terminalId)}catch{}return i||{ok:!1,code:"scheduled_release_failed"}}const p=(e,t)=>"string"==typeof e&&e.trim().length>0&&e.length<=t&&!a.test(e)&&!c.test(e),m=e=>e instanceof Date?e:new Date(e),f=e=>!Number.isNaN(m(e).getTime()),h=(e,t)=>{let n=s.get(e);n||(n=new Intl.DateTimeFormat("en-CA",{timeZone:e,weekday:"short",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}),s.set(e,n));const i=Object.fromEntries(n.formatToParts(t).filter(e=>"literal"!==e.type).map(e=>[e.type,e.value]));return{weekday:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(i.weekday),time:`${i.hour}:${i.minute}`}};export function scheduleValidationError(e={}){if(!e||"object"!=typeof e||!i.has(e.kind))return"invalid_kind";if(!p(e.title,120)||!p(e.prompt,12e3))return"unsafe_or_invalid_text";if(!r.has(e.runtime))return"invalid_runtime";if(!(null==e.provider||p(e.provider,80)&&u.test(e.provider)))return"invalid_provider";if(!(null==e.model||p(e.model,160)&&u.test(e.model)))return"invalid_model";if(!Number.isInteger(e.maxRuntimeMinutes)||e.maxRuntimeMinutes<1||e.maxRuntimeMinutes>30)return"invalid_max_runtime";try{new Intl.DateTimeFormat("en",{timeZone:e.timezone})}catch{return"invalid_timezone"}return"once"!==e.kind||f(e.at)?null==e.intervalMinutes||_.has(e.intervalMinutes)?"once"!==e.kind&&!/^\d{2}:\d{2}$/.test(e.time)||"once"!==e.kind&&(Number(e.time.slice(0,2))>23||Number(e.time.slice(3))>59)?"invalid_local_time":"weekly"===e.kind&&(!Number.isInteger(e.weekday)||e.weekday<0||e.weekday>6)?"invalid_weekday":null:"invalid_interval":"invalid_once_time"}export function normalizeSchedule(e={},t=new Date){const n=scheduleValidationError(e);if(n)return Object.freeze({ok:!1,code:n});const i={kind:e.kind,title:e.title.trim(),prompt:e.prompt.trim(),runtime:e.runtime,provider:e.provider||null,model:e.model||null,timezone:e.timezone,maxRuntimeMinutes:e.maxRuntimeMinutes,..."once"===e.kind?{at:m(e.at).toISOString()}:{time:e.time,..."weekly"===e.kind?{weekday:e.weekday}:{},...e.intervalMinutes?{interval_minutes:e.intervalMinutes}:{}}},r=nextScheduledAt(i,t);return r?Object.freeze({ok:!0,value:Object.freeze(i),nextRunAt:r.toISOString()}):Object.freeze({ok:!1,code:"schedule_exhausted"})}export function nextScheduledAt(e,t=new Date){if("once"===e?.kind){const n=m(e.at);return f(n)&&n.getTime()>m(t).getTime()?n:null}const n=Number(e?.interval_minutes);if(_.has(n))return new Date(m(t).getTime()+6e4*n);const i=6e4*Math.floor(m(t).getTime()/6e4)+6e4;for(let t=i,n=i+3456e7;t<n;t+=6e4){const n=h(e.timezone,new Date(t));if(n.time===e.time&&("daily"===e.kind||n.weekday===e.weekday))return new Date(t)}return null}export function classifyRunOutcome({result:e=null,evidenceRefs:t=[],pendingControls:n=[],runId:i=null,canceled:r=!1}={}){if("runtime_limit"===e?.subtype)return Object.freeze({outcome:"failed",code:"runtime_limit"});if(r||["aborted","canceled","cancelled","interrupted"].includes(e?.subtype))return Object.freeze({outcome:"canceled",code:"canceled"});const o=(Array.isArray(t)?t:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16);if(o.length)return Object.freeze({outcome:"delivered",code:"evidence_delivered",evidence:o});const a=(Array.isArray(n)?n:[]).find(e=>"pending"===e?.status&&e?.id&&e?.request_context?.run_id&&(!i||e.request_context.run_id===i)&&Number.isSafeInteger(Number(e.source_event_from_seq)));return a?Object.freeze({outcome:"needs_decision",code:"pending_pair_control",controlItemId:a.id}):"success"===e?.subtype?Object.freeze({outcome:"failed",code:"missing_deliverable"}):Object.freeze({outcome:"failed",code:e?.subtype||"runtime_failed"})}export function sanitizeScheduledOutcomeIntent({runId:e,result:t=null,evidenceRefs:n=[],canceled:i=!1}={}){if("string"!=typeof e||!u.test(e))return null;const r="string"==typeof t?.subtype&&l.test(t.subtype)?t.subtype:"runtime_failed";return Object.freeze({runId:e,result:Object.freeze({subtype:r}),evidenceRefs:Object.freeze((o=n,(Array.isArray(o)?o:[]).filter(e=>e&&d.has(e.type)&&"string"==typeof e.id&&u.test(e.id)).slice(0,16).map(e=>Object.freeze({type:e.type,id:e.id})))),canceled:!0===i});var o}export function scheduledRunDeadlineRequired({runId:e,deadlineAt:t,outcomeRecorded:n=!1,outcomePending:i=null}={}){return Boolean(e&&Number.isFinite(t)&&!n&&!i)}export function scheduledOutcomeReconciliationComplete(e=[]){return Array.isArray(e)&&0===e.length}export function scheduledRunSpawnAdmission({spawnTimes:e=[],now:i=Date.now(),totalLive:r=0,disabled:o=!1}={},d=t){const a={...t,...d||{}},c=(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>i-a.windowMs).slice(-(a.perWindowCap||1)),u=n({hop:0,spawnTimes:c,now:i,spawnedLive:0,totalLive:r,plan:"free",disabled:o},a);return Object.freeze({...u,spawnTimes:Object.freeze(u.ok?[...c,i].slice(-a.perWindowCap):c)})}export function preflightScheduledRun({schedule:e,occurrence:t,ownerId:n,bridgeId:i,bridgeFencing:r,providerAvailable:o,capsOk:d=!0,now:a=new Date}={}){return e?.enabled?t?.id&&t.schedule_revision===e.revision?!n||!i||!Number.isSafeInteger(r)||r<1||t.owner_id!==n||t.bridge_id!==i||t.bridge_fencing!==r?Object.freeze({ok:!1,code:"owner_bridge_mismatch"}):d?"function"==typeof o&&o(e)?!t.lease_expires_at||m(t.lease_expires_at).getTime()<=m(a).getTime()?Object.freeze({ok:!1,code:"lease_expired"}):Object.freeze({ok:!0,code:"admitted"}):Object.freeze({ok:!1,code:"provider_unavailable"}):Object.freeze({ok:!1,code:"runtime_cap_exceeded"}):Object.freeze({ok:!1,code:"stale_schedule_revision"}):Object.freeze({ok:!1,code:"schedule_disabled"})}const b=async({fetchImpl:e,supabaseUrl:t,headers:n,name:i,body:r})=>{const o=await e(`${String(t).replace(/\/$/,"")}/rest/v1/rpc/${i}`,{method:"POST",headers:n,body:JSON.stringify(r)});if(!o?.ok)throw new Error(`${i}_failed`);return o.json()};export async function pollDueScheduledRuns({enabled:t=!1,fetchImpl:n=globalThis.fetch,supabaseUrl:i,anonKey:r,token:o,roomCode:d,bridgeId:a,bridgeFencing:c,ownerId:l,providerAvailable:s,openTerminal:_,admitRun:p,releaseRun:f,makeTerminalId:h=e,capsOk:y=()=>!0,now:k=new Date}={}){if(!t)return Object.freeze({ok:!1,code:"feature_disabled",runs:[]});if(!("function"==typeof n&&i&&r&&o&&d&&a&&Number.isSafeInteger(c))||c<1||!l||"function"!=typeof _||"function"!=typeof p||"function"!=typeof f||"function"!=typeof h)return Object.freeze({ok:!1,code:"runner_unavailable",runs:[]});const w={apikey:r,Authorization:`Bearer ${o}`,"Content-Type":"application/json"},g=String(d).trim().toUpperCase();try{const e=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"expire_code_scheduled_runs",body:{p_room_code:g,p_bridge_id:a,p_bridge_fencing:c,p_limit:24}});if(!e?.ok)throw new Error("expire_code_scheduled_runs_failed");const t=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"list_due_code_schedules",body:{p_room_code:g,p_limit:12}}),r=[];for(const e of Array.isArray(t)?t:[]){if(String(e?.session_code||"").trim().toUpperCase()!==g){r.push({scheduleId:e?.id,code:"room_scope_mismatch"});continue}if(!y(e)){r.push({scheduleId:e.id,code:"runtime_cap_exceeded"});continue}if("function"!=typeof s||!s(e)){r.push({scheduleId:e.id,code:"provider_unavailable"});continue}const t=h();if("string"!=typeof t||!u.test(t)){r.push({scheduleId:e.id,code:"terminal_id_unavailable"});continue}const o={...e.schedule_spec||{},kind:e.schedule_kind||e.kind,timezone:e.timezone},d="once"===o.kind?new Date("9999-12-31T23:59:00.000Z"):nextScheduledAt(o,new Date(e.next_run_at));if(!d){r.push({scheduleId:e.id,code:"next_run_invalid"});continue}const v=`${e.id}:${m(e.next_run_at).toISOString()}`,I=Math.min(1860,Math.max(60,60*e.max_runtime_minutes+60)),z=m(k).getTime()+1e3*I;let x=null;try{x=await p({schedule:e,runId:v,terminalId:t,deadlineAt:z})}catch{x=null}if(!x?.ok){r.push({scheduleId:e.id,code:x?.code||"spawn_not_admitted"});continue}let O=!1;try{const o=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"claim_due_code_schedule",body:{p_schedule_id:e.id,p_expected_revision:e.revision,p_bridge_id:a,p_bridge_fencing:c,p_occurrence_key:v,p_lease_seconds:I}});if(!o?.ok||!o?.run){r.push({scheduleId:e.id,code:o?.code||"claim_failed"});continue}const u=preflightScheduledRun({schedule:e,occurrence:o.run,ownerId:l,bridgeId:a,bridgeFencing:c,providerAvailable:s,capsOk:!0,now:k});if(!u.ok){r.push({scheduleId:e.id,runId:o.run.id,code:u.code});continue}const p=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"open_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_terminal_id:t,p_next_run_at:d.toISOString()}});if(!p?.ok){r.push({scheduleId:e.id,runId:o.run.id,code:p?.code||"open_not_persisted"});continue}const m=p.run||o.run;let f=null;try{f=await _({schedule:e,run:m,terminalId:t,admission:x.lease})}catch{f=null}if(f){O=!0,r.push({scheduleId:e.id,runId:o.run.id,code:"opened"});continue}const h=await b({fetchImpl:n,supabaseUrl:i,headers:w,name:"finish_code_scheduled_run",body:{p_run_id:o.run.id,p_bridge_id:a,p_bridge_fencing:c,p_outcome:"failed",p_outcome_code:"terminal_open_failed",p_evidence_refs:[],p_decision_item_id:null}});r.push({scheduleId:e.id,runId:o.run.id,code:h?.ok?"open_failed":h?.code||"open_failed_unrecorded"})}finally{if(!O)try{await f(x.lease)}catch{}}}return Object.freeze({ok:!0,code:"polled",runs:Object.freeze(r)})}catch{return Object.freeze({ok:!1,code:"runner_read_failed",runs:[]})}}export async function finishScheduledRun({fetchImpl:e=globalThis.fetch,supabaseUrl:t,anonKey:n,token:i,roomCode:r,runId:o,bridgeId:d,bridgeFencing:a,result:c,evidenceRefs:u,canceled:l=!1}={}){if(!(t&&n&&i&&r&&o&&d&&Number.isSafeInteger(a))||a<1)return Object.freeze({ok:!1,code:"outcome_unavailable"});const s={apikey:n,Authorization:`Bearer ${i}`,"Content-Type":"application/json"};try{const n=classifyRunOutcome({result:c,evidenceRefs:u,pendingControls:await b({fetchImpl:e,supabaseUrl:t,headers:s,name:"list_code_pair_controls",body:{p_session_code:String(r).toUpperCase(),p_status:"pending",p_limit:100}}),runId:o,canceled:l}),i=await b({fetchImpl:e,supabaseUrl:t,headers:s,name:"finish_code_scheduled_run",body:{p_run_id:o,p_outcome:n.outcome,p_outcome_code:n.code,p_bridge_id:d,p_bridge_fencing:a,p_evidence_refs:n.evidence||[],p_decision_item_id:n.controlItemId||null}});return Object.freeze({ok:Boolean(i?.ok),code:i?.code||"outcome_write_failed",outcome:n})}catch{return Object.freeze({ok:!1,code:"outcome_write_failed"})}}export async function finishScheduledRunWithRetry(e={},{attempts:t=3,delays:n=[100,400],sleep:i=e=>new Promise(t=>setTimeout(t,e))}={}){const r=Math.max(1,Math.min(5,Number.isInteger(t)?t:3));let o=Object.freeze({ok:!1,code:"outcome_write_failed"});for(let t=0;t<r;t++){if(o=await finishScheduledRun(e),o.ok)return o;t+1<r&&await i(Math.max(0,Math.min(5e3,Number(n[t]??n.at(-1)??0)||0)))}return o}export{o as OUTCOMES};
|
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 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,"&").replace(/</g,"<").replace(/>/g,">"),$=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 c=r.join(a,String(o)),d=r.join(c,"node_modules","thinkpool-pair","bridge.mjs");if(t.existsSync(d)&&g(d)===String(o))return d;const p=l("stage a bridge runtime update");if(!p?.ok)throw new Error(p?.reason||"host memory is critically low");const u=`${c}.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(c,{recursive:!0,force:!0}),t.renameSync(u,c)}catch(e){throw t.rmSync(u,{recursive:!0,force:!0}),e}return d}export function buildArtifact(n,{room:i=null,cmdArgs:o=[],autoUpdate:s=!1,version:a=m,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(),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",P=s?"thinkpool-pair@latest":`thinkpool-pair@${a}`,j=s||l?["--prefer-online"]:[],E=i?["-y",...j,P,i]:["-y",...j,P],F=!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=${$(_)}; exec ${[v,...E,...x].map($).join(" ")}`]:[v,...E,...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>${d?"":`<key>NPM_CONFIG_CACHE</key><string>${f(_)}</string>`}${s?"<key>THINKPOOL_PAIR_AUTOUPDATE</key><string>1</string>":""}${F.darwin}</dict>\n</dict></plist>\n`,logDir:b,post:[],expectedRuntime:d||P,note:"Loaded as a LaunchAgent — starts at login, restarts on crash."}}if("linux"===n){const t=d?[process.execPath,d,...i?[i]:[],...x]:[v,...E,...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=${d?t.map(e=>`'${e.replace(/'/g,"'\\''")}'`).join(" "):l?`/bin/bash -lc "${h(_)}; export NPM_CONFIG_CACHE=${$(_)}; exec ${[v,...E,...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${d?"":`Environment=NPM_CONFIG_CACHE=${_}\n`}${s?"Environment=THINKPOOL_PAIR_AUTOUPDATE=1\n":""}${F.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:d||P,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=d?[process.execPath,d,...i?[i,"--supervise"]:[],...x].map(e=>`"${e}"`).join(" "):i?["npx","-y",...j,P,i,"--supervise",...x].join(" "):["npx","-y",...j,P].join(" ");return{file:n,content:`@echo off\r\ntitle thinkpool-pair ${k}\r\n${d?"":`set "NPM_CONFIG_CACHE=${_}"\r\n`}${F.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 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)}`,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"),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=${$(d)}`,`helper_file=${$(c)}`,`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: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: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)}`,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=${$(t)}`,'unit="$target.service"',`target_file=${$(n)}`,`staged_file=${$(i)}`,`backup_file=${$(c)}`,`expected=${$(o)}`,`version=${$(s)}`,`status=${$(d)}`,`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: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(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(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 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 d=null;if(!o)try{d=provisionRuntime(s)}catch(e){return process.stderr.write(` ⚠ runtime install failed; service left unchanged: ${e.message}\n`),!1}const f=buildArtifact(process.platform,{room:e,cmdArgs:i,autoUpdate:o,version:s,staleProof:c,runtimeEntry:d});try{sweepExistingServices(p(e),process.platform)}catch{}if(t.mkdirSync(f.logDir,{recursive:!0}),t.mkdirSync(r.dirname(f.file),{recursive:!0}),"darwin"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${f.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}}(f,s))return!1}else if("linux"===process.platform){if(process.stderr.write(`\n ◆ staged replacement for ${f.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}}(f,s))return!1}else{t.writeFileSync(f.file,f.content),process.stderr.write(`\n ◆ wrote ${f.file}\n`);for(const e of f.post)try{n(e,{stdio:"inherit"})}catch(t){return process.stderr.write(` ⚠ "${e}" failed; service update NOT confirmed: ${t.message}\n`),!1}}const h=Math.max(10,parseInt(process.env.THINKPOOL_PAIR_UPDATE_IDLE,10)||90),g=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 (≥${h}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.`,v=e?` ${e}`:"",y=e?`room ${e}`:"your account (auto-serves every session)",b=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(` ◆ ${y}\n ◆ ${"darwin"===process.platform?"Launchd is completing and verifying the reload independently.":f.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${b} ◆ ${g}\n ◆ logs: ${r.join(f.logDir,`${u(e)}.log`)}\n ◆ remove with: ${a("uninstall-service")}${v}\n\n`),!0}export async function installAndConfirmService(i,a=[],{platform:l=process.platform,version:c=m,staleProof:d=!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:c,staleProof:d}))return!1;const A=()=>{const e=$(i,{platform:l,exec:f}),t=!!i||"win32"===l||s(g(),e,c,{notBefore:x});return!(e?.version!==c||!t||(S.write(` ✓ bridge install confirmed — running v${c}.\n`),0))};if("win32"===l)return!0;const P=y()+Math.max(0,Number(b)||0),j=p(i);for(;;){const e=v();if(e&&"object"==typeof e&&e.target===j&&e.version===c){if(!1===e.ok)return S.write(` ⚠ install v${c} was not confirmed: ${e.error||"the launchd handoff failed"}.\n`),!1;if(!0===e.ok&&A())return!0}if(y()>=P)return S.write(` ⚠ install v${c} 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:c=updateService,install:d=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===c(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:d,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),P=p(i);let j=null;for(;;){const e=h();if(e&&"object"==typeof e&&e.target===P){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&&(j=_(),j.ok))return!0}if(m()>=A)return j?x(j):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 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,"&").replace(/</g,"<").replace(/>/g,">"),$=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`)}}
|
package/host-auth.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e from"node:fs";import r from"node:os";import t from"node:path";import{randomUUID as o}from"node:crypto";export const HOST_AUTH_TTL_MS=9e5;export const HOST_AUTH_SERVICES=Object.freeze([{id:"github",name:"GitHub"},{id:"vercel",name:"Vercel"},{id:"supabase",name:"Supabase"}]);const n=Object.freeze({vercel:"53.3.1",supabase:"2.116.0"}),i=new Set(["connected","failed","cancelled","expired"]);export function stripAnsi(e){return String(e||"").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g,"").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g,"").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g,"")}export function parseHostAuthOutput(e,r,t){const o=stripAnsi(`${r||""}${t||""}`).slice(-16e3),n=(o.match(/https:\/\/[^\s]+/g)||[]).map(r=>function(e,r){try{const t=new URL(String(r||"").replace(/[),.;]+$/,""));if("https:"!==t.protocol)return null;if("github"===e&&"github.com"===t.hostname&&"/login/device"===t.pathname)return t.toString();if("vercel"===e&&"vercel.com"===t.hostname&&"/oauth/device"===t.pathname)return t.toString();if("supabase"===e&&"supabase.com"===t.hostname&&"/dashboard/cli/login"===t.pathname)return t.toString()}catch{}return null}(e,r)).find(Boolean)||null;let i=null;if("vercel"===e&&n)try{i=new URL(n).searchParams.get("user_code")}catch{}i||"github"!==e||(i=o.match(/(?:one-time\s+(?:OAuth\s+)?code|code)\s*:?\s*([A-Z0-9]{4}-[A-Z0-9]{4})/i)?.[1]||null);const s="supabase"===e&&/enter your verification code/i.test(o);return{buffer:o,verificationUri:n,userCode:i?String(i).toUpperCase():null,needsInput:s}}function s(r,o=process.env){const n=String(o.PATH||"").split(t.delimiter).filter(Boolean),i="win32"===process.platform?["",".exe",".cmd",".bat"]:[""];return n.some(o=>i.some(n=>{try{return e.accessSync(t.join(o,`${r}${n}`),e.constants.X_OK),!0}catch{return!1}}))}export function resolveHostAuthCommand(e,{env:r=process.env,has:t=s}={}){return HOST_AUTH_SERVICES.some(r=>r.id===e)?"github"===e?t("gh",r)?{command:"gh",args:["auth","login","--hostname","github.com","--web","--git-protocol","https","--skip-ssh-key"]}:null:"vercel"===e&&t("vercel",r)?{command:"vercel",args:["login"]}:"supabase"===e&&t("supabase",r)?{command:"supabase",args:["login","--no-browser"]}:t("npx",r)?"vercel"===e?{command:"npx",args:["--yes",`vercel@${n.vercel}`,"login"]}:"supabase"===e?{command:"npx",args:["--yes",`supabase@${n.supabase}`,"login","--no-browser"]}:null:null:null}function a(e=process.env){const r={...e,NO_COLOR:"1",TERM:"xterm-256color",GH_BROWSER:"false",BROWSER:"false"};for(const e of Object.keys(r))(/^(?:CODEX|CLAUDE|CI$)/.test(e)||/^(?:GH_TOKEN|GITHUB_TOKEN|VERCEL_TOKEN|SUPABASE_ACCESS_TOKEN)$/.test(e))&&delete r[e];return r}function c(e){return e?{flowId:e.flowId,service:e.service,phase:e.phase,verificationUri:e.verificationUri||null,userCode:e.userCode||null,needsInput:!0===e.needsInput,expiresAt:e.expiresAt,error:e.error||null}:null}async function u(){const e=await import("node-pty");return e.default||e}export function createHostAuthManager({loadPty:e=u,resolveCommand:t=resolveHostAuthCommand,now:n=()=>Date.now(),setTimer:s=setTimeout,clearTimer:l=clearTimeout,env:p=process.env,cwd:d=r.homedir()}={}){let h=null;const f=()=>{if(h&&!i.has(h.phase)){h.phase="expired",h.error="This login expired. Start a new one.",h.cancelRequested=!0;try{h.proc?.kill()}catch{}}};return{start:async r=>{if(!HOST_AUTH_SERVICES.some(e=>e.id===r))return{ok:!1,error:"Unsupported host account."};if(h&&!i.has(h.phase))return{ok:!1,error:"Finish or cancel the current login first.",flow:c(h)};const u=t(r,{env:p});if(!u)return{ok:!1,error:"github"===r?"GitHub CLI is not installed on this bridge.":"Neither this provider CLI nor npx is available on this bridge."};let m;try{m=await e()}catch{return{ok:!1,error:"Remote login needs PTY support. Update or reinstall this bridge."}}const g=n();h={flowId:o(),service:r,phase:"starting",startedAt:g,expiresAt:g+9e5,buffer:"",verificationUri:null,userCode:null,needsInput:!1,error:null,cancelRequested:!1,proc:null,timer:null,githubGitConfirmed:!1,githubBrowserConfirmed:!1};try{return h.proc=m.spawn(u.command,u.args,{name:"xterm-256color",cols:100,rows:28,cwd:d,env:a(p)}),h.proc.onData(e=>{if(!h||i.has(h.phase))return;"github"===r&&String(e).includes("[6n")&&h.proc?.write("[1;1R");const t=parseHostAuthOutput(r,h.buffer,e);h.buffer=t.buffer,"github"===r&&!h.githubGitConfirmed&&/authenticate git with your github credentials/i.test(h.buffer)&&(h.githubGitConfirmed=!0,h.proc?.write("y\r")),"github"===r&&!h.githubBrowserConfirmed&&/press enter to open .*github\.com.*browser/i.test(h.buffer)&&(h.githubBrowserConfirmed=!0,h.proc?.write("\r")),t.verificationUri&&(h.verificationUri=t.verificationUri),t.userCode&&(h.userCode=t.userCode),t.needsInput&&(h.needsInput=!0),h.verificationUri&&(h.phase="action_required")}),h.proc.onExit(({exitCode:e})=>{h&&(h.timer&&l(h.timer),h.timer=null,h.cancelRequested||"expired"===h.phase?"expired"!==h.phase&&(h.phase="cancelled"):0===e?(h.phase="connected",h.error=null):(h.phase="failed",h.error="The provider did not complete this login. Start a new one."),h.buffer="",h.proc=null)}),h.timer=s(f,9e5),h.timer?.unref?.(),{ok:!0,flow:c(h)}}catch{return h?.timer&&l(h.timer),h.phase="failed",h.error="The provider login could not be started on this bridge.",h.proc=null,{ok:!1,error:h.error,flow:c(h)}}},status:e=>!h||e&&h.flowId!==e?null:c(h),submit:(e,r)=>{if(!h||h.flowId!==e)return{ok:!1,error:"This login is no longer active."};if("supabase"!==h.service||!h.needsInput||"action_required"!==h.phase)return{ok:!1,error:"This login is not waiting for a verification code."};const t=String(r||"").trim();if(!/^[A-Za-z0-9_-]{4,128}$/.test(t))return{ok:!1,error:"Enter the verification code shown by Supabase."};try{return h.proc?.write(`${t}\r`),h.needsInput=!1,h.phase="waiting",{ok:!0,flow:c(h)}}catch{return{ok:!1,error:"The verification code could not be delivered."}}},cancel:e=>{if(!h||h.flowId!==e)return{ok:!1,error:"This login is no longer active."};if(i.has(h.phase))return{ok:!0,flow:c(h)};h.cancelRequested=!0,h.phase="cancelled",h.error=null,h.timer&&l(h.timer),h.timer=null;try{h.proc?.kill()}catch{}return h.proc=null,h.buffer="",{ok:!0,flow:c(h)}}}}
|