thinkpool-pair 0.7.424 → 0.7.425
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/codex-session.mjs +1 -1
- package/package.json +1 -2
- package/publish-manifest.json +7 -8
- package/host-auth.mjs +0 -1
package/account.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as e}from"node:child_process";import{fileURLToPath as r}from"node:url";import{randomUUID as t}from"node:crypto";import o from"node:path";import n from"node:fs";import{createClient as s}from"@supabase/supabase-js";import i from"node:os";import{saveAuth as a,loadAuth as c,markAuthReconnectRequired as d,loadDirs as l,bindDir as p,loadServed as u,rememberServed as m,loadDefaultDir as f,saveDefaultDir as h,claudeRecentProjectDir as y}from"./auth-store.mjs";import{isSafeToRestart as g}from"./update-gate.mjs";import{makeThrottledTrack as v,presenceRecoveryEligible as w,presenceSelfEchoObservation as b,recoveryBackoffMs as k,reducePresenceChannelEvidence as _,subscribeWithoutStartupDeadlock as S}from"./presence.mjs";import{publicKeyB64 as $,announceProviders as R,listProviders as T,addProvider as I,addProviderModel as O,removeProvider as x,unseal as C}from"./providers.mjs";import{supervisorServes as A,supervisorRoomsToStop as P}from"./serve-consent.mjs";import{resolveServeDir as q}from"./serve-dir.mjs";import{pairKeyFor as N,pairTopic as E,CROSSROOM_BUS as M}from"./cross-terminal.mjs";import{installedAgentCommands as U}from"./agent-detect.mjs";import{agentVisibilityStates as L,setAgentPickerVisibility as j}from"./agent-visibility.mjs";import{hostMemoryAdmission as D}from"./host-memory.mjs";import{HostCapacityAuthority as F}from"./host-capacity.mjs";import{createPairBusBroker as H,mergePairRoomRoster as B,pairBusStatusFromRealtime as z,PAIR_BUS_STATUS as G}from"./pair-bus.mjs";import{readCloudSessionTranscript as K}from"./cloud-session-transcript.mjs";import{clearSupervisorReady as W,supervisorPresenceEchoed as J,writeSupervisorReady as V}from"./supervisor-ready.mjs";import{PAIR_CLI as X,pairCli as Y}from"./command-guidance.mjs";import{syncRealtimeAuth as Z}from"./design-edit.mjs";import{crossRotationLock as Q}from"./rotation-lock.mjs";import{loadOrCreateBridgeId as ee,bridgeLabel as re}from"./bridge-identity.mjs";import{createHostAuthManager as te}from"./host-auth.mjs";const oe=(()=>{try{return JSON.parse(n.readFileSync(new URL("./package.json",import.meta.url),"utf8")).version}catch{return null}})(),ne=r(new URL("./bridge.mjs",import.meta.url));export function createStartupAnchor({set:e=setInterval,clear:r=clearInterval}={}){const t=e(()=>{},6e4);let o=!1;return()=>{o||(o=!0,r(t))}}const se=o.join(i.homedir(),".thinkpool-pair","account.lock");function ie(e){if(!e||e===process.pid)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}export function acquireSingleton(e=se){try{n.mkdirSync(o.dirname(e),{recursive:!0})}catch{return!1}for(let r=0;r<3;r++)try{const r=n.openSync(e,"wx");return n.writeSync(r,String(process.pid)),n.closeSync(r),!0}catch(r){if("EEXIST"!==r.code)return!1;let t=0;try{t=parseInt(n.readFileSync(e,"utf8").trim(),10)||0}catch{return!1}if(ie(t))return!1;try{n.rmSync(e,{force:!0})}catch{return!1}}return!1}export function releaseSingleton(e=se){try{(parseInt(n.readFileSync(e,"utf8").trim(),10)||0)===process.pid&&n.rmSync(e,{force:!0})}catch{}}export function acquireMachineLock(e=se){return acquireSingleton(e)}export function releaseMachineLock(e=se){return releaseSingleton(e)}export function shouldHoldMachineLock(e=process.env){return"1"!==e.THINKPOOL_PAIR_ACCOUNT_CHILD}export async function runLogin(e,r,t){const o=s(e,r,{auth:{persistSession:!1}}),{data:n,error:i}=await o.rpc("start_device_code"),c=Array.isArray(n)?n[0]:n;!i&&c?.device_code||(process.stderr.write(`\n ✗ couldn't start login: ${i?.message||"no response"}\n`),process.exit(1));const{device_code:d,user_code:l}=c,p=`${t}/code/link?c=${encodeURIComponent(l)}`;process.stderr.write(`\n ◆ Link this device to your thinkpool account.\n Open (signed in) and approve:\n\n ${p}\n\n code: ${l}\n\n Waiting…\n`);const u=Date.now(),m=async()=>{Date.now()-u>3e5&&(process.stderr.write(`\n ◇ link timed out — run \`${Y("login")}\` again.\n`),process.exit(1));try{const{data:e}=await o.rpc("claim_device_code",{p_device_code:d});"approved"===e?.status&&e.session?.refresh_token&&(a(e.session),process.stderr.write(`\n ◆ linked as ${e.session.email||"your account"}. You can close the browser tab.\n Now run: ${X}\n`),process.exit(0)),"expired"!==e?.status&&"not_found"!==e?.status||(process.stderr.write(`\n ◇ link expired — run \`${Y("login")}\` again.\n`),process.exit(1))}catch{}setTimeout(m,3e3)};m(),await new Promise(()=>{})}const ae=e=>new Promise(r=>{const t=setTimeout(r,e);t.unref?.()});export function withTimeout(e,r,t="op"){return new Promise((o,n)=>{const s=setTimeout(()=>n(new Error(`${t} timed out after ${r}ms`)),r);s.unref?.(),Promise.resolve(e).then(e=>{clearTimeout(s),o(e)},e=>{clearTimeout(s),n(e)})})}export function savedTokenStillValid(e,r=Math.floor(Date.now()/1e3),t=300){if(!e?.access_token)return!1;const o=e.expires_at||function(e){try{const r=String(e).split(".")[1],t=Buffer.from(r.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"),o=JSON.parse(t)?.exp;return"number"==typeof o?o:0}catch{return 0}}(e.access_token);return!!o&&o-r>t}const ce=o.join(i.homedir(),".thinkpool-pair","refresh.lock");export function acquireRefreshLock(e=ce,r=3e4){try{n.mkdirSync(o.dirname(e),{recursive:!0})}catch{}for(let t=0;t<2;t++)try{const r=n.openSync(e,"wx");return n.writeSync(r,JSON.stringify({pid:process.pid,at:Date.now()})),n.closeSync(r),!0}catch(t){if("EEXIST"!==t.code)return!0;let o=1/0;try{o=Date.now()-(JSON.parse(n.readFileSync(e,"utf8"))?.at||0)}catch{o=1/0}if(o<r)return!1;try{n.rmSync(e,{force:!0})}catch{return!0}}return!1}export function releaseRefreshLock(e=ce){try{n.rmSync(e,{force:!0})}catch{}}async function de(e,r){if(!r?.access_token||!r?.refresh_token)return null;try{const{data:t,error:o}=await e.auth.setSession({access_token:r.access_token,refresh_token:r.refresh_token});return o||!t?.session?null:t.session}catch{return null}}export async function refreshAccountSession(e,r,t={}){const{load:o=c,save:n=a,acquire:s=acquireRefreshLock,release:i=releaseRefreshLock,wait:d=ae,crossLock:l=null}=t,p=async r=>{const t=o();if(t?.refresh_token&&r&&t.refresh_token!==r){const r=await de(e,t);if(r)return{session:r,adopted:!0}}return null};let u=await p(r);if(u)return u;const m=o()?.refresh_token||r;if(!m)return null;let f=!1;const h=async()=>{if(l&&!f){let e=!0;try{e=await l.acquire()}catch{e=!0}if(!1===e)return!1;f=!0}return s()};let y=await h();if(!y){for(let e=0;e<20;e++){if(await d(250),u=await p(m),u)return f&&await(l?.release?.()),f=!1,u;if(await h()){y=!0;break}}if(!y)return f&&await(l?.release?.()),f=!1,null}try{if(u=await p(m),u)return u;const r=o()?.refresh_token||m,{data:t,error:s}=await e.auth.refreshSession({refresh_token:r});return s||!t?.session?{session:null,error:s||null}:(n(t.session),{session:t.session,adopted:!1})}finally{if(f){f=!1;try{await(l?.release?.())}catch{}}y&&i()}}export const ACCOUNT_RECONNECT_ERROR_CODES=new Set(["refresh_token_not_found","refresh_token_already_used","session_not_found"]);export function accountRefreshNeedsReconnect(e){return ACCOUNT_RECONNECT_ERROR_CODES.has(String(e?.code||""))}export async function authedClient(e,r,{crossLockFor:t}={}){const o=c();if(!o?.refresh_token)return null;if(o.reconnect_required)return{reconnectRequired:!0};const n=s(e,r,{auth:{persistSession:!1,autoRefreshToken:!1}});if(savedTokenStillValid(o)){const e=await de(n,o);if(e)return{sb:n,session:e}}const i=await refreshAccountSession(n,o.refresh_token,t?{crossLock:t(n)}:{});return i?.session?{sb:n,session:i.session}:accountRefreshNeedsReconnect(i?.error)?(d(i.error.code),{reconnectRequired:!0}):null}export async function authedClientWithRetry(e,r,t={}){const{attempt:o=authedClient,load:n=c,wait:s=ae,onRetry:i=null,maxAttempts:a=1/0,baseMs:d=15e3,capMs:l=3e5,crossLockFor:p=null}=t;let u=d;for(let t=1;t<=a;t++){const c=await o(e,r,{crossLockFor:p});if(c?.reconnectRequired)return c;if(c)return c;if(!n()?.refresh_token)return null;if(t>=a)break;i?.(u,t),await s(u),u=Math.min(2*u,l)}return null}export function claimLoopWedged({lastClaimOkAt:e,now:r,quietMsThreshold:t=4e4}){return r-e>=t}export function scheduleTokenRefresh({sb:e,session:r,onRefreshed:t,onReconnectRequired:o,skewMs:n=9e4,forceMs:s=0,retryMs:i=3e4,refresh:a=refreshAccountSession,crossLock:c=null}){let d=null,l=r,p=!1,u=!1;const m=e=>{p||(clearTimeout(d),d=setTimeout(h,Math.max(1e3,e)),d?.unref&&d.unref())},f=()=>{if(s)return s;const e=l?.expires_at?1e3*l.expires_at:Date.now()+36e5;return Math.max(3e4,e-Date.now()-n)};async function h(){if(!p)try{const r=await a(e,l.refresh_token,c?{crossLock:c}:void 0);if(!r?.session){if(accountRefreshNeedsReconnect(r?.error)&&!u){u=!0;try{await(o?.(r.error))}catch{}}throw r?.error||new Error("no session")}l=r.session;try{e.realtime?.setAuth?.(l.access_token)}catch{}u=!1;try{await(t?.(l))}catch{}m(f())}catch{m(i)}}return m(f()),{cancel:()=>{p=!0,clearTimeout(d)},_tick:h}}export function runBind(e,r){const t=(e||"").toUpperCase().trim();t||(console.error(`usage: ${Y("bind","<ROOM>","<directory>")}`),process.exit(1));const s=o.resolve(r||".");n.existsSync(s)&&n.statSync(s).isDirectory()||(console.error(`✗ not a directory: ${s}`),process.exit(1)),p(t,s),console.error(`✓ bound ${t} → ${s}\n Run \`${X}\` (or restart it) to serve this session.`),process.exit(0)}export async function runAccount(r,s){acquireSingleton()||(console.error(`\n ◇ An account bridge is already running on this machine\n (~/.thinkpool-pair/account.lock). Not starting a second — two would race\n your saved login. Stop the other one first, or share a single room with\n \`${Y("<ROOM>")}\`.\n`),process.exit(0)),W();const c=createStartupAnchor(),X=ee(),se=t(),ie=e=>Q(e,X),ae=await authedClientWithRetry(r,s,{crossLockFor:ie,onRetry:e=>process.stderr.write(`\n ◇ Couldn't refresh your account session yet (offline, or another bridge is mid-rotation) — retrying in ${Math.round(e/1e3)}s. Saved login left in place.\n`)});ae||(releaseSingleton(),console.error(`\n ◇ Not linked to a thinkpool account on this machine.\n Link it: ${Y("login")}\n`),process.exit(0)),ae.reconnectRequired&&(c(),releaseSingleton(),console.error("\n ◇ This bridge lost its thinkpool account link.\n Reconnect it: npx thinkpool-pair@latest login\n"),process.exit(0));const{sb:ce,session:de}=ae,le=de.user?.email||"your account";let pe=de.access_token||null;const ue=process.cwd();let me=!1;try{const e=await import("./service.mjs");me=e.serviceActive(null)}catch{}const fe=()=>"1"===process.env.THINKPOOL_PAIR_AUTOUPDATE||me,he="1"===process.env.THINKPOOL_PAIR_AUTOUPDATE?" (auto-updating background service — survives reboot, tracks @latest)\n":me?" (background service — survives reboot, restarts on crash, pinned to this version)\n":` (run hands-off + auto-updating: ${Y("install-service")})\n`,ye=f()||y()||ue;process.stderr.write(`\n ◆ thinkpool-pair — account mode · ${le}\n New sessions auto-serve from ${ye}${f()?" (your default)":y()?" (your most-recent Claude Code project)":""}\n (change it: ${Y("set-default-dir","<dir>")} · one room: ${Y("bind","<code>","<dir>")})\n${he}`);const ge=new Map,ve=new F,we=new Map,be=new Map,ke=new Map,_e=new Set,Se=new Map,$e=new Map,Re=new Map,Te=new Map,Ie=new Map;let Oe=!1,xe=null,Ce=!1;const Ae=new Map,Pe=new Set,qe=new Map,Ne=i.hostname().replace(/\.local$/,""),Ee=(t?.()||"").slice(0,8);let Me=null;console.log(`◇ supervisor boot sup=${Ee} host=${Ne} pid=${process.pid} npx=${oe||"dev"}`),await Z(ce,pe,s)||process.stderr.write("\n ⚠ realtime auth could not be initialized; private bridge control is unavailable.\n");const Ue=ce.channel(`tpacct:${de.user.id}`,{config:{presence:{key:Ne},broadcast:{self:!1}}}),Le=ce.channel(`tpacct-control:${de.user.id}`,{config:{private:!0,broadcast:{self:!1}}});let je=!1,De=()=>{};Le.on("broadcast",{event:"bind-room"},({payload:e})=>{const r=String(e?.code||"").toUpperCase().trim();let t=String(e?.dir||"").trim();t.startsWith("~")&&(t=o.join(i.homedir(),t.slice(1))),r&&t&&(n.existsSync(t)?(p(r,t),e?.setDefault&&(h(t),process.stderr.write(`\n ◆ default project dir set → ${t}\n`)),process.stderr.write(`\n ◆ ${r} bound → ${t} (from the web). Serving…\n`),De()):process.stderr.write(`\n ◇ bind ${r}: directory not found (${t}) — ignored.\n`))}),Le.on("broadcast",{event:"serve-room"},({payload:e})=>{const r=String(e?.code||"").toUpperCase().trim();/^[A-Z0-9]{4,12}$/.test(r)&&De()}),Le.on("broadcast",{event:"agent-output-hygiene"},({payload:e})=>{if("boolean"==typeof e?.enabled)for(const r of ge.values())try{r.send({t:"agent-output-hygiene",enabled:e.enabled})}catch{}}),Le.on("broadcast",{event:"shutdown"},async()=>{if(process.stderr.write("\n ◇ disconnect requested from the dashboard — stopping bridge.\n"),setTimeout(()=>{try{releaseSingleton()}catch{}process.exit(0)},2500),fe())try{(await import("./service.mjs")).uninstallService(null)}catch{}process.kill(process.pid,"SIGTERM")});let Fe=!1;Le.on("broadcast",{event:"restart"},async({payload:e}={})=>{const r=e?.nonce,t=async(e,t)=>{try{await Le.send({type:"broadcast",event:"restart-status",payload:{nonce:r,ok:e,error:t}})}catch{}};if(Fe||Ce)await t(!1,"a bridge update is already being prepared");else{Fe=!0;try{const r=await import("./service.mjs"),o=r.serviceActive(null),n=o?r.stageServiceUpdate(null):null;if(o&&!n)return void await t(!1,"the published runtime could not be staged; the existing bridge was left running");xe=n||("string"==typeof e?.version?e.version:oe)||"restart",Oe=!0,await t(!0)}catch(e){return void await t(!1,`restart preparation failed: ${e?.message||e}`)}finally{Fe=!1}Cr()}});let He="init",Be="connected";const ze=v(Ue,{minMs:5e3,onStatus:e=>{He=e}}),Ge=$(),Ke=()=>ze({name:Ne,bridgeId:se,version:oe,rooms:[...ge.keys()],readyRooms:[..._e],refused:[...qe].map(([e,r])=>({code:e,reason:r})),service:fe(),providerPubKey:Ge,providers:R(),agents:L(U()),accountAuthState:Be,ts:Date.now()}),We=te(),Je=async e=>{try{await withTimeout(ce.rpc("report_bridge_auth_state",{p_bridge_id:X,p_state:e}),5e3,"report_bridge_auth_state")}catch{}},Ve=(e,r)=>{try{Le.send({type:"broadcast",event:e,payload:r})}catch{}};Le.on("broadcast",{event:"master-brief-cleanup"},({payload:e})=>{const r=String(e?.nonce||""),t=String(e?.transcript||"").trim();if(!r||!t||t.length>12e3)return void Ve("master-brief-cleanup-res",{nonce:r,ok:!1,error:"That recording could not be cleaned up."});const o=[..._e].find(e=>!1!==ge.get(e)?.connected),n=o?ge.get(o):null;if(n)try{n.send({t:"master-brief-cleanup",nonce:r,transcript:t,speaker:e?.speaker})}catch{Ve("master-brief-cleanup-res",{nonce:r,ok:!1,error:"The connected agent was unavailable."})}else Ve("master-brief-cleanup-res",{nonce:r,ok:!1,error:"Open a session with a connected agent to clean this Brief."})}),Le.on("broadcast",{event:"provider-add"},async({payload:e})=>{const r=e?.nonce;let t;try{t=C(e?.sealed)}catch{return Ve("provider-add-res",{nonce:r,ok:!1,error:"could not decrypt the sealed payload"})}const o=I(t);o.ok&&(Ke(),process.stderr.write(`\n ◆ provider added (${String(t?.name||"").slice(0,40)}) — re-announced.\n`)),Ve("provider-add-res",{nonce:r,ok:o.ok,error:o.ok?void 0:o.error,id:o.ok?o.id:void 0})}),Le.on("broadcast",{event:"provider-add-model"},async({payload:e})=>{const r=e?.nonce,t=O({id:e?.id,model:e?.model,name:e?.name});t.ok&&(Ke(),process.stderr.write(`\n ◆ model added to an existing provider (${String(e?.model||"").slice(0,40)}) — re-announced.\n`)),Ve("provider-add-model-res",{nonce:r,ok:t.ok,error:t.ok?void 0:t.error,id:t.ok?t.id:void 0})}),Le.on("broadcast",{event:"provider-remove"},async({payload:e})=>{const r=e?.nonce,t=x(e?.id);t.ok&&(Ke(),process.stderr.write("\n ◆ provider removed — re-announced.\n")),Ve("provider-remove-res",{nonce:r,ok:t.ok,error:t.ok?void 0:t.error})}),Le.on("broadcast",{event:"providers-list-req"},async({payload:e})=>{const r=e?.nonce;Ve("providers-list-res",{nonce:r,ok:!0,providers:T()})});const Xe=e=>e?.targetBridgeId===se;Le.on("broadcast",{event:"host-auth-start"},async({payload:e})=>{if(!Xe(e))return;const r=await We.start(String(e?.service||""));Ve("host-auth-res",{nonce:e?.nonce,...r})}),Le.on("broadcast",{event:"host-auth-status"},({payload:e})=>{if(!Xe(e))return;const r=We.status(String(e?.flowId||""));Ve("host-auth-res",{nonce:e?.nonce,ok:!!r,flow:r,error:r?void 0:"This login is no longer active."})}),Le.on("broadcast",{event:"host-auth-submit"},({payload:e})=>{if(!Xe(e))return;const r=We.submit(String(e?.flowId||""),e?.code);Ve("host-auth-res",{nonce:e?.nonce,...r})}),Le.on("broadcast",{event:"host-auth-cancel"},({payload:e})=>{if(!Xe(e))return;const r=We.cancel(String(e?.flowId||""));Ve("host-auth-res",{nonce:e?.nonce,...r})}),Le.on("broadcast",{event:"agent-visibility-set"},async({payload:e})=>{const r=e?.nonce,t=U(),o=j(e?.cmd,!0===e?.enabled,{installed:t});if(o.ok){Ke();for(const e of ge.values())try{e.send({t:"agent-visibility-changed"})}catch{}process.stderr.write(`\n ◆ terminal picker agents updated — ${o.agents.filter(e=>e.enabled).map(e=>e.cmd).join(", ")} visible.\n`)}Ve("agent-visibility-res",{nonce:r,ok:o.ok,error:o.ok?void 0:o.error,agents:o.ok?o.agents:void 0})});let Ye=_(),Ze=!1,Qe=!1;const er=()=>{if(Qe||!Ze)return;let e=!1;try{e=J(Ue.presenceState?.(),Ne,se,oe)}catch{e=!1}e&&(Qe=V({version:oe,bridgeId:se}),Qe||process.stderr.write("\n ◇ Could not record local supervisor readiness; the bridge remains running but installers will not claim it was confirmed.\n"))};Ue.on("presence",{event:"sync"},()=>{Ye=_(Ye,"sync"),er()});const rr=await S(Ue,e=>{if("SUBSCRIBED"===e)Ye=_(Ye,e),Ke();else if("CLOSED"===e||"CHANNEL_ERROR"===e||"TIMED_OUT"===e){Ye=_(Ye,e);try{ce.realtime?.connect?.()}catch{}}});rr.connected||process.stderr.write(`\n ◇ account realtime not ready at startup (${rr.status}) — staying alive and recovering in process.\n`);const tr=await S(Le,e=>{"CLOSED"!==e&&"CHANNEL_ERROR"!==e&&"TIMED_OUT"!==e||process.stderr.write(`\n ⚠ private account control ${e}; host-changing dashboard actions are unavailable until Realtime reconnects.\n`)});tr.connected||process.stderr.write(`\n ◇ private account control not ready at startup (${tr.status}) — presence remains available.\n`);const or=scheduleTokenRefresh({sb:ce,session:de,crossLock:ie(ce),onRefreshed:async e=>{Be="connected",a(e),Ke(),await Je("connected"),pe=e.access_token||pe;for(const e of ge.values())try{e.send({t:"token",accessToken:pe})}catch{}},onReconnectRequired:async e=>{Be="reconnect-required",d(e?.code||"refresh-token-invalid"),Ke(),await Je("reconnect-required"),process.stderr.write("\n ◇ This bridge lost its thinkpool account link.\n Reconnect it: npx thinkpool-pair@latest login\n")},forceMs:parseInt(process.env.TP_TOKEN_REFRESH_FORCE_MS,10)||0});let nr=!1,sr=0,ir=null,ar=Date.now(),cr=0,dr=0,lr="";const pr=async()=>{try{const{data:e}=await withTimeout(ce.rpc("claim_bridge",{p_bridge_id:X,p_hostname:Ne}),1e4,"claim_bridge");ar=Date.now();const r=Array.isArray(e)?e[0]:e,t=!!r?.held,o=r?.holder_host||"?";return t&&!nr?process.stderr.write("\n ◆ holding this account — serving your sessions (single active bridge).\n"):!t&&nr?process.stderr.write(`\n ◇ another device (${o}) took over this account — standing by.\n`):t||ir===o||process.stderr.write(`\n ◇ another device (${o}) is serving this account — standing by.\n (this Mac takes over automatically if that device goes offline.)\n`),nr=t,sr=t&&Number.isSafeInteger(Number(r?.fencing))&&Number(r.fencing)>0?Number(r.fencing):0,ir=t?null:o,Me!==t&&(console.log(`claim_tick sup=${Ee} held=${t} holder=${t?Ne:o}`),Me=t),t}catch{return nr}},ur=H({channels:Te,randomId:t}),mr=e=>ur.resolve(e),fr=(e,r,t)=>ur.requestFirst(e,r,t),hr=(e,r,t)=>ur.requestCollect(e,r,t),yr=async e=>{try{const r=await withTimeout(ce.rpc("list_pair_rooms",{p_code:e}),2500,"paired-room roster");return r?.error||!Array.isArray(r?.data)?{verified:!1,rooms:[]}:{verified:!0,rooms:r.data}}catch{return{verified:!1,rooms:[]}}},gr=(e,r)=>{if(!e||Te.has(e))return;const o=E(r);if(!o)return;const n=ce.channel(o,{config:{private:!0,broadcast:{self:!1}}}),s={channel:n,key:r,status:G.JOINING};n.on("broadcast",{event:"peer-list-req"},({payload:r})=>((e,r,t)=>{if("1"===process.env.TP_PAIRBUS_OFF||"1"===process.env.TP_CROSSROOM_OFF)return;const o=[...ge.keys()].filter(r=>Re.get(r)===e).map(e=>({code:e,name:Se.get(e)||null,host:Ne}));try{r.send({type:"broadcast",event:"peer-list-res",payload:{nonce:t?.nonce,rooms:o}})}catch{}})(e,n,r)).on("broadcast",{event:"peer-list-res"},({payload:e})=>mr(e)).on("broadcast",{event:"peer-peek-req"},({payload:r})=>((e,r,o)=>{const n=e=>{try{r.send({type:"broadcast",event:"peer-peek-res",payload:{nonce:o?.nonce,...e}})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF||"1"===process.env.TP_CROSSROOM_OFF)return n({error:"Cross-session reach is off on this device."});const s=String(o?.targetRoom||"").toUpperCase().trim(),i=ge.get(s);if(!i||Re.get(s)!==e)return n({error:`Room ${s} is not a room this device shares with you.`});const a=t();$e.set(a,{reply:e=>n({text:e.text,error:e.error}),askerRoom:null});try{i.send({t:"pair-peek-local",reqId:a,terminal:o.terminal,lines:o.lines})}catch{$e.delete(a),n({error:`Could not reach room ${s}.`})}})(e,n,r)).on("broadcast",{event:"peer-peek-res"},({payload:e})=>mr(e)).on("broadcast",{event:"peer-post-req"},({payload:r})=>((e,r,o)=>{const n=e=>{try{r.send({type:"broadcast",event:"peer-post-res",payload:{nonce:o?.nonce,...e}})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF)return n({error:"Cross-room posting is off on this device."});if(!(e=>{const r=Date.now();let t=Ie.get(e);return(!t||r>=t.resetAt)&&(t={count:0,resetAt:r+6e4},Ie.set(e,t)),!(t.count>=6||(t.count++,0))})(e))return n({error:"Too many cross-room posts to this device right now — try again shortly."});const s=String(o?.targetRoom||"").toUpperCase().trim(),i=ge.get(s);if(!i||Re.get(s)!==e)return n({error:`Room ${s} is not a room this device shares with you.`});const a=t();$e.set(a,{reply:e=>n({ok:e.ok,ref:e.ref,error:e.error}),askerRoom:null});try{i.send({t:"pair-post-local",reqId:a,fromRoom:o.fromRoom,fromHost:o.fromHost,fromTerminalName:o.fromTerminalName,text:o.text,terminal:o.terminal})}catch{$e.delete(a),n({error:`Could not reach room ${s}.`})}})(e,n,r)).on("broadcast",{event:"peer-post-res"},({payload:e})=>mr(e)).subscribe(e=>{s.status=z(e),"SUBSCRIBED"===e?process.stderr.write("\n ◆ pair bus subscribed — cross-machine Ensemble reach with your partner.\n"):"CHANNEL_ERROR"!==e&&"TIMED_OUT"!==e||process.stderr.write(`\n ◇ pair bus ${e.toLowerCase().replace("_"," ")} — partner sessions are offline until it reconnects.\n`)}),Te.set(e,s)};let vr=0;const wr=async()=>{const e=Date.now();if(!(vr&&e-vr<6e4)){vr=e;try{await br()}finally{vr===e&&(vr=0)}}};De=()=>{wr().catch(()=>{})};const br=async()=>{if(await(async()=>{try{const{data:e}=await withTimeout(ce.rpc("enroll_bridge",{p_bridge_id:X,p_label:re(),p_version:oe}),1e4,"enroll_bridge"),r=Array.isArray(e)?e[0]:e,t=r?.reason||"";t!==lr&&("revoked"===t?process.stderr.write("\n ◇ this device was revoked from your thinkpool account — standing by.\n Restore it from the account page to serve again.\n"):"foreign"===t&&process.stderr.write("\n ◇ this machine's bridge identity belongs to a different thinkpool account.\n Remove ~/.thinkpool-pair/bridge-id and relink to enroll fresh.\n"),lr=t)}catch{}})(),!await pr()){if(ge.size)for(const[,e]of ge)try{e.kill("SIGTERM")}catch{}return void Ke()}let r=[],s=!1;const a=de.user.id;try{const{data:e,error:t}=await ce.from("code_sessions").select("code,name,last_active_at,owner_id,participants,closed_at").eq("owner_id",a).is("closed_at",null).order("last_active_at",{ascending:!1}).limit(20);if(t)throw t;r=e||[],s=!0;const o=new Set(r.map(e=>e.code));for(const e of[...qe.keys()])o.has(e)||qe.delete(e)}catch{}if(s)for(const e of P({rooms:r,childRooms:ge.keys(),myUid:a})){const r=ge.get(e);if(r){console.log(`child_retire sup=${Ee} room=${e} pid=${r.pid||"?"} reason=not-active-owned`);try{r.kill("SIGTERM")}catch{}}}const c=l(),d=u();for(const s of r){const r=s.code;if(!r||ge.has(r)){r&&qe.delete(r);continue}if(!A({ownerId:s.owner_id,myUid:a,closedAt:s.closed_at}))continue;const l=q({bound:c[r],remembered:d[r],defaultDir:f()||y()||ue,home:i.homedir()});if(l.refuse){if(qe.set(r,l.reason),!Pe.has(r)){Pe.add(r);const e="home"===l.reason?`no project directory set — refusing to serve it from your home folder (${l.dir}); the agent would lose its session context and the room would show "${o.basename(l.dir)}" as the repo.`:"no directory to serve it from.";process.stderr.write(`\n ◇ ${r}: ${e}\n Bind it to its repo: ${Y("bind",r,"<repo-dir>")}\n`)}continue}const p=l.dir;if(!n.existsSync(p)){qe.set(r,"dir-gone"),Pe.has(r)||(Pe.add(r),process.stderr.write(`\n ◇ ${r}: bound directory is gone (${p}) — re-bind it.\n`));continue}Pe.delete(r),qe.delete(r),l.fromDefault&&m(r,p),Se.set(r,s.name||null),process.stderr.write(`\n ◆ serving ${r}${s.name?` "${s.name}"`:""} → ${p}${c[r]?"":" (default)"}\n`);const u={...process.env,THINKPOOL_PAIR_ACCOUNT_CHILD:"1",TP_ACCOUNT_BRIDGE_ID:X,TP_ACCOUNT_BRIDGE_FENCING:String(sr)};delete u.THINKPOOL_PAIR_AUTOUPDATE,pe&&(u.TP_ACCESS_TOKEN=pe);const h=ge.has(r),g=D(`spawn the room bridge for ${r}`);if(!g.ok){Pe.has(`memory:${r}`)||(Pe.add(`memory:${r}`),process.stderr.write(`\n ◇ ${r}: ${g.reason}\n`)),qe.set(r,"host-memory");continue}Pe.delete(`memory:${r}`);const v=e(process.execPath,[ne,r,"--headless","--auto=claude"],{cwd:p,stdio:["inherit","inherit","inherit","ipc"],env:u});console.log(`child_spawn sup=${Ee} room=${r} pid=${v.pid} dir=${p} had_prior=${h}`),ge.set(r,v),_e.delete(r),we.set(r,!1),be.set(r,!1),ke.set(r,!1),v.on("message",e=>{if(e)if("room-ready"===e.t)_e.add(r),console.log(`child_ready sup=${Ee} room=${r} pid=${v.pid}`),Ke();else if("idle"===e.t)we.set(r,!!e.idle),be.set(r,null!=e.between?!!e.between:!!e.idle),ke.set(r,!!e.webPeer),Cr();else if("busy"===e.t)we.set(r,!1),be.set(r,!1);else if("master-brief-cleanup-res"===e.t)Ve("master-brief-cleanup-res",{nonce:e.nonce,ok:!0===e.ok,...e.brief?{brief:e.brief}:{},...e.error?{error:e.error}:{}});else if("capacity-acquire"===e.t){const t=ve.acquire({room:r,terminalId:e.terminalId});try{v.send({t:"capacity-acquire-result",reqId:e.reqId,...t})}catch{t.ok&&ve.release({room:r,terminalId:e.terminalId})}}else if("capacity-release"===e.t){if(ve.release({room:r,terminalId:e.terminalId}))for(const e of ge.values())try{e.send({t:"capacity-changed"})}catch{}}else if("restart-probe-result"===e.t){const t=Ae.get(e.nonce);if(!t||!t.pending.has(r)||ge.get(r)!==v)return;t.pending.delete(r),t.ok=t.ok&&!0===e.between,be.set(r,!0===e.between),t.pending.size||(clearTimeout(t.timer),Ae.delete(e.nonce),t.resolve(t.ok))}else if("apply-update"===e.t)Oe=!0,Cr();else if("pair-list-req"===e.t)(async()=>{if("1"===process.env.TP_CROSSROOM_OFF){try{v.send({t:"pair-list-res",reqId:e.reqId,rooms:[],error:"Cross-session reach is turned off on this bridge."})}catch{}return}const t=[...ge.keys()].filter(e=>e!==r).map(e=>({code:e,name:Se.get(e)||null}));let o=[],n=[];if("1"!==process.env.TP_PAIRBUS_OFF){const e=await Promise.all([yr(r),Te.size?hr("peer-list-req",{},1200):Promise.resolve([])]);o=e[0].rooms,n=e[1]}const s=B({authorized:o,replies:n,myUid:de.user.id,localCodes:ge.keys()});try{v.send({t:"pair-list-res",reqId:e.reqId,rooms:[...t,...s]})}catch{}})();else if("pair-peek-req"===e.t)(async()=>{const o=r=>{try{v.send({t:"pair-peek-res",reqId:e.reqId,...r})}catch{}},n=async(r,t)=>{if(!t.verified)return o({error:`Room ${r}'s pair authorization could not be verified, so its cloud transcript was not read.`});try{const t=await withTimeout(K({sb:ce,targetRoom:r,terminal:e.terminal,lines:e.lines}),5e3,"paired cloud transcript");return o({text:t})}catch(e){return o({error:`Room ${r}'s host bridge is offline, and its cloud transcript could not be read: ${e?.message||e}`})}};if("1"===process.env.TP_CROSSROOM_OFF)return o({error:"Cross-session reach is turned off on this bridge."});const s=String(e.targetRoom||"").toUpperCase().trim();if(!s||s===r)return o({error:`${s||"That"} is not another of your active sessions.`});const i=ge.get(s);if(i){const n=t();$e.set(n,{reply:e=>o({text:e.text,error:e.error}),askerRoom:r});try{i.send({t:"pair-peek-local",reqId:n,terminal:e.terminal,lines:e.lines})}catch{$e.delete(n),o({error:`Could not reach session ${s}.`})}return}if("1"===process.env.TP_PAIRBUS_OFF)return o({error:"Cross-session reach is turned off on this bridge."});const[a,c]=await Promise.all([yr(r),Te.size?hr("peer-list-req",{},800):Promise.resolve([])]);if(a.verified&&!a.rooms.some(e=>e?.code===s&&e?.owner_id!==de.user.id))return o({error:`Room ${s} is not shared with this pair.`});if(!Te.size)return n(s,a);if(!c.some(e=>e?.rooms?.some(e=>e?.code===s)))return n(s,a);const d=await(l=s,p=e.terminal,u=e.lines,fr("peer-peek-req",{targetRoom:l,terminal:p,lines:u},M.busTimeoutMs));var l,p,u;if(!d)return n(s,a);o({text:d.text,error:d.error})})();else if("pair-post-req"===e.t)(async()=>{const o=r=>{try{v.send({t:"pair-post-res",reqId:e.reqId,...r})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF)return o({error:"Cross-room posting is turned off on this bridge."});const n=String(e.targetRoom||"").toUpperCase().trim();if(!n||n===r)return o({error:`${n||"That"} is not another of your sessions.`});const s=ge.get(n);if(s){const i=t();$e.set(i,{reply:e=>o({ok:e.ok,ref:e.ref,error:e.error}),askerRoom:r});try{s.send({t:"pair-post-local",reqId:i,fromRoom:r,fromHost:Ne,fromTerminalName:e.fromTerminalName,text:e.text,terminal:e.terminal})}catch{$e.delete(i),o({error:`Could not reach session ${n}.`})}return}const[i,a]=await Promise.all([yr(r),Te.size?hr("peer-list-req",{},1200):Promise.resolve([])]);if(i.verified&&!i.rooms.some(e=>e?.code===n&&e?.owner_id!==de.user.id))return o({error:`Room ${n} is not shared with this pair.`});if(!Te.size)return o({error:i.verified?`Room ${n} is paired with you, but its host bridge is offline.`:`Room ${n} is not on this machine, and no pair bus is open to reach it.`});if(!a.some(e=>e?.rooms?.some(e=>e?.code===n)))return o({error:`Room ${n} is paired with you, but its host bridge is offline — no recipient approval card can be raised yet.`});const c=await(d=n,l=e.terminal,p=e.text,u=r,m=e.fromTerminalName,fr("peer-post-req",{targetRoom:d,terminal:l,text:p,fromRoom:u,fromHost:Ne,fromTerminalName:m},18e4));var d,l,p,u,m;if(!c)return o({error:`Room ${n} did not respond to the hand-off in time.`});o({ok:c.ok,ref:c.ref,error:c.error})})();else if("pair-peek-res"===e.t||"pair-post-res"===e.t){const r=$e.get(e.reqId);if(!r)return;$e.delete(e.reqId);try{r.reply(e)}catch{}}});const w=e=>{if(console.log(`child_exit sup=${Ee} room=${r} pid=${v?.pid||"?"} code=${e||"?"}`),ge.delete(r),_e.delete(r),we.delete(r),be.delete(r),ke.delete(r),Se.delete(r),Re.delete(r),ve.releaseRoom(r))for(const e of ge.values())try{e.send({t:"capacity-changed"})}catch{}for(const[e,t]of $e)t?.askerRoom===r&&$e.delete(e)};if(v.on("exit",e=>w(e)),v.on("error",e=>{w();try{process.stderr.write(`\n ◇ ${r}: failed to launch its bridge (${e?.message||e}) — retrying next tick.\n`)}catch{}}),xe)try{v.send({t:"update-available",version:xe})}catch{}}if("1"!==process.env.TP_PAIRBUS_OFF)for(const e of r){if(!e?.code||!ge.has(e.code))continue;const r=Array.isArray(e.participants)?e.participants:[],t=2===r.length?r.find(e=>e&&e!==de.user.id):null;if(!t){Re.delete(e.code);continue}Re.set(e.code,t);const o=N(de.user.id,t);o&&gr(t,o)}Ke()};await wr();const kr=setInterval(wr,15e3);Ze=!0,er(),c();const _r=[];let Sr=!1,$r=0,Rr=0,Tr=!1;const Ir=setInterval(async()=>{if(Sr)return;const e=Date.now();if(!claimLoopWedged({lastClaimOkAt:ar,now:e}))return $r=0,void(Rr=0);if(Tr||e<Rr)return;Tr=!0;const r=k($r);Rr=e+r,$r+=1,console.log(`claim_watchdog sup=${Ee} quiet_ms=${e-ar} attempt=${$r} next_ms=${r} — reconnecting realtime + re-claiming`);try{ce.realtime?.connect?.()}catch{}vr=0;try{await pr(),Ke()}catch{}finally{Tr=!1}},2e4);Ir.unref?.();const Or=setInterval(()=>{if(Sr)return;const e=Date.now(),r=w({claimHeld:nr,lastClaimOkAt:ar,now:e});let t=!1;try{t=!!Ue.presenceState?.()[Ne]}catch{t=!1}const o=b({tracking:r,subscribedAt:Math.max(Ye.subscribedAt,ar),syncSeenAt:Ye.syncSeenAt,present:t,missingSince:cr,now:e});if(cr=o.missingSince,!cr)return void(dr=0);const n=o.verdict;if("recycle"===n){if(dr===cr)return;dr=cr,console.log(`presence_watchdog sup=${Ee} action=recycle missing_ms=${e-cr} track=${He} — full realtime disconnect+reconnect`);try{ce.realtime?.disconnect?.()}catch{}try{ce.realtime?.connect?.()}catch{}try{Ke()}catch{}}else"respawn"===n&&(console.log(`presence_watchdog sup=${Ee} action=respawn missing_ms=${e-cr} track=${He} — recycle failed, exiting for a clean supervisor respawn`),process.exit(1))},15e3);Or.unref?.();const xr=e=>{if(Sr)return;Sr=!0;const r=je?1:0;console.log(`supervisor_stop sup=${Ee} reason=${e||"unknown"} restarting=${je} children=${ge.size}`),clearInterval(kr),clearInterval(Ir),clearInterval(Or);for(const e of _r)try{clearInterval(e)}catch{}or.cancel(),W({pid:process.pid}),releaseSingleton();for(const r of ge.values())try{r.kill(e||"SIGTERM")}catch{}ur.close();for(const{channel:e}of Te.values())try{ce.removeChannel(e)}catch{}(async()=>{try{nr&&await ce.rpc("release_bridge",{p_bridge_id:X})}catch{}try{await Ue.untrack()}catch{}try{await ce.removeChannel(Le)}catch{}try{await ce.removeChannel(Ue)}catch{}process.exit(r)})(),setTimeout(()=>process.exit(r),1500)};for(const e of["SIGINT","SIGTERM","SIGHUP"])process.on(e,()=>xr(e));async function Cr(){if(!Ce&&g({pendingUpdate:xe,stopping:Sr,childIdle:we,childBetween:be,childPeer:ke,requested:Oe})){if(Ce=!0,!await function(e=2500){const r=[...ge.entries()];if(!r.length)return Promise.resolve(!0);const o=t();return new Promise(t=>{const n={pending:new Set(r.map(([e])=>e)),ok:!0,resolve:t,timer:null};n.timer=setTimeout(()=>{Ae.delete(o),t(!1)},e),n.timer.unref?.(),Ae.set(o,n);for(const[e,t]of r)try{t.send({t:"restart-probe",nonce:o})}catch{n.pending.delete(e),n.ok=!1}n.pending.size||(clearTimeout(n.timer),Ae.delete(o),t(n.ok))})}())return Ce=!1,void process.stderr.write("\n ◆ bridge update still queued — an agent became active before the restart boundary.\n");process.stderr.write(`\n ◆ thinkpool-pair ${xe} ready (running ${oe}) — restarting account bridge to update; sessions resume.\n`);try{const e=await import("./service.mjs");if(e.serviceActive(null)&&!1!==e.applyStagedServiceUpdate(null,xe))return}catch{}if(Ce=!1,je=!0,!fe())try{e(process.execPath,[process.argv[1]],{detached:!0,stdio:"ignore",env:process.env}).unref()}catch{}xr("SIGTERM")}}if("1"===process.env.THINKPOOL_PAIR_AUTOUPDATE&&oe){const e=1e3*Math.max(60,parseInt(process.env.THINKPOOL_PAIR_UPDATE_INTERVAL,10)||1800),r=(e,r)=>{const t=e=>String(e).split("-")[0].split(".").map(e=>parseInt(e,10)||0),o=t(e),n=t(r);for(let e=0;e<3;e++){if((o[e]||0)>(n[e]||0))return!0;if((o[e]||0)<(n[e]||0))return!1}return!1},t=async()=>{const e=new AbortController,r=setTimeout(()=>e.abort(),8e3);try{const r=await fetch("https://registry.npmjs.org/thinkpool-pair/latest",{signal:e.signal,headers:{accept:"application/json"}});if(!r.ok)return null;const t=await r.json();return"string"==typeof t?.version?t.version:null}catch{return null}finally{clearTimeout(r)}},o=async()=>{const e=await t();if(e&&r(e,oe)){xe||process.stderr.write(`\n ◆ thinkpool-pair ${e} published — "update ready" surfaced to each room; applies between turns.\n`);try{(await import("./service.mjs")).provisionRuntime(e)}catch(r){return void process.stderr.write(`\n ⚠ bridge ${e} could not be staged yet: ${r?.message||r}\n`)}xe=e;for(const r of ge.values())try{r.send({t:"update-available",version:e})}catch{}Cr()}};_r.push(setInterval(o,e).unref()),_r.push(setInterval(Cr,15e3).unref()),_r.push(setTimeout(o,6e4).unref())}await new Promise(()=>{})}
|
|
1
|
+
import{spawn as e}from"node:child_process";import{fileURLToPath as r}from"node:url";import{randomUUID as t}from"node:crypto";import o from"node:path";import n from"node:fs";import{createClient as s}from"@supabase/supabase-js";import i from"node:os";import{saveAuth as a,loadAuth as c,markAuthReconnectRequired as d,loadDirs as l,bindDir as p,loadServed as u,rememberServed as m,loadDefaultDir as f,saveDefaultDir as h,claudeRecentProjectDir as y}from"./auth-store.mjs";import{isSafeToRestart as g}from"./update-gate.mjs";import{makeThrottledTrack as v,presenceRecoveryEligible as w,presenceSelfEchoObservation as b,recoveryBackoffMs as k,reducePresenceChannelEvidence as _,subscribeWithoutStartupDeadlock as $}from"./presence.mjs";import{publicKeyB64 as R,announceProviders as S,listProviders as T,addProvider as I,addProviderModel as O,removeProvider as x,unseal as C}from"./providers.mjs";import{supervisorServes as A,supervisorRoomsToStop as P}from"./serve-consent.mjs";import{resolveServeDir as q}from"./serve-dir.mjs";import{pairKeyFor as N,pairTopic as E,CROSSROOM_BUS as M}from"./cross-terminal.mjs";import{installedAgentCommands as U}from"./agent-detect.mjs";import{agentVisibilityStates as L,setAgentPickerVisibility as D}from"./agent-visibility.mjs";import{hostMemoryAdmission as j}from"./host-memory.mjs";import{HostCapacityAuthority as F}from"./host-capacity.mjs";import{createPairBusBroker as H,mergePairRoomRoster as B,pairBusStatusFromRealtime as z,PAIR_BUS_STATUS as G}from"./pair-bus.mjs";import{readCloudSessionTranscript as K}from"./cloud-session-transcript.mjs";import{clearSupervisorReady as W,supervisorPresenceEchoed as J,writeSupervisorReady as V}from"./supervisor-ready.mjs";import{PAIR_CLI as X,pairCli as Y}from"./command-guidance.mjs";import{syncRealtimeAuth as Z}from"./design-edit.mjs";import{crossRotationLock as Q}from"./rotation-lock.mjs";import{loadOrCreateBridgeId as ee,bridgeLabel as re}from"./bridge-identity.mjs";const te=(()=>{try{return JSON.parse(n.readFileSync(new URL("./package.json",import.meta.url),"utf8")).version}catch{return null}})(),oe=r(new URL("./bridge.mjs",import.meta.url));export function createStartupAnchor({set:e=setInterval,clear:r=clearInterval}={}){const t=e(()=>{},6e4);let o=!1;return()=>{o||(o=!0,r(t))}}const ne=o.join(i.homedir(),".thinkpool-pair","account.lock");function se(e){if(!e||e===process.pid)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}export function acquireSingleton(e=ne){try{n.mkdirSync(o.dirname(e),{recursive:!0})}catch{return!1}for(let r=0;r<3;r++)try{const r=n.openSync(e,"wx");return n.writeSync(r,String(process.pid)),n.closeSync(r),!0}catch(r){if("EEXIST"!==r.code)return!1;let t=0;try{t=parseInt(n.readFileSync(e,"utf8").trim(),10)||0}catch{return!1}if(se(t))return!1;try{n.rmSync(e,{force:!0})}catch{return!1}}return!1}export function releaseSingleton(e=ne){try{(parseInt(n.readFileSync(e,"utf8").trim(),10)||0)===process.pid&&n.rmSync(e,{force:!0})}catch{}}export function acquireMachineLock(e=ne){return acquireSingleton(e)}export function releaseMachineLock(e=ne){return releaseSingleton(e)}export function shouldHoldMachineLock(e=process.env){return"1"!==e.THINKPOOL_PAIR_ACCOUNT_CHILD}export async function runLogin(e,r,t){const o=s(e,r,{auth:{persistSession:!1}}),{data:n,error:i}=await o.rpc("start_device_code"),c=Array.isArray(n)?n[0]:n;!i&&c?.device_code||(process.stderr.write(`\n ✗ couldn't start login: ${i?.message||"no response"}\n`),process.exit(1));const{device_code:d,user_code:l}=c,p=`${t}/code/link?c=${encodeURIComponent(l)}`;process.stderr.write(`\n ◆ Link this device to your thinkpool account.\n Open (signed in) and approve:\n\n ${p}\n\n code: ${l}\n\n Waiting…\n`);const u=Date.now(),m=async()=>{Date.now()-u>3e5&&(process.stderr.write(`\n ◇ link timed out — run \`${Y("login")}\` again.\n`),process.exit(1));try{const{data:e}=await o.rpc("claim_device_code",{p_device_code:d});"approved"===e?.status&&e.session?.refresh_token&&(a(e.session),process.stderr.write(`\n ◆ linked as ${e.session.email||"your account"}. You can close the browser tab.\n Now run: ${X}\n`),process.exit(0)),"expired"!==e?.status&&"not_found"!==e?.status||(process.stderr.write(`\n ◇ link expired — run \`${Y("login")}\` again.\n`),process.exit(1))}catch{}setTimeout(m,3e3)};m(),await new Promise(()=>{})}const ie=e=>new Promise(r=>{const t=setTimeout(r,e);t.unref?.()});export function withTimeout(e,r,t="op"){return new Promise((o,n)=>{const s=setTimeout(()=>n(new Error(`${t} timed out after ${r}ms`)),r);s.unref?.(),Promise.resolve(e).then(e=>{clearTimeout(s),o(e)},e=>{clearTimeout(s),n(e)})})}export function savedTokenStillValid(e,r=Math.floor(Date.now()/1e3),t=300){if(!e?.access_token)return!1;const o=e.expires_at||function(e){try{const r=String(e).split(".")[1],t=Buffer.from(r.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"),o=JSON.parse(t)?.exp;return"number"==typeof o?o:0}catch{return 0}}(e.access_token);return!!o&&o-r>t}const ae=o.join(i.homedir(),".thinkpool-pair","refresh.lock");export function acquireRefreshLock(e=ae,r=3e4){try{n.mkdirSync(o.dirname(e),{recursive:!0})}catch{}for(let t=0;t<2;t++)try{const r=n.openSync(e,"wx");return n.writeSync(r,JSON.stringify({pid:process.pid,at:Date.now()})),n.closeSync(r),!0}catch(t){if("EEXIST"!==t.code)return!0;let o=1/0;try{o=Date.now()-(JSON.parse(n.readFileSync(e,"utf8"))?.at||0)}catch{o=1/0}if(o<r)return!1;try{n.rmSync(e,{force:!0})}catch{return!0}}return!1}export function releaseRefreshLock(e=ae){try{n.rmSync(e,{force:!0})}catch{}}async function ce(e,r){if(!r?.access_token||!r?.refresh_token)return null;try{const{data:t,error:o}=await e.auth.setSession({access_token:r.access_token,refresh_token:r.refresh_token});return o||!t?.session?null:t.session}catch{return null}}export async function refreshAccountSession(e,r,t={}){const{load:o=c,save:n=a,acquire:s=acquireRefreshLock,release:i=releaseRefreshLock,wait:d=ie,crossLock:l=null}=t,p=async r=>{const t=o();if(t?.refresh_token&&r&&t.refresh_token!==r){const r=await ce(e,t);if(r)return{session:r,adopted:!0}}return null};let u=await p(r);if(u)return u;const m=o()?.refresh_token||r;if(!m)return null;let f=!1;const h=async()=>{if(l&&!f){let e=!0;try{e=await l.acquire()}catch{e=!0}if(!1===e)return!1;f=!0}return s()};let y=await h();if(!y){for(let e=0;e<20;e++){if(await d(250),u=await p(m),u)return f&&await(l?.release?.()),f=!1,u;if(await h()){y=!0;break}}if(!y)return f&&await(l?.release?.()),f=!1,null}try{if(u=await p(m),u)return u;const r=o()?.refresh_token||m,{data:t,error:s}=await e.auth.refreshSession({refresh_token:r});return s||!t?.session?{session:null,error:s||null}:(n(t.session),{session:t.session,adopted:!1})}finally{if(f){f=!1;try{await(l?.release?.())}catch{}}y&&i()}}export const ACCOUNT_RECONNECT_ERROR_CODES=new Set(["refresh_token_not_found","refresh_token_already_used","session_not_found"]);export function accountRefreshNeedsReconnect(e){return ACCOUNT_RECONNECT_ERROR_CODES.has(String(e?.code||""))}export async function authedClient(e,r,{crossLockFor:t}={}){const o=c();if(!o?.refresh_token)return null;if(o.reconnect_required)return{reconnectRequired:!0};const n=s(e,r,{auth:{persistSession:!1,autoRefreshToken:!1}});if(savedTokenStillValid(o)){const e=await ce(n,o);if(e)return{sb:n,session:e}}const i=await refreshAccountSession(n,o.refresh_token,t?{crossLock:t(n)}:{});return i?.session?{sb:n,session:i.session}:accountRefreshNeedsReconnect(i?.error)?(d(i.error.code),{reconnectRequired:!0}):null}export async function authedClientWithRetry(e,r,t={}){const{attempt:o=authedClient,load:n=c,wait:s=ie,onRetry:i=null,maxAttempts:a=1/0,baseMs:d=15e3,capMs:l=3e5,crossLockFor:p=null}=t;let u=d;for(let t=1;t<=a;t++){const c=await o(e,r,{crossLockFor:p});if(c?.reconnectRequired)return c;if(c)return c;if(!n()?.refresh_token)return null;if(t>=a)break;i?.(u,t),await s(u),u=Math.min(2*u,l)}return null}export function claimLoopWedged({lastClaimOkAt:e,now:r,quietMsThreshold:t=4e4}){return r-e>=t}export function scheduleTokenRefresh({sb:e,session:r,onRefreshed:t,onReconnectRequired:o,skewMs:n=9e4,forceMs:s=0,retryMs:i=3e4,refresh:a=refreshAccountSession,crossLock:c=null}){let d=null,l=r,p=!1,u=!1;const m=e=>{p||(clearTimeout(d),d=setTimeout(h,Math.max(1e3,e)),d?.unref&&d.unref())},f=()=>{if(s)return s;const e=l?.expires_at?1e3*l.expires_at:Date.now()+36e5;return Math.max(3e4,e-Date.now()-n)};async function h(){if(!p)try{const r=await a(e,l.refresh_token,c?{crossLock:c}:void 0);if(!r?.session){if(accountRefreshNeedsReconnect(r?.error)&&!u){u=!0;try{await(o?.(r.error))}catch{}}throw r?.error||new Error("no session")}l=r.session;try{e.realtime?.setAuth?.(l.access_token)}catch{}u=!1;try{await(t?.(l))}catch{}m(f())}catch{m(i)}}return m(f()),{cancel:()=>{p=!0,clearTimeout(d)},_tick:h}}export function runBind(e,r){const t=(e||"").toUpperCase().trim();t||(console.error(`usage: ${Y("bind","<ROOM>","<directory>")}`),process.exit(1));const s=o.resolve(r||".");n.existsSync(s)&&n.statSync(s).isDirectory()||(console.error(`✗ not a directory: ${s}`),process.exit(1)),p(t,s),console.error(`✓ bound ${t} → ${s}\n Run \`${X}\` (or restart it) to serve this session.`),process.exit(0)}export async function runAccount(r,s){acquireSingleton()||(console.error(`\n ◇ An account bridge is already running on this machine\n (~/.thinkpool-pair/account.lock). Not starting a second — two would race\n your saved login. Stop the other one first, or share a single room with\n \`${Y("<ROOM>")}\`.\n`),process.exit(0)),W();const c=createStartupAnchor(),X=ee(),ne=t(),se=e=>Q(e,X),ie=await authedClientWithRetry(r,s,{crossLockFor:se,onRetry:e=>process.stderr.write(`\n ◇ Couldn't refresh your account session yet (offline, or another bridge is mid-rotation) — retrying in ${Math.round(e/1e3)}s. Saved login left in place.\n`)});ie||(releaseSingleton(),console.error(`\n ◇ Not linked to a thinkpool account on this machine.\n Link it: ${Y("login")}\n`),process.exit(0)),ie.reconnectRequired&&(c(),releaseSingleton(),console.error("\n ◇ This bridge lost its thinkpool account link.\n Reconnect it: npx thinkpool-pair@latest login\n"),process.exit(0));const{sb:ae,session:ce}=ie,de=ce.user?.email||"your account";let le=ce.access_token||null;const pe=process.cwd();let ue=!1;try{const e=await import("./service.mjs");ue=e.serviceActive(null)}catch{}const me=()=>"1"===process.env.THINKPOOL_PAIR_AUTOUPDATE||ue,fe="1"===process.env.THINKPOOL_PAIR_AUTOUPDATE?" (auto-updating background service — survives reboot, tracks @latest)\n":ue?" (background service — survives reboot, restarts on crash, pinned to this version)\n":` (run hands-off + auto-updating: ${Y("install-service")})\n`,he=f()||y()||pe;process.stderr.write(`\n ◆ thinkpool-pair — account mode · ${de}\n New sessions auto-serve from ${he}${f()?" (your default)":y()?" (your most-recent Claude Code project)":""}\n (change it: ${Y("set-default-dir","<dir>")} · one room: ${Y("bind","<code>","<dir>")})\n${fe}`);const ye=new Map,ge=new F,ve=new Map,we=new Map,be=new Map,ke=new Set,_e=new Map,$e=new Map,Re=new Map,Se=new Map,Te=new Map;let Ie=!1,Oe=null,xe=!1;const Ce=new Map,Ae=new Set,Pe=new Map,qe=i.hostname().replace(/\.local$/,""),Ne=(t?.()||"").slice(0,8);let Ee=null;console.log(`◇ supervisor boot sup=${Ne} host=${qe} pid=${process.pid} npx=${te||"dev"}`),await Z(ae,le,s)||process.stderr.write("\n ⚠ realtime auth could not be initialized; private bridge control is unavailable.\n");const Me=ae.channel(`tpacct:${ce.user.id}`,{config:{presence:{key:qe},broadcast:{self:!1}}}),Ue=ae.channel(`tpacct-control:${ce.user.id}`,{config:{private:!0,broadcast:{self:!1}}});let Le=!1,De=()=>{};Ue.on("broadcast",{event:"bind-room"},({payload:e})=>{const r=String(e?.code||"").toUpperCase().trim();let t=String(e?.dir||"").trim();t.startsWith("~")&&(t=o.join(i.homedir(),t.slice(1))),r&&t&&(n.existsSync(t)?(p(r,t),e?.setDefault&&(h(t),process.stderr.write(`\n ◆ default project dir set → ${t}\n`)),process.stderr.write(`\n ◆ ${r} bound → ${t} (from the web). Serving…\n`),De()):process.stderr.write(`\n ◇ bind ${r}: directory not found (${t}) — ignored.\n`))}),Ue.on("broadcast",{event:"serve-room"},({payload:e})=>{const r=String(e?.code||"").toUpperCase().trim();/^[A-Z0-9]{4,12}$/.test(r)&&De()}),Ue.on("broadcast",{event:"agent-output-hygiene"},({payload:e})=>{if("boolean"==typeof e?.enabled)for(const r of ye.values())try{r.send({t:"agent-output-hygiene",enabled:e.enabled})}catch{}}),Ue.on("broadcast",{event:"shutdown"},async()=>{if(process.stderr.write("\n ◇ disconnect requested from the dashboard — stopping bridge.\n"),setTimeout(()=>{try{releaseSingleton()}catch{}process.exit(0)},2500),me())try{(await import("./service.mjs")).uninstallService(null)}catch{}process.kill(process.pid,"SIGTERM")});let je=!1;Ue.on("broadcast",{event:"restart"},async({payload:e}={})=>{const r=e?.nonce,t=async(e,t)=>{try{await Ue.send({type:"broadcast",event:"restart-status",payload:{nonce:r,ok:e,error:t}})}catch{}};if(je||xe)await t(!1,"a bridge update is already being prepared");else{je=!0;try{const r=await import("./service.mjs"),o=r.serviceActive(null),n=o?r.stageServiceUpdate(null):null;if(o&&!n)return void await t(!1,"the published runtime could not be staged; the existing bridge was left running");Oe=n||("string"==typeof e?.version?e.version:te)||"restart",Ie=!0,await t(!0)}catch(e){return void await t(!1,`restart preparation failed: ${e?.message||e}`)}finally{je=!1}Ir()}});let Fe="init",He="connected";const Be=v(Me,{minMs:5e3,onStatus:e=>{Fe=e}}),ze=R(),Ge=()=>Be({name:qe,bridgeId:ne,version:te,rooms:[...ye.keys()],readyRooms:[...ke],refused:[...Pe].map(([e,r])=>({code:e,reason:r})),service:me(),providerPubKey:ze,providers:S(),agents:L(U()),accountAuthState:He,ts:Date.now()}),Ke=async e=>{try{await withTimeout(ae.rpc("report_bridge_auth_state",{p_bridge_id:X,p_state:e}),5e3,"report_bridge_auth_state")}catch{}},We=(e,r)=>{try{Ue.send({type:"broadcast",event:e,payload:r})}catch{}};Ue.on("broadcast",{event:"master-brief-cleanup"},({payload:e})=>{const r=String(e?.nonce||""),t=String(e?.transcript||"").trim();if(!r||!t||t.length>12e3)return void We("master-brief-cleanup-res",{nonce:r,ok:!1,error:"That recording could not be cleaned up."});const o=[...ke].find(e=>!1!==ye.get(e)?.connected),n=o?ye.get(o):null;if(n)try{n.send({t:"master-brief-cleanup",nonce:r,transcript:t,speaker:e?.speaker})}catch{We("master-brief-cleanup-res",{nonce:r,ok:!1,error:"The connected agent was unavailable."})}else We("master-brief-cleanup-res",{nonce:r,ok:!1,error:"Open a session with a connected agent to clean this Brief."})}),Ue.on("broadcast",{event:"provider-add"},async({payload:e})=>{const r=e?.nonce;let t;try{t=C(e?.sealed)}catch{return We("provider-add-res",{nonce:r,ok:!1,error:"could not decrypt the sealed payload"})}const o=I(t);o.ok&&(Ge(),process.stderr.write(`\n ◆ provider added (${String(t?.name||"").slice(0,40)}) — re-announced.\n`)),We("provider-add-res",{nonce:r,ok:o.ok,error:o.ok?void 0:o.error,id:o.ok?o.id:void 0})}),Ue.on("broadcast",{event:"provider-add-model"},async({payload:e})=>{const r=e?.nonce,t=O({id:e?.id,model:e?.model,name:e?.name});t.ok&&(Ge(),process.stderr.write(`\n ◆ model added to an existing provider (${String(e?.model||"").slice(0,40)}) — re-announced.\n`)),We("provider-add-model-res",{nonce:r,ok:t.ok,error:t.ok?void 0:t.error,id:t.ok?t.id:void 0})}),Ue.on("broadcast",{event:"provider-remove"},async({payload:e})=>{const r=e?.nonce,t=x(e?.id);t.ok&&(Ge(),process.stderr.write("\n ◆ provider removed — re-announced.\n")),We("provider-remove-res",{nonce:r,ok:t.ok,error:t.ok?void 0:t.error})}),Ue.on("broadcast",{event:"providers-list-req"},async({payload:e})=>{const r=e?.nonce;We("providers-list-res",{nonce:r,ok:!0,providers:T()})}),Ue.on("broadcast",{event:"agent-visibility-set"},async({payload:e})=>{const r=e?.nonce,t=U(),o=D(e?.cmd,!0===e?.enabled,{installed:t});if(o.ok){Ge();for(const e of ye.values())try{e.send({t:"agent-visibility-changed"})}catch{}process.stderr.write(`\n ◆ terminal picker agents updated — ${o.agents.filter(e=>e.enabled).map(e=>e.cmd).join(", ")} visible.\n`)}We("agent-visibility-res",{nonce:r,ok:o.ok,error:o.ok?void 0:o.error,agents:o.ok?o.agents:void 0})});let Je=_(),Ve=!1,Xe=!1;const Ye=()=>{if(Xe||!Ve)return;let e=!1;try{e=J(Me.presenceState?.(),qe,ne,te)}catch{e=!1}e&&(Xe=V({version:te,bridgeId:ne}),Xe||process.stderr.write("\n ◇ Could not record local supervisor readiness; the bridge remains running but installers will not claim it was confirmed.\n"))};Me.on("presence",{event:"sync"},()=>{Je=_(Je,"sync"),Ye()});const Ze=await $(Me,e=>{if("SUBSCRIBED"===e)Je=_(Je,e),Ge();else if("CLOSED"===e||"CHANNEL_ERROR"===e||"TIMED_OUT"===e){Je=_(Je,e);try{ae.realtime?.connect?.()}catch{}}});Ze.connected||process.stderr.write(`\n ◇ account realtime not ready at startup (${Ze.status}) — staying alive and recovering in process.\n`);const Qe=await $(Ue,e=>{"CLOSED"!==e&&"CHANNEL_ERROR"!==e&&"TIMED_OUT"!==e||process.stderr.write(`\n ⚠ private account control ${e}; host-changing dashboard actions are unavailable until Realtime reconnects.\n`)});Qe.connected||process.stderr.write(`\n ◇ private account control not ready at startup (${Qe.status}) — presence remains available.\n`);const er=scheduleTokenRefresh({sb:ae,session:ce,crossLock:se(ae),onRefreshed:async e=>{He="connected",a(e),Ge(),await Ke("connected"),le=e.access_token||le;for(const e of ye.values())try{e.send({t:"token",accessToken:le})}catch{}},onReconnectRequired:async e=>{He="reconnect-required",d(e?.code||"refresh-token-invalid"),Ge(),await Ke("reconnect-required"),process.stderr.write("\n ◇ This bridge lost its thinkpool account link.\n Reconnect it: npx thinkpool-pair@latest login\n")},forceMs:parseInt(process.env.TP_TOKEN_REFRESH_FORCE_MS,10)||0});let rr=!1,tr=0,or=null,nr=Date.now(),sr=0,ir=0,ar="";const cr=async()=>{try{const{data:e}=await withTimeout(ae.rpc("claim_bridge",{p_bridge_id:X,p_hostname:qe}),1e4,"claim_bridge");nr=Date.now();const r=Array.isArray(e)?e[0]:e,t=!!r?.held,o=r?.holder_host||"?";return t&&!rr?process.stderr.write("\n ◆ holding this account — serving your sessions (single active bridge).\n"):!t&&rr?process.stderr.write(`\n ◇ another device (${o}) took over this account — standing by.\n`):t||or===o||process.stderr.write(`\n ◇ another device (${o}) is serving this account — standing by.\n (this Mac takes over automatically if that device goes offline.)\n`),rr=t,tr=t&&Number.isSafeInteger(Number(r?.fencing))&&Number(r.fencing)>0?Number(r.fencing):0,or=t?null:o,Ee!==t&&(console.log(`claim_tick sup=${Ne} held=${t} holder=${t?qe:o}`),Ee=t),t}catch{return rr}},dr=H({channels:Se,randomId:t}),lr=e=>dr.resolve(e),pr=(e,r,t)=>dr.requestFirst(e,r,t),ur=(e,r,t)=>dr.requestCollect(e,r,t),mr=async e=>{try{const r=await withTimeout(ae.rpc("list_pair_rooms",{p_code:e}),2500,"paired-room roster");return r?.error||!Array.isArray(r?.data)?{verified:!1,rooms:[]}:{verified:!0,rooms:r.data}}catch{return{verified:!1,rooms:[]}}},fr=(e,r)=>{if(!e||Se.has(e))return;const o=E(r);if(!o)return;const n=ae.channel(o,{config:{private:!0,broadcast:{self:!1}}}),s={channel:n,key:r,status:G.JOINING};n.on("broadcast",{event:"peer-list-req"},({payload:r})=>((e,r,t)=>{if("1"===process.env.TP_PAIRBUS_OFF||"1"===process.env.TP_CROSSROOM_OFF)return;const o=[...ye.keys()].filter(r=>Re.get(r)===e).map(e=>({code:e,name:_e.get(e)||null,host:qe}));try{r.send({type:"broadcast",event:"peer-list-res",payload:{nonce:t?.nonce,rooms:o}})}catch{}})(e,n,r)).on("broadcast",{event:"peer-list-res"},({payload:e})=>lr(e)).on("broadcast",{event:"peer-peek-req"},({payload:r})=>((e,r,o)=>{const n=e=>{try{r.send({type:"broadcast",event:"peer-peek-res",payload:{nonce:o?.nonce,...e}})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF||"1"===process.env.TP_CROSSROOM_OFF)return n({error:"Cross-session reach is off on this device."});const s=String(o?.targetRoom||"").toUpperCase().trim(),i=ye.get(s);if(!i||Re.get(s)!==e)return n({error:`Room ${s} is not a room this device shares with you.`});const a=t();$e.set(a,{reply:e=>n({text:e.text,error:e.error}),askerRoom:null});try{i.send({t:"pair-peek-local",reqId:a,terminal:o.terminal,lines:o.lines})}catch{$e.delete(a),n({error:`Could not reach room ${s}.`})}})(e,n,r)).on("broadcast",{event:"peer-peek-res"},({payload:e})=>lr(e)).on("broadcast",{event:"peer-post-req"},({payload:r})=>((e,r,o)=>{const n=e=>{try{r.send({type:"broadcast",event:"peer-post-res",payload:{nonce:o?.nonce,...e}})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF)return n({error:"Cross-room posting is off on this device."});if(!(e=>{const r=Date.now();let t=Te.get(e);return(!t||r>=t.resetAt)&&(t={count:0,resetAt:r+6e4},Te.set(e,t)),!(t.count>=6||(t.count++,0))})(e))return n({error:"Too many cross-room posts to this device right now — try again shortly."});const s=String(o?.targetRoom||"").toUpperCase().trim(),i=ye.get(s);if(!i||Re.get(s)!==e)return n({error:`Room ${s} is not a room this device shares with you.`});const a=t();$e.set(a,{reply:e=>n({ok:e.ok,ref:e.ref,error:e.error}),askerRoom:null});try{i.send({t:"pair-post-local",reqId:a,fromRoom:o.fromRoom,fromHost:o.fromHost,fromTerminalName:o.fromTerminalName,text:o.text,terminal:o.terminal})}catch{$e.delete(a),n({error:`Could not reach room ${s}.`})}})(e,n,r)).on("broadcast",{event:"peer-post-res"},({payload:e})=>lr(e)).subscribe(e=>{s.status=z(e),"SUBSCRIBED"===e?process.stderr.write("\n ◆ pair bus subscribed — cross-machine Ensemble reach with your partner.\n"):"CHANNEL_ERROR"!==e&&"TIMED_OUT"!==e||process.stderr.write(`\n ◇ pair bus ${e.toLowerCase().replace("_"," ")} — partner sessions are offline until it reconnects.\n`)}),Se.set(e,s)};let hr=0;const yr=async()=>{const e=Date.now();if(!(hr&&e-hr<6e4)){hr=e;try{await gr()}finally{hr===e&&(hr=0)}}};De=()=>{yr().catch(()=>{})};const gr=async()=>{if(await(async()=>{try{const{data:e}=await withTimeout(ae.rpc("enroll_bridge",{p_bridge_id:X,p_label:re(),p_version:te}),1e4,"enroll_bridge"),r=Array.isArray(e)?e[0]:e,t=r?.reason||"";t!==ar&&("revoked"===t?process.stderr.write("\n ◇ this device was revoked from your thinkpool account — standing by.\n Restore it from the account page to serve again.\n"):"foreign"===t&&process.stderr.write("\n ◇ this machine's bridge identity belongs to a different thinkpool account.\n Remove ~/.thinkpool-pair/bridge-id and relink to enroll fresh.\n"),ar=t)}catch{}})(),!await cr()){if(ye.size)for(const[,e]of ye)try{e.kill("SIGTERM")}catch{}return void Ge()}let r=[],s=!1;const a=ce.user.id;try{const{data:e,error:t}=await ae.from("code_sessions").select("code,name,last_active_at,owner_id,participants,closed_at").eq("owner_id",a).is("closed_at",null).order("last_active_at",{ascending:!1}).limit(20);if(t)throw t;r=e||[],s=!0;const o=new Set(r.map(e=>e.code));for(const e of[...Pe.keys()])o.has(e)||Pe.delete(e)}catch{}if(s)for(const e of P({rooms:r,childRooms:ye.keys(),myUid:a})){const r=ye.get(e);if(r){console.log(`child_retire sup=${Ne} room=${e} pid=${r.pid||"?"} reason=not-active-owned`);try{r.kill("SIGTERM")}catch{}}}const c=l(),d=u();for(const s of r){const r=s.code;if(!r||ye.has(r)){r&&Pe.delete(r);continue}if(!A({ownerId:s.owner_id,myUid:a,closedAt:s.closed_at}))continue;const l=q({bound:c[r],remembered:d[r],defaultDir:f()||y()||pe,home:i.homedir()});if(l.refuse){if(Pe.set(r,l.reason),!Ae.has(r)){Ae.add(r);const e="home"===l.reason?`no project directory set — refusing to serve it from your home folder (${l.dir}); the agent would lose its session context and the room would show "${o.basename(l.dir)}" as the repo.`:"no directory to serve it from.";process.stderr.write(`\n ◇ ${r}: ${e}\n Bind it to its repo: ${Y("bind",r,"<repo-dir>")}\n`)}continue}const p=l.dir;if(!n.existsSync(p)){Pe.set(r,"dir-gone"),Ae.has(r)||(Ae.add(r),process.stderr.write(`\n ◇ ${r}: bound directory is gone (${p}) — re-bind it.\n`));continue}Ae.delete(r),Pe.delete(r),l.fromDefault&&m(r,p),_e.set(r,s.name||null),process.stderr.write(`\n ◆ serving ${r}${s.name?` "${s.name}"`:""} → ${p}${c[r]?"":" (default)"}\n`);const u={...process.env,THINKPOOL_PAIR_ACCOUNT_CHILD:"1",TP_ACCOUNT_BRIDGE_ID:X,TP_ACCOUNT_BRIDGE_FENCING:String(tr)};delete u.THINKPOOL_PAIR_AUTOUPDATE,le&&(u.TP_ACCESS_TOKEN=le);const h=ye.has(r),g=j(`spawn the room bridge for ${r}`);if(!g.ok){Ae.has(`memory:${r}`)||(Ae.add(`memory:${r}`),process.stderr.write(`\n ◇ ${r}: ${g.reason}\n`)),Pe.set(r,"host-memory");continue}Ae.delete(`memory:${r}`);const v=e(process.execPath,[oe,r,"--headless","--auto=claude"],{cwd:p,stdio:["inherit","inherit","inherit","ipc"],env:u});console.log(`child_spawn sup=${Ne} room=${r} pid=${v.pid} dir=${p} had_prior=${h}`),ye.set(r,v),ke.delete(r),ve.set(r,!1),we.set(r,!1),be.set(r,!1),v.on("message",e=>{if(e)if("room-ready"===e.t)ke.add(r),console.log(`child_ready sup=${Ne} room=${r} pid=${v.pid}`),Ge();else if("idle"===e.t)ve.set(r,!!e.idle),we.set(r,null!=e.between?!!e.between:!!e.idle),be.set(r,!!e.webPeer),Ir();else if("busy"===e.t)ve.set(r,!1),we.set(r,!1);else if("master-brief-cleanup-res"===e.t)We("master-brief-cleanup-res",{nonce:e.nonce,ok:!0===e.ok,...e.brief?{brief:e.brief}:{},...e.error?{error:e.error}:{}});else if("capacity-acquire"===e.t){const t=ge.acquire({room:r,terminalId:e.terminalId});try{v.send({t:"capacity-acquire-result",reqId:e.reqId,...t})}catch{t.ok&&ge.release({room:r,terminalId:e.terminalId})}}else if("capacity-release"===e.t){if(ge.release({room:r,terminalId:e.terminalId}))for(const e of ye.values())try{e.send({t:"capacity-changed"})}catch{}}else if("restart-probe-result"===e.t){const t=Ce.get(e.nonce);if(!t||!t.pending.has(r)||ye.get(r)!==v)return;t.pending.delete(r),t.ok=t.ok&&!0===e.between,we.set(r,!0===e.between),t.pending.size||(clearTimeout(t.timer),Ce.delete(e.nonce),t.resolve(t.ok))}else if("apply-update"===e.t)Ie=!0,Ir();else if("pair-list-req"===e.t)(async()=>{if("1"===process.env.TP_CROSSROOM_OFF){try{v.send({t:"pair-list-res",reqId:e.reqId,rooms:[],error:"Cross-session reach is turned off on this bridge."})}catch{}return}const t=[...ye.keys()].filter(e=>e!==r).map(e=>({code:e,name:_e.get(e)||null}));let o=[],n=[];if("1"!==process.env.TP_PAIRBUS_OFF){const e=await Promise.all([mr(r),Se.size?ur("peer-list-req",{},1200):Promise.resolve([])]);o=e[0].rooms,n=e[1]}const s=B({authorized:o,replies:n,myUid:ce.user.id,localCodes:ye.keys()});try{v.send({t:"pair-list-res",reqId:e.reqId,rooms:[...t,...s]})}catch{}})();else if("pair-peek-req"===e.t)(async()=>{const o=r=>{try{v.send({t:"pair-peek-res",reqId:e.reqId,...r})}catch{}},n=async(r,t)=>{if(!t.verified)return o({error:`Room ${r}'s pair authorization could not be verified, so its cloud transcript was not read.`});try{const t=await withTimeout(K({sb:ae,targetRoom:r,terminal:e.terminal,lines:e.lines}),5e3,"paired cloud transcript");return o({text:t})}catch(e){return o({error:`Room ${r}'s host bridge is offline, and its cloud transcript could not be read: ${e?.message||e}`})}};if("1"===process.env.TP_CROSSROOM_OFF)return o({error:"Cross-session reach is turned off on this bridge."});const s=String(e.targetRoom||"").toUpperCase().trim();if(!s||s===r)return o({error:`${s||"That"} is not another of your active sessions.`});const i=ye.get(s);if(i){const n=t();$e.set(n,{reply:e=>o({text:e.text,error:e.error}),askerRoom:r});try{i.send({t:"pair-peek-local",reqId:n,terminal:e.terminal,lines:e.lines})}catch{$e.delete(n),o({error:`Could not reach session ${s}.`})}return}if("1"===process.env.TP_PAIRBUS_OFF)return o({error:"Cross-session reach is turned off on this bridge."});const[a,c]=await Promise.all([mr(r),Se.size?ur("peer-list-req",{},800):Promise.resolve([])]);if(a.verified&&!a.rooms.some(e=>e?.code===s&&e?.owner_id!==ce.user.id))return o({error:`Room ${s} is not shared with this pair.`});if(!Se.size)return n(s,a);if(!c.some(e=>e?.rooms?.some(e=>e?.code===s)))return n(s,a);const d=await(l=s,p=e.terminal,u=e.lines,pr("peer-peek-req",{targetRoom:l,terminal:p,lines:u},M.busTimeoutMs));var l,p,u;if(!d)return n(s,a);o({text:d.text,error:d.error})})();else if("pair-post-req"===e.t)(async()=>{const o=r=>{try{v.send({t:"pair-post-res",reqId:e.reqId,...r})}catch{}};if("1"===process.env.TP_PAIRBUS_OFF)return o({error:"Cross-room posting is turned off on this bridge."});const n=String(e.targetRoom||"").toUpperCase().trim();if(!n||n===r)return o({error:`${n||"That"} is not another of your sessions.`});const s=ye.get(n);if(s){const i=t();$e.set(i,{reply:e=>o({ok:e.ok,ref:e.ref,error:e.error}),askerRoom:r});try{s.send({t:"pair-post-local",reqId:i,fromRoom:r,fromHost:qe,fromTerminalName:e.fromTerminalName,text:e.text,terminal:e.terminal})}catch{$e.delete(i),o({error:`Could not reach session ${n}.`})}return}const[i,a]=await Promise.all([mr(r),Se.size?ur("peer-list-req",{},1200):Promise.resolve([])]);if(i.verified&&!i.rooms.some(e=>e?.code===n&&e?.owner_id!==ce.user.id))return o({error:`Room ${n} is not shared with this pair.`});if(!Se.size)return o({error:i.verified?`Room ${n} is paired with you, but its host bridge is offline.`:`Room ${n} is not on this machine, and no pair bus is open to reach it.`});if(!a.some(e=>e?.rooms?.some(e=>e?.code===n)))return o({error:`Room ${n} is paired with you, but its host bridge is offline — no recipient approval card can be raised yet.`});const c=await(d=n,l=e.terminal,p=e.text,u=r,m=e.fromTerminalName,pr("peer-post-req",{targetRoom:d,terminal:l,text:p,fromRoom:u,fromHost:qe,fromTerminalName:m},18e4));var d,l,p,u,m;if(!c)return o({error:`Room ${n} did not respond to the hand-off in time.`});o({ok:c.ok,ref:c.ref,error:c.error})})();else if("pair-peek-res"===e.t||"pair-post-res"===e.t){const r=$e.get(e.reqId);if(!r)return;$e.delete(e.reqId);try{r.reply(e)}catch{}}});const w=e=>{if(console.log(`child_exit sup=${Ne} room=${r} pid=${v?.pid||"?"} code=${e||"?"}`),ye.delete(r),ke.delete(r),ve.delete(r),we.delete(r),be.delete(r),_e.delete(r),Re.delete(r),ge.releaseRoom(r))for(const e of ye.values())try{e.send({t:"capacity-changed"})}catch{}for(const[e,t]of $e)t?.askerRoom===r&&$e.delete(e)};if(v.on("exit",e=>w(e)),v.on("error",e=>{w();try{process.stderr.write(`\n ◇ ${r}: failed to launch its bridge (${e?.message||e}) — retrying next tick.\n`)}catch{}}),Oe)try{v.send({t:"update-available",version:Oe})}catch{}}if("1"!==process.env.TP_PAIRBUS_OFF)for(const e of r){if(!e?.code||!ye.has(e.code))continue;const r=Array.isArray(e.participants)?e.participants:[],t=2===r.length?r.find(e=>e&&e!==ce.user.id):null;if(!t){Re.delete(e.code);continue}Re.set(e.code,t);const o=N(ce.user.id,t);o&&fr(t,o)}Ge()};await yr();const vr=setInterval(yr,15e3);Ve=!0,Ye(),c();const wr=[];let br=!1,kr=0,_r=0,$r=!1;const Rr=setInterval(async()=>{if(br)return;const e=Date.now();if(!claimLoopWedged({lastClaimOkAt:nr,now:e}))return kr=0,void(_r=0);if($r||e<_r)return;$r=!0;const r=k(kr);_r=e+r,kr+=1,console.log(`claim_watchdog sup=${Ne} quiet_ms=${e-nr} attempt=${kr} next_ms=${r} — reconnecting realtime + re-claiming`);try{ae.realtime?.connect?.()}catch{}hr=0;try{await cr(),Ge()}catch{}finally{$r=!1}},2e4);Rr.unref?.();const Sr=setInterval(()=>{if(br)return;const e=Date.now(),r=w({claimHeld:rr,lastClaimOkAt:nr,now:e});let t=!1;try{t=!!Me.presenceState?.()[qe]}catch{t=!1}const o=b({tracking:r,subscribedAt:Math.max(Je.subscribedAt,nr),syncSeenAt:Je.syncSeenAt,present:t,missingSince:sr,now:e});if(sr=o.missingSince,!sr)return void(ir=0);const n=o.verdict;if("recycle"===n){if(ir===sr)return;ir=sr,console.log(`presence_watchdog sup=${Ne} action=recycle missing_ms=${e-sr} track=${Fe} — full realtime disconnect+reconnect`);try{ae.realtime?.disconnect?.()}catch{}try{ae.realtime?.connect?.()}catch{}try{Ge()}catch{}}else"respawn"===n&&(console.log(`presence_watchdog sup=${Ne} action=respawn missing_ms=${e-sr} track=${Fe} — recycle failed, exiting for a clean supervisor respawn`),process.exit(1))},15e3);Sr.unref?.();const Tr=e=>{if(br)return;br=!0;const r=Le?1:0;console.log(`supervisor_stop sup=${Ne} reason=${e||"unknown"} restarting=${Le} children=${ye.size}`),clearInterval(vr),clearInterval(Rr),clearInterval(Sr);for(const e of wr)try{clearInterval(e)}catch{}er.cancel(),W({pid:process.pid}),releaseSingleton();for(const r of ye.values())try{r.kill(e||"SIGTERM")}catch{}dr.close();for(const{channel:e}of Se.values())try{ae.removeChannel(e)}catch{}(async()=>{try{rr&&await ae.rpc("release_bridge",{p_bridge_id:X})}catch{}try{await Me.untrack()}catch{}try{await ae.removeChannel(Ue)}catch{}try{await ae.removeChannel(Me)}catch{}process.exit(r)})(),setTimeout(()=>process.exit(r),1500)};for(const e of["SIGINT","SIGTERM","SIGHUP"])process.on(e,()=>Tr(e));async function Ir(){if(!xe&&g({pendingUpdate:Oe,stopping:br,childIdle:ve,childBetween:we,childPeer:be,requested:Ie})){if(xe=!0,!await function(e=2500){const r=[...ye.entries()];if(!r.length)return Promise.resolve(!0);const o=t();return new Promise(t=>{const n={pending:new Set(r.map(([e])=>e)),ok:!0,resolve:t,timer:null};n.timer=setTimeout(()=>{Ce.delete(o),t(!1)},e),n.timer.unref?.(),Ce.set(o,n);for(const[e,t]of r)try{t.send({t:"restart-probe",nonce:o})}catch{n.pending.delete(e),n.ok=!1}n.pending.size||(clearTimeout(n.timer),Ce.delete(o),t(n.ok))})}())return xe=!1,void process.stderr.write("\n ◆ bridge update still queued — an agent became active before the restart boundary.\n");process.stderr.write(`\n ◆ thinkpool-pair ${Oe} ready (running ${te}) — restarting account bridge to update; sessions resume.\n`);try{const e=await import("./service.mjs");if(e.serviceActive(null)&&!1!==e.applyStagedServiceUpdate(null,Oe))return}catch{}if(xe=!1,Le=!0,!me())try{e(process.execPath,[process.argv[1]],{detached:!0,stdio:"ignore",env:process.env}).unref()}catch{}Tr("SIGTERM")}}if("1"===process.env.THINKPOOL_PAIR_AUTOUPDATE&&te){const e=1e3*Math.max(60,parseInt(process.env.THINKPOOL_PAIR_UPDATE_INTERVAL,10)||1800),r=(e,r)=>{const t=e=>String(e).split("-")[0].split(".").map(e=>parseInt(e,10)||0),o=t(e),n=t(r);for(let e=0;e<3;e++){if((o[e]||0)>(n[e]||0))return!0;if((o[e]||0)<(n[e]||0))return!1}return!1},t=async()=>{const e=new AbortController,r=setTimeout(()=>e.abort(),8e3);try{const r=await fetch("https://registry.npmjs.org/thinkpool-pair/latest",{signal:e.signal,headers:{accept:"application/json"}});if(!r.ok)return null;const t=await r.json();return"string"==typeof t?.version?t.version:null}catch{return null}finally{clearTimeout(r)}},o=async()=>{const e=await t();if(e&&r(e,te)){Oe||process.stderr.write(`\n ◆ thinkpool-pair ${e} published — "update ready" surfaced to each room; applies between turns.\n`);try{(await import("./service.mjs")).provisionRuntime(e)}catch(r){return void process.stderr.write(`\n ⚠ bridge ${e} could not be staged yet: ${r?.message||r}\n`)}Oe=e;for(const r of ye.values())try{r.send({t:"update-available",version:e})}catch{}Ir()}};wr.push(setInterval(o,e).unref()),wr.push(setInterval(Ir,15e3).unref()),wr.push(setTimeout(o,6e4).unref())}await new Promise(()=>{})}
|
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.425",
|
|
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,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"package": "thinkpool-pair",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.425",
|
|
5
5
|
"kind": "minified",
|
|
6
|
-
"minifiedModules":
|
|
7
|
-
"sourceModuleBytes":
|
|
8
|
-
"artifactModuleBytes":
|
|
6
|
+
"minifiedModules": 142,
|
|
7
|
+
"sourceModuleBytes": 1872064,
|
|
8
|
+
"artifactModuleBytes": 854789,
|
|
9
9
|
"files": {
|
|
10
|
-
"package.json": "
|
|
10
|
+
"package.json": "6e386a4599b22678117912acf6c6c0ed6a63882a50f21663d4e18e07405761d8",
|
|
11
11
|
"LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
|
|
12
12
|
"bridge.mjs": "a5c51c1cfc4f1e906a02d2f0ecc6aa2a0e275556359102fe24f3b743f11b648a",
|
|
13
13
|
"bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
|
|
@@ -16,7 +16,6 @@
|
|
|
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",
|
|
@@ -147,7 +146,7 @@
|
|
|
147
146
|
"service.mjs": "412ee48ad7b1dd281063f30f2519b077aa4cc92305e6780ac226293756f6e6f8",
|
|
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/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)}}}}
|