thinkpool-pair 0.7.413 → 0.7.415

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{PEEK as e,cleanPtyTail as r,digestLogEvent as t,formatLaneAge as n,resolveSibling as a,siblingRef as o}from"./cross-terminal.mjs";const i=(e,r)=>{if(e?.error)throw new Error(`${r}: ${e.error.message||e.error}`);return e?.data},s=e=>{const r="number"==typeof e?e:Date.parse(String(e||""));return Number.isFinite(r)&&r>0?r:null},l=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const cloudTerminalRoster=(e,r=Date.now())=>(Array.isArray(e)?e:[]).filter(e=>e?.id).map(e=>{const t="pty"===e.kind?"shell":"agent",n=s(e.last_active_at);return{id:String(e.id),ref:o(e.id),name:String(e.name||""),kind:t,cmd:String(e.agent||("shell"===t?"shell":"think"===e.kind?"think":"agent")),lastActionAt:n,lastActionAgeMs:null==n?null:Math.max(0,r-n),sortOrder:Number.isFinite(Number(e.sort_order))?Number(e.sort_order):Number.MAX_SAFE_INTEGER}}).sort((e,r)=>e.sortOrder-r.sortOrder||e.id.localeCompare(r.id));export const formatCloudTerminalRoster=({room:e,terminals:r,now:t=Date.now()}={})=>{const a=cloudTerminalRoster(r,t);return a.length?`Cloud fallback for room ${e} · partner bridge offline (snapshots may lag the host-local event log).\nDurable terminals (call read_session again with a ref or name to read one):\n${a.map(e=>`- ${l(e)} · ${e.kind} · ${e.cmd} · bridge offline · ${n(e.lastActionAgeMs)}`).join("\n")}`:`Room ${e} is paired with you, but its host bridge is offline and no durable terminal rows are available in the cloud.`};export const formatCloudAgentTranscript=({room:r,terminal:a,snapshot:o,lines:i,now:m=Date.now()}={})=>{const d=Math.min(e.maxLines,Math.max(1,Number(i)||e.defaultLines)),c=o?.payload,u=!0===c?.__txsnap&&String(c.term||"")===a.id&&Array.isArray(c.events),f=u?s(c.ts)||s(o?.created_at):null,h=u?c.events.map(r=>t(r,e.lineCap)).filter(Boolean).slice(-d).join("\n"):"";return`Cloud fallback for room ${r} · partner bridge offline · ${null==f?"unknown snapshot time":`snapshot saved ${n(Math.max(0,m-f))}`} (may lag the host-local event log).\nTerminal ${l(a)} (${a.kind} · ${a.cmd}) — last ${d} readable lines:\n`+(h||"(no durable structured transcript snapshot is available)")};export const formatCloudShellTranscript=({room:t,terminal:n,rows:a,lines:o,archived:i=!1}={})=>{const s=Math.min(e.maxLines,Math.max(1,Number(o)||e.defaultLines)),m=(Array.isArray(a)?a:[]).slice().sort((e,r)=>Number(e.seq)-Number(r.seq)),d=r(m.map(e=>e?.line||"").join("\n"),s).join("\n");return`Cloud fallback for room ${t} · partner bridge offline${i?" · archived scrollback":""} (may lag the host-local event log).\nTerminal ${l(n)} (${n.kind} · ${n.cmd}) — last ${s} readable lines:\n`+(d||"(no durable shell transcript is available)")};export async function readCloudSessionTranscript({sb:r,targetRoom:t,terminal:n,lines:o,now:s=Date.now()}={}){if(!r?.from)throw new Error("cloud transcript client is unavailable");const l=String(t||"").toUpperCase().trim();if(!l)throw new Error("cloud transcript room is missing");const m=Math.min(e.maxLines,Math.max(1,Number(o)||e.defaultLines)),d=await r.from("code_terminals").select("id,name,agent,kind,last_active_at,sort_order").eq("session_code",l).order("sort_order",{ascending:!0}).order("id",{ascending:!0}).limit(100),c=i(d,"durable terminal roster")||[];if(!n)return formatCloudTerminalRoster({room:l,terminals:c,now:s});const u=cloudTerminalRoster(c,s),f=a(u,n);if(!f)return`${formatCloudTerminalRoster({room:l,terminals:c,now:s})}\nNo durable terminal matches "${n}". Use a name or stable ref from the roster above.`;if("agent"===f.kind){const e=await r.from("code_messages").select("payload,created_at").eq("session_code",l).eq("id",`tx:${f.id}`).maybeSingle(),t=i(e,"durable structured transcript");return formatCloudAgentTranscript({room:l,terminal:f,snapshot:t,lines:m,now:s})}const h=await r.from("code_terminal_lines").select("seq,line").eq("terminal_id",f.id).order("seq",{ascending:!1}).limit(m),p=i(h,"durable shell transcript")||[];if(p.length)return formatCloudShellTranscript({room:l,terminal:f,rows:p,lines:m});const b=await r.from("code_terminal_lines_archive").select("seq,line").eq("session_code",l).eq("terminal_id",f.id).order("seq",{ascending:!1}).limit(m),g=i(b,"archived shell transcript")||[];return formatCloudShellTranscript({room:l,terminal:f,rows:g,lines:m,archived:g.length>0})}
1
+ import{PEEK as e,cleanPtyTail as r,digestLogEvent as n,formatLaneAge as t,resolveSibling as a,siblingRef as o}from"./cross-terminal.mjs";const i=(e,r)=>{if(e?.error)throw new Error(`${r}: ${e.error.message||e.error}`);return e?.data},s=e=>{const r="number"==typeof e?e:Date.parse(String(e||""));return Number.isFinite(r)&&r>0?r:null},l=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const cloudTerminalRoster=(e,r=Date.now())=>(Array.isArray(e)?e:[]).filter(e=>e?.id).map(e=>{const n="pty"===e.kind?"shell":"agent",t=s(e.last_active_at);return{id:String(e.id),ref:o(e.id),name:String(e.name||""),kind:n,cmd:String(e.agent||("shell"===n?"shell":"think"===e.kind?"think":"agent")),lastActionAt:t,lastActionAgeMs:null==t?null:Math.max(0,r-t),sortOrder:Number.isFinite(Number(e.sort_order))?Number(e.sort_order):Number.MAX_SAFE_INTEGER}}).sort((e,r)=>e.sortOrder-r.sortOrder||e.id.localeCompare(r.id));export const formatCloudTerminalRoster=({room:e,terminals:r,now:n=Date.now()}={})=>{const a=cloudTerminalRoster(r,n);return a.length?`Cloud fallback for room ${e} · partner bridge offline (snapshots may lag the host-local event log).\nDurable terminals (call read_session again with a ref or name to read one):\n${a.map(e=>`- ${l(e)} · ${e.kind} · ${e.cmd} · bridge offline · ${t(e.lastActionAgeMs)}`).join("\n")}`:`Room ${e} is paired with you, but its host bridge is offline and no durable terminal rows are available in the cloud.`};export const formatCloudAgentTranscript=({room:r,terminal:a,snapshot:o,lines:i,now:m=Date.now()}={})=>{const d=Math.min(e.maxLines,Math.max(1,Number(i)||e.defaultLines)),c=o?.payload,u=!0===c?.__txsnap&&String(c.term||"")===a.id&&Array.isArray(c.events),f=u?s(c.ts)||s(o?.created_at):null,h=u?c.events.map(r=>n(r,e.lineCap)).filter(Boolean).slice(-d).join("\n"):"",p=null==f?"unknown snapshot time":`snapshot saved ${t(Math.max(0,m-f))}`,b=u&&c.handoff&&"object"==typeof c.handoff?c.handoff:null,g=b?["handoff "+(b.sdkSessionId?`warm (${$=b.sdkSessionId,"string"==typeof $&&$.length>12?`${$.slice(0,8)}…`:$||"?"})`:"cold"),b.branch,b.sha?b.sha.slice(0,10):null,b.worktree?`worktree ${b.worktree}`:null].filter(Boolean).join(" · "):null;var $;return`Cloud fallback for room ${r} · partner bridge offline · ${p} (may lag the host-local event log).\nTerminal ${l(a)} (${a.kind} · ${a.cmd}) — last ${d} readable lines:\n`+(g?`${g}\n`:"")+(h||"(no durable structured transcript snapshot is available)")};export const formatCloudShellTranscript=({room:n,terminal:t,rows:a,lines:o,archived:i=!1}={})=>{const s=Math.min(e.maxLines,Math.max(1,Number(o)||e.defaultLines)),m=(Array.isArray(a)?a:[]).slice().sort((e,r)=>Number(e.seq)-Number(r.seq)),d=r(m.map(e=>e?.line||"").join("\n"),s).join("\n");return`Cloud fallback for room ${n} · partner bridge offline${i?" · archived scrollback":""} (may lag the host-local event log).\nTerminal ${l(t)} (${t.kind} · ${t.cmd}) — last ${s} readable lines:\n`+(d||"(no durable shell transcript is available)")};export async function readCloudSessionTranscript({sb:r,targetRoom:n,terminal:t,lines:o,now:s=Date.now()}={}){if(!r?.from)throw new Error("cloud transcript client is unavailable");const l=String(n||"").toUpperCase().trim();if(!l)throw new Error("cloud transcript room is missing");const m=Math.min(e.maxLines,Math.max(1,Number(o)||e.defaultLines)),d=await r.from("code_terminals").select("id,name,agent,kind,last_active_at,sort_order").eq("session_code",l).order("sort_order",{ascending:!0}).order("id",{ascending:!0}).limit(100),c=i(d,"durable terminal roster")||[];if(!t)return formatCloudTerminalRoster({room:l,terminals:c,now:s});const u=cloudTerminalRoster(c,s),f=a(u,t);if(!f)return`${formatCloudTerminalRoster({room:l,terminals:c,now:s})}\nNo durable terminal matches "${t}". Use a name or stable ref from the roster above.`;if("agent"===f.kind){const e=await r.from("code_messages").select("payload,created_at").eq("session_code",l).eq("id",`tx:${f.id}`).maybeSingle(),n=i(e,"durable structured transcript");return formatCloudAgentTranscript({room:l,terminal:f,snapshot:n,lines:m,now:s})}const h=await r.from("code_terminal_lines").select("seq,line").eq("terminal_id",f.id).order("seq",{ascending:!1}).limit(m),p=i(h,"durable shell transcript")||[];if(p.length)return formatCloudShellTranscript({room:l,terminal:f,rows:p,lines:m});const b=await r.from("code_terminal_lines_archive").select("seq,line").eq("session_code",l).eq("terminal_id",f.id).order("seq",{ascending:!1}).limit(m),g=i(b,"archived shell transcript")||[];return formatCloudShellTranscript({room:l,terminal:f,rows:g,lines:m,archived:g.length>0})}
package/flow-models.mjs CHANGED
@@ -1 +1 @@
1
- const e={scaffold:"sonnet",feature:"opus",fix:"opus",review:"opus"},o=["gpt-5.6-luna","gpt-5.4-mini","gpt-5.3-codex-spark"],t=["gpt-5.6-terra","gpt-5.4"],n=["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"],r=Object.freeze({fable:"nous:anthropic/claude-fable-5"});export function normalizeFlowRuntime(e,o=null){return"claude"===e||"codex"===e||"hermes"===e?e:"claude"===o||"codex"===o||"hermes"===o?o:null}export function modelCatalogValues(e=[]){return new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean))}const a=e=>/^nous:/i.test(e)&&(e=>/^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+$/.test(e))(e);export function resolveHermesOpenModel(e,o=[],{strictCatalog:t=!1}={}){const n=String(e||"").trim();if(!n)return{ok:!0,model:void 0,validatedBy:"default"};const i=[...modelCatalogValues(o)];if(i.includes(n)&&a(n))return{ok:!0,model:n,validatedBy:"catalog"};const l=(e=>{const o=r[e.toLowerCase()];if(o)return o;if(/^glm[-_.]/i.test(e))return`nous:z-ai/${e.toLowerCase()}`;const t=e.match(/^([^:/]+):([^/]+)$/);return t?`nous:${"zai"===t[1].toLowerCase()?"z-ai":t[1].toLowerCase()}/${t[2]}`:e})(n),s=e=>String(e).toLowerCase().replace(/[^a-z0-9]+/g,""),u=s(l).replace(/^nous/,""),c=i.filter(e=>s(e).replace(/^nous/,"")===u);return 1===c.length&&a(c[0])?{ok:!0,model:c[0],validatedBy:"catalog"}:t&&i.length?{ok:!1,error:`Could not open a Hermes terminal on ${JSON.stringify(n)} — that model is not in this session's ACP catalog.`}:a(l)?{ok:!0,model:l,validatedBy:"child-runtime"}:{ok:!1,error:`Could not resolve Hermes model ${JSON.stringify(n)}. thinkpool Hermes only routes through Nous Portal; use a full ACP model ID such as "nous:z-ai/glm-5.2".`}}function i(e,t){const n=modelCatalogValues(t);for(const o of e)if(n.has(o))return o;if(e===o)for(const e of n)if(/spark/i.test(e))return e}function l(e,o){const t=modelCatalogValues(e);for(const e of o)for(const o of t)if(e.test(o))return o}export function flowLaneModelFor({sliceType:r,runtime:a="claude",catalog:s=[],env:u=process.env}={}){if("codex"===normalizeFlowRuntime(a,"claude")){const e=u.TP_FLOW_CODEX_LANE_MODEL;if("inherit"===e)return;return e?modelCatalogValues(s).has(e)?e:void 0:i("scaffold"===r?o:t,s)}if("hermes"===normalizeFlowRuntime(a,"claude")){const e=u.TP_FLOW_HERMES_LANE_MODEL,o=modelCatalogValues(s);if("inherit"===e)return;return e?o.has(e)?e:void 0:"review"===r?i(n,s)||l(s,[/terra/i,/sonnet/i,/balanced/i]):l(s,"scaffold"===r?[/(?:luna|mini)/i]:[/terra/i,/(?:luna|mini)/i])}const c=u.TP_FLOW_CLAUDE_LANE_MODEL||u.TP_FLOW_LANE_MODEL;if("inherit"!==c)return c||e[r]||"opus"}export function flowConductorModelFor({runtime:e="claude",originModel:o,catalog:t=[],env:n=process.env}={}){if("codex"===normalizeFlowRuntime(e,"claude")){const e=n.TP_FLOW_CODEX_CONDUCTOR_MODEL;if("inherit"===e)return;const r=modelCatalogValues(t);return e?r.has(e)?e:void 0:o&&r.has(o)?o:void 0}if("hermes"===normalizeFlowRuntime(e,"claude")){const e=n.TP_FLOW_HERMES_CONDUCTOR_MODEL,r=modelCatalogValues(t);if("inherit"===e)return;return e?r.has(e)?e:void 0:o&&r.has(o)?o:void 0}const r=n.TP_FLOW_CLAUDE_CONDUCTOR_MODEL||n.TP_FLOW_CONDUCTOR_MODEL;if("inherit"!==r)return r||void 0}export function spawnedLaneModelFor({explicitModel:e,sliceType:o,runtime:t="claude",catalog:n=[],env:r=process.env}={}){return e||flowLaneModelFor({sliceType:o,runtime:t,catalog:n,env:r})}export function resolveCodexModel(e,o=[]){if(e)return modelCatalogValues(o).has(e)?e:void 0}export function assertRuntimeModelCompatible({runtime:e="claude",provider:o=null,model:t}={}){if("claude"===e&&(!o||"anthropic"===o)&&/^gpt-/i.test(String(t||"")))throw new Error(`Codex model ${JSON.stringify(t)} cannot run on the built-in Claude provider`);return!0}
1
+ const e={scaffold:"sonnet",feature:"opus",fix:"opus",review:"opus"},o=["gpt-5.6-luna","gpt-5.4-mini","gpt-5.3-codex-spark"],t=["gpt-5.6-terra","gpt-5.4"],n=["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"],r=Object.freeze({fable:"nous:anthropic/claude-fable-5"});export function normalizeFlowRuntime(e,o=null){return"claude"===e||"codex"===e||"hermes"===e?e:"claude"===o||"codex"===o||"hermes"===o?o:null}export function modelCatalogValues(e=[]){return new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean))}const a=e=>/^nous:/i.test(e)&&(e=>/^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+$/.test(e))(e);export function resolveHermesOpenModel(e,o=[],{strictCatalog:t=!1}={}){const n=String(e||"").trim();if(!n)return{ok:!0,model:void 0,validatedBy:"default"};const l=[...modelCatalogValues(o)];if(l.includes(n)&&a(n))return{ok:!0,model:n,validatedBy:"catalog"};const i=(e=>{const o=r[e.toLowerCase()];if(o)return o;if(/^glm[-_.]/i.test(e))return`nous:z-ai/${e.toLowerCase()}`;const t=e.match(/^([^:/]+):([^/]+)$/);return t?`nous:${"zai"===t[1].toLowerCase()?"z-ai":t[1].toLowerCase()}/${t[2]}`:e})(n),s=e=>String(e).toLowerCase().replace(/[^a-z0-9]+/g,""),u=s(i).replace(/^nous/,""),c=l.filter(e=>s(e).replace(/^nous/,"")===u);return 1===c.length&&a(c[0])?{ok:!0,model:c[0],validatedBy:"catalog"}:t&&l.length?{ok:!1,error:`Could not open a Hermes terminal on ${JSON.stringify(n)} — that model is not in this session's ACP catalog.`}:a(i)?{ok:!0,model:i,validatedBy:"child-runtime"}:{ok:!1,error:`Could not resolve Hermes model ${JSON.stringify(n)}. thinkpool Hermes only routes through Nous Portal; use a full ACP model ID such as "nous:z-ai/glm-5.2".`}}function l(e,t){const n=modelCatalogValues(t);for(const o of e)if(n.has(o))return o;if(e===o)for(const e of n)if(/spark/i.test(e))return e}function i(e,o){const t=modelCatalogValues(e);for(const e of o)for(const o of t)if(e.test(o))return o}export function flowLaneModelFor({sliceType:r,runtime:a="claude",catalog:s=[],env:u=process.env}={}){if("codex"===normalizeFlowRuntime(a,"claude")){const e=u.TP_FLOW_CODEX_LANE_MODEL;if("inherit"===e)return;return e?modelCatalogValues(s).has(e)?e:void 0:l("scaffold"===r?o:t,s)}if("hermes"===normalizeFlowRuntime(a,"claude")){const e=u.TP_FLOW_HERMES_LANE_MODEL,o=modelCatalogValues(s);if("inherit"===e)return;return e?o.has(e)?e:void 0:"review"===r?l(n,s)||i(s,[/terra/i,/sonnet/i,/balanced/i]):i(s,"scaffold"===r?[/(?:luna|mini)/i]:[/terra/i,/(?:luna|mini)/i])}const c=u.TP_FLOW_CLAUDE_LANE_MODEL||u.TP_FLOW_LANE_MODEL;if("inherit"!==c)return c||e[r]||"opus"}export function flowConductorModelFor({runtime:e="claude",originModel:o,catalog:t=[],env:n=process.env}={}){if("codex"===normalizeFlowRuntime(e,"claude")){const e=n.TP_FLOW_CODEX_CONDUCTOR_MODEL;if("inherit"===e)return;const r=modelCatalogValues(t);return e?r.has(e)?e:void 0:o&&r.has(o)?o:void 0}if("hermes"===normalizeFlowRuntime(e,"claude")){const e=n.TP_FLOW_HERMES_CONDUCTOR_MODEL,r=modelCatalogValues(t);if("inherit"===e)return;return e?r.has(e)?e:void 0:o&&r.has(o)?o:void 0}const r=n.TP_FLOW_CLAUDE_CONDUCTOR_MODEL||n.TP_FLOW_CONDUCTOR_MODEL;if("inherit"!==r)return r||void 0}export function spawnedLaneModelFor({explicitModel:e,sliceType:o,runtime:t="claude",catalog:n=[],env:r=process.env}={}){return e||flowLaneModelFor({sliceType:o,runtime:t,catalog:n,env:r})}export function resolveCodexModel(e,o=[]){if(e)return modelCatalogValues(o).has(e)?e:void 0}export function codexSpawnModelFor(e,o=[]){return resolveCodexModel(e,o)||flowLaneModelFor({sliceType:"feature",runtime:"codex",catalog:o})}export function assertRuntimeModelCompatible({runtime:e="claude",provider:o=null,model:t}={}){if("claude"===e&&(!o||"anthropic"===o)&&/^gpt-/i.test(String(t||"")))throw new Error(`Codex model ${JSON.stringify(t)} cannot run on the built-in Claude provider`);return!0}
@@ -0,0 +1 @@
1
+ import t from"node:crypto";import{promisify as e}from"node:util";import r from"node:zlib";const n=e(r.gzip),o=20971520;function s(e){return t.createHash("sha256").update(e).digest("hex")}function i({accountId:t,room:e,terminalId:r}){return`handoff/${t}/${e}/${r}.jsonl.gz`}function a({session:t}){return!(!t||!0!==t.turnActive)}function l(t){if(!Array.isArray(t))return null;for(let e=t.length-1;e>=0;e--){const r=t[e]?.work;if(r&&"object"==typeof r){const t=r.identity?.sdkSessionId||null,e=r.git?.branch||null,n=r.git?.headSha||null,o=r.git?.worktreeState||null,s=r.capturedAt||null;return t||e||n?{sdkSessionId:t,branch:e,sha:n,worktree:o,capturedAt:s}:null}}return null}function c(t){if(!Array.isArray(t))return"";const e=new Set(["you","assistant","tool_use","tool_result","result","error","attachment","propose","pool","__struct","__txsnap"]);return t.filter(t=>t&&"object"==typeof t&&e.has(t.kind)).slice(-1e4).map(t=>JSON.stringify(t)).join("\n")+"\n"}async function d(t){let e=t.split("\n").filter(Boolean);const r=async()=>{const t=e.slice(0,Math.floor(.7*e.length)).join("\n")+"\n",r=await n(t,{level:9});return{compressed:r,bytes:r.byteLength,truncated:!0}};try{const s=await n(t,{level:9});if(s.byteLength<=o)return{compressed:s,bytes:s.byteLength,truncated:!1};const i=await r();if(i.bytes<=o)return i;e=e.slice(0,Math.floor(.3*e.length));const a=await r();return a.bytes<=o?a:{bytes:a.bytes,compressed:null,truncated:!0}}catch(t){return{bytes:0,compressed:null,truncated:!1,error:t?.message||String(t)}}}export async function exportHandoffBundles({sb:t,room:e,terminals:r=[],env:n={},log:o=()=>{}}){const{accountId:u,bridgeId:f,fencing:p}=n;if(!t)throw new Error("exportHandoffBundles requires sb (Supabase client)");if(!e)throw new Error("exportHandoffBundles requires room (session code)");if(!u)throw new Error("exportHandoffBundles requires env.accountId");if(!f)throw new Error("exportHandoffBundles requires env.bridgeId");let h=0,m=0,w=0;for(const n of r){const r=n?.id;if(r)try{if(a(n)){o(`[handoff-out] terminal ${r}: skipped (turn in flight)`),m++;continue}const y=n.session||{},g=n.transcript||[],b=l(g)||{},{sdkSessionId:$,branch:_,sha:k}=b,I=c(g);I||o(`[handoff-out] terminal ${r}: empty transcript, writing minimal bundle`);const x=await d(I),{compressed:S,bytes:A,truncated:B}=x;let v=null,H=null,j=null;if(S){H=A,j=s(S);const n=i({accountId:u,room:e,terminalId:r});try{const{error:e}=await t.storage.from("code-handoffs").upload(n,S,{contentType:"application/gzip",upsert:!0});if(e)throw e;v=n,o(`[handoff-out] terminal ${r}: uploaded transcript (${A} bytes${B?", truncated":""})`)}catch(t){o(`[handoff-out] terminal ${r}: upload failed: ${t?.message||t}`),v=null}}else o(`[handoff-out] terminal ${r}: transcript too large (${A} bytes cap exceeded), writing minimal bundle`);const q={session_code:e,terminal_id:r,from_bridge_id:f,lease_generation:Number(p)||0,sdk_session_id:$,branch:_,sha:k,transcript_path:v,transcript_bytes:H,transcript_sha256:j,cwd:y.cwd||null,warm:!!($&&_&&k)};try{const{error:e}=await t.from("code_session_handoffs").insert(q);if(e)throw e;h++,o(`[handoff-out] terminal ${r}: wrote handoff row (warm=${q.warm})`)}catch(t){o(`[handoff-out] terminal ${r}: DB insert failed: ${t?.message||t}`),w++}}catch(t){o(`[handoff-out] terminal ${n.id||"?"}: error: ${t?.message||t}`),w++}else o("[handoff-out] skipping terminal with missing id")}return o(`[handoff-out] complete: written=${h}, skipped=${m}, errors=${w}`),{written:h,skipped:m,errors:w}}
@@ -139,6 +139,12 @@ tools.mcp_tool.discover_mcp_tools = lambda *args, **kwargs: []
139
139
  import model_tools
140
140
  _get_definitions = model_tools.get_tool_definitions
141
141
  def constrained_definitions(*args, **kwargs):
142
+ # Hermes 0.20.4 tool_search (default "auto") defers ANY MCP/plugin tool off
143
+ # the model-facing surface and swaps in tool_search/tool_describe/tool_call
144
+ # bridge tools. thinkpool's exact-inventory contract requires the thinkpool
145
+ # MCP tools to be real, present tools every turn, so the bridge is always
146
+ # skipped here: the full role schema stays eager.
147
+ kwargs["skip_tool_search_assembly"] = True
142
148
  return filter_schemas(_get_definitions(*args, **kwargs))
143
149
  model_tools.get_tool_definitions = constrained_definitions
144
150
 
@@ -1 +1 @@
1
- import{execFileSync as e}from"node:child_process";const t=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/,n=/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/,r=new Set(["claude","codex","hermes"]),o=new Set(["default","acceptEdits","plan","review","bypassPermissions"]),i=new Set(["main","worker","side","flow-conductor","flow-builder","flow-reviewer","scheduled"]),a=new Set(["clean","dirty","unavailable"]),l=/(?:\r|\n|(?:^|[\\/])(?:Users|home|private|tmp|var|workspace)(?:[\\/]|$)|\.thinkpool[\\/]|(?:api[_-]?key|authorization|bearer|password|secret)\b)/i,u=1e5;function d(e){if("string"!=typeof e)return null;const n=e.trim();return t.test(n)?n:null}function s(e,t=120){if("string"!=typeof e)return null;const n=e.replace(/\s+/g," ").trim();return!n||n.length>t||l.test(n)?null:n}function c(e){if("string"!=typeof e)return null;const t=e.trim().toLowerCase();return n.test(t)?t:null}function f(e={}){return"conductor"===e.flowRole?"flow-conductor":"reviewer"===e.flowRole||"review"===e.sliceType?"flow-reviewer":e.flowSessionId||e.flowTaskKey||"builder"===e.flowRole?"flow-builder":e.scheduleRunId?"scheduled":e.sideParent?"side":e.spawnedBy?"worker":"main"}function g(e){return new Set(String(e||"").split("\0").filter(Boolean))}export function readLaneWorkGit(t,n=e,r=null){if("string"!=typeof t||!t.trim())return{worktreeState:"unavailable"};let o=null;try{o=c(n("git",["-C",t,"rev-parse","HEAD"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}))}catch{return{worktreeState:"unavailable"}}try{const e=n("git",["-C",t,"status","--porcelain=v1","--untracked-files=all"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),i={...o?{headSha:o}:{},worktreeState:String(e||"").trim()?"dirty":"clean"},a="clean"===r?.worktreeState?c(r?.headSha):null;if(a){if(o&&(i.headChanged=o!==a,i.headChanged)){const e=function(e,t,n){try{return s(n("git",["-C",e,"log","-1","--format=%s",t],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}))}catch{return null}}(t,o,n);e&&(i.headSubject=e)}const e=function(e,t,n){try{const r=n("git",["-C",e,"diff","--name-only","-z","--no-renames",t,"--"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),o=n("git",["-C",e,"ls-files","--others","--exclude-standard","-z"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),i=g(r);for(const e of g(o))i.add(e);return Math.min(i.size,u)}catch{return null}}(t,a,n);null!==e&&(i.changedFileCount=e)}return i}catch{return{...o?{headSha:o}:{},worktreeState:"unavailable"}}}export function normalizeLaneWorkSnapshot(e){if(!e||"object"!=typeof e||Array.isArray(e)||![1,2].includes(e.version))return null;const t=d(e.terminalId),n=Number.isSafeInteger(e.turnRev)&&e.turnRev>0?e.turnRev:null,l="string"==typeof e.capturedAt&&Number.isFinite(Date.parse(e.capturedAt))?new Date(e.capturedAt).toISOString():null,f=e.identity,g=r.has(f?.runtime)?f.runtime:null,p=i.has(f?.laneRole)?f.laneRole:null,h=a.has(e.git?.worktreeState)?e.git.worktreeState:null;if(!(t&&n&&l&&g&&p&&h))return null;const w={runtime:g,laneRole:p},m=s(f.model),S=o.has(f.mode)?f.mode:null,y=d(f.parentTerminalId),k=d(f.flowId),v=d(f.flowTaskKey);m&&(w.model=m),S&&(w.mode=S),y&&(w.parentTerminalId=y),k&&(w.flowId=k),v&&(w.flowTaskKey=v);const b={worktreeState:h},C=c(e.git?.headSha),I="boolean"==typeof e.git?.headChanged?e.git.headChanged:null,R=!0===I?s(e.git?.headSubject):null,A=Number.isSafeInteger(e.git?.changedFileCount)&&e.git.changedFileCount>=0&&e.git.changedFileCount<=u?e.git.changedFileCount:null;return C&&(b.headSha=C),null!==I&&(b.headChanged=I),R&&(b.headSubject=R),null!==A&&(b.changedFileCount=A),{version:e.version,terminalId:t,turnRev:n,capturedAt:l,identity:w,git:b}}export function buildLaneWorkSnapshot(t,{now:n=Date.now(),gitFacts:r}={}){const o=t?.sideParent||("string"!=typeof t?.spawnedBy||t.spawnedBy.startsWith("flow:")?null:t.spawnedBy),i={runtime:t?.runtime,model:t?.model,mode:t?.mode,laneRole:f(t),parentTerminalId:o,flowId:t?.flowSessionId,flowTaskKey:t?.flowTaskKey};return normalizeLaneWorkSnapshot({version:2,terminalId:t?.id,turnRev:Number(t?._turnRev),capturedAt:new Date(n).toISOString(),identity:i,git:r||readLaneWorkGit(t?.cwd,e,t?._turnGitStart)})}export function workerCompletionGitFacts(e){const t=normalizeLaneWorkSnapshot(e);return t?{...t.git}:null}
1
+ import{execFileSync as e}from"node:child_process";const t=/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/,n=/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/,r=new Set(["claude","codex","hermes"]),i=new Set(["default","acceptEdits","plan","review","bypassPermissions"]),o=new Set(["main","worker","side","flow-conductor","flow-builder","flow-reviewer","scheduled"]),a=new Set(["clean","dirty","unavailable"]),l=/(?:\r|\n|(?:^|[\\/])(?:Users|home|private|tmp|var|workspace)(?:[\\/]|$)|\.thinkpool[\\/]|(?:api[_-]?key|authorization|bearer|password|secret)\b)/i,u=1e5;function s(e){if("string"!=typeof e)return null;const n=e.trim();return t.test(n)?n:null}function d(e,t=120){if("string"!=typeof e)return null;const n=e.replace(/\s+/g," ").trim();return!n||n.length>t||l.test(n)?null:n}function c(e){if("string"!=typeof e)return null;const t=e.trim().toLowerCase();return n.test(t)?t:null}const f=/^[A-Za-z0-9][A-Za-z0-9._/-]{0,110}$/;function g(e){if("string"!=typeof e)return null;const t=e.trim();return"HEAD"===t||t.includes("..")||/^[0-9a-f]{7,64}$/i.test(t)?null:f.test(t)?t:null}function p(e={}){return"conductor"===e.flowRole?"flow-conductor":"reviewer"===e.flowRole||"review"===e.sliceType?"flow-reviewer":e.flowSessionId||e.flowTaskKey||"builder"===e.flowRole?"flow-builder":e.scheduleRunId?"scheduled":e.sideParent?"side":e.spawnedBy?"worker":"main"}function h(e){return new Set(String(e||"").split("\0").filter(Boolean))}export function readLaneWorkGit(t,n=e,r=null){if("string"!=typeof t||!t.trim())return{worktreeState:"unavailable"};let i=null,o=null;try{i=c(n("git",["-C",t,"rev-parse","HEAD"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]})),o=function(e,t){try{return g(t("git",["-C",e,"rev-parse","--abbrev-ref","HEAD"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}))}catch{return null}}(t,n)}catch{return{worktreeState:"unavailable"}}try{const e=n("git",["-C",t,"status","--porcelain=v1","--untracked-files=all"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),a={...i?{headSha:i}:{},...o?{branch:o}:{},worktreeState:String(e||"").trim()?"dirty":"clean"},l="clean"===r?.worktreeState?c(r?.headSha):null;if(l){if(i&&(a.headChanged=i!==l,a.headChanged)){const e=function(e,t,n){try{return d(n("git",["-C",e,"log","-1","--format=%s",t],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}))}catch{return null}}(t,i,n);e&&(a.headSubject=e)}const e=function(e,t,n){try{const r=n("git",["-C",e,"diff","--name-only","-z","--no-renames",t,"--"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),i=n("git",["-C",e,"ls-files","--others","--exclude-standard","-z"],{encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}),o=h(r);for(const e of h(i))o.add(e);return Math.min(o.size,u)}catch{return null}}(t,l,n);null!==e&&(a.changedFileCount=e)}return a}catch{return{...i?{headSha:i}:{},...o?{branch:o}:{},worktreeState:"unavailable"}}}export function normalizeLaneWorkSnapshot(e){if(!e||"object"!=typeof e||Array.isArray(e)||![1,2].includes(e.version))return null;const t=s(e.terminalId),n=Number.isSafeInteger(e.turnRev)&&e.turnRev>0?e.turnRev:null,l="string"==typeof e.capturedAt&&Number.isFinite(Date.parse(e.capturedAt))?new Date(e.capturedAt).toISOString():null,f=e.identity,p=r.has(f?.runtime)?f.runtime:null,h=o.has(f?.laneRole)?f.laneRole:null,w=a.has(e.git?.worktreeState)?e.git.worktreeState:null;if(!(t&&n&&l&&p&&h&&w))return null;const m={runtime:p,laneRole:h},S=d(f.model),y=i.has(f.mode)?f.mode:null,k=s(f.parentTerminalId),b=s(f.flowId),v=s(f.flowTaskKey),I=s(f.sdkSessionId);S&&(m.model=S),y&&(m.mode=y),k&&(m.parentTerminalId=k),b&&(m.flowId=b),v&&(m.flowTaskKey=v),I&&(m.sdkSessionId=I);const C={worktreeState:w},A=c(e.git?.headSha),R=g(e.git?.branch),z="boolean"==typeof e.git?.headChanged?e.git.headChanged:null,F=!0===z?d(e.git?.headSubject):null,T=Number.isSafeInteger(e.git?.changedFileCount)&&e.git.changedFileCount>=0&&e.git.changedFileCount<=u?e.git.changedFileCount:null;return A&&(C.headSha=A),R&&(C.branch=R),null!==z&&(C.headChanged=z),F&&(C.headSubject=F),null!==T&&(C.changedFileCount=T),{version:e.version,terminalId:t,turnRev:n,capturedAt:l,identity:m,git:C}}export function buildLaneWorkSnapshot(t,{now:n=Date.now(),gitFacts:r}={}){const i=t?.sideParent||("string"!=typeof t?.spawnedBy||t.spawnedBy.startsWith("flow:")?null:t.spawnedBy),o={runtime:t?.runtime,model:t?.model,mode:t?.mode,laneRole:p(t),parentTerminalId:i,flowId:t?.flowSessionId,flowTaskKey:t?.flowTaskKey,sdkSessionId:t?.session?.sessionId||null};return normalizeLaneWorkSnapshot({version:2,terminalId:t?.id,turnRev:Number(t?._turnRev),capturedAt:new Date(n).toISOString(),identity:o,git:r||readLaneWorkGit(t?.cwd,e,t?._turnGitStart)})}export function workerCompletionGitFacts(e){const t=normalizeLaneWorkSnapshot(e);return t?{...t.git}:null}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.413",
3
+ "version": "0.7.415",
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",
@@ -39,6 +39,9 @@
39
39
  "lane-worktree.mjs",
40
40
  "lane-work-snapshot.mjs",
41
41
  "branch-checkpoint.mjs",
42
+ "bridge-identity.mjs",
43
+ "rotation-lock.mjs",
44
+ "switch-runtime.mjs",
42
45
  "permission-rules.mjs",
43
46
  "public-proof.mjs",
44
47
  "codex-event-mapper.mjs",
@@ -124,6 +127,7 @@
124
127
  "past-work-search.mjs",
125
128
  "account-briefs.mjs",
126
129
  "voice-briefs.mjs",
130
+ "handoff-out.mjs",
127
131
  "review-check.mjs",
128
132
  "flow-review-gate.mjs",
129
133
  "flow-review-reflect.mjs",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": 1,
3
3
  "package": "thinkpool-pair",
4
- "version": "0.7.413",
4
+ "version": "0.7.415",
5
5
  "kind": "minified",
6
- "minifiedModules": 136,
7
- "sourceModuleBytes": 1830714,
8
- "artifactModuleBytes": 836688,
6
+ "minifiedModules": 140,
7
+ "sourceModuleBytes": 1866843,
8
+ "artifactModuleBytes": 846856,
9
9
  "files": {
10
- "package.json": "5e46a3049f556907869cb812c4d2de21f3dbad69c633f4f1d37ca41a6e9af35d",
10
+ "package.json": "e89a4b6f201b4f1bd545494e21b5c96994d7d29b1dc6339eeb551862f88f61fd",
11
11
  "LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
12
- "bridge.mjs": "0b765d303506c195ec3a999f250548f93f0220daf90cdb299bbf2cf5c0f43b14",
12
+ "bridge.mjs": "8bb2fd26335b608e7701a1d3121f6779e8155f7cd3441d4727e30ad0a95b43a9",
13
13
  "bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
14
14
  "command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
15
15
  "abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
@@ -37,8 +37,11 @@
37
37
  "codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
38
38
  "codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
39
39
  "lane-worktree.mjs": "20a4327b501d3771c8e41e0908926d50c477db9d76c41d5243204d72d20417e8",
40
- "lane-work-snapshot.mjs": "ba40233059ea86ba2e08d0b61763427baa9602eb6edf6c445a6b22fd15af212c",
40
+ "lane-work-snapshot.mjs": "85af98593d5372d50b133aa20317503491925624af2b781fc0e780b88bbde3ed",
41
41
  "branch-checkpoint.mjs": "6301c39df24b0343aacb05c13aad4a37dcce1c1acee3ffdf7465fc51db6acaa2",
42
+ "bridge-identity.mjs": "71437cba17c22d7190afda94a677cb636526c472c6c1a4e226000b571c255d8d",
43
+ "rotation-lock.mjs": "ce2da6d9e72f18fc78d7300e07844f3d588810a8136109a77b232bff2d41e65d",
44
+ "switch-runtime.mjs": "af0c89aa25d6ad756617688b80506d8ce6dbfe23eded8fcca49a0e5859caac66",
42
45
  "permission-rules.mjs": "552a9e33dc85ea2dbbe168d6335fcee4f11ef7c0a5b47fd8d9c5b74d24c149ef",
43
46
  "public-proof.mjs": "00e010d629e0bb2ab859167037e65b6c90986a23cb7df0fb5f2c898972c34581",
44
47
  "codex-event-mapper.mjs": "a7c423806b5e80957733c21d756078740773ae7230305ed00dba72cecb3a558a",
@@ -56,7 +59,7 @@
56
59
  "runtime-compatibility-profile.mjs": "137730baf95d315723872e8df80e66305bccb909e3c6cb81ae753ea9adadac84",
57
60
  "hermes-model-cache.mjs": "aee23efdebe1aa5e10053f291a9153981fe9b45065c293c9519ca73477251e72",
58
61
  "hermes-policy.mjs": "df6dd384ab130b3626045816b6ac4c1d2aecde37fb7b7a4be0c1bffc375fd8ff",
59
- "hermes-acp-bootstrap.py": "621ea1e0de6a30645d36dd1f40411444fc81c20e7fec6849d342dcf6cb88eeb1",
62
+ "hermes-acp-bootstrap.py": "f6e389610d4621e57242afdd7ca9d87bc4488f899bc641a813c0072ea0e7345a",
60
63
  "hermes-event-mapper.mjs": "7c714e15d06d970c4d602e755461bacf8f4d4136a8afc03406e8b3207ff84cdc",
61
64
  "hermes-probe.mjs": "3fc419707f0df9342bc3a5fbfbba2ec2ba1511309dfb1ad8cc79f6f8da4e6b3d",
62
65
  "hermes-setup.mjs": "50840b3e0101dc94aa2cd6dd2456efd8377e30bd5d797a00d360fae28e13dddb",
@@ -99,7 +102,7 @@
99
102
  "side-lane.mjs": "306d451e2d163ecdf40030d886a338770b11bc57952543a107e422f95ca49d45",
100
103
  "worker-completion.mjs": "72210efc40e5b3861e60fedff9cd455956563c6874270617e85573e58b5586a0",
101
104
  "cross-terminal.mjs": "1e8b3320b0bd79ee8d532cc2fbf1825e46428c1c4c5efbb3877c69e1e8e3a21e",
102
- "cloud-session-transcript.mjs": "1e8b3000d628eac0cff171901d6fed13983528b42e23e277583ef65bf8f9fa2b",
105
+ "cloud-session-transcript.mjs": "5d831340b30f23b6f1caed90ce5b89ffc7eba37ada708b0811ab8b44060e3f9b",
103
106
  "pair-bus.mjs": "cbc28cee7f4f06b2213091e0b7b07d61c1d01f0ff207c5c6d808cd616ece221e",
104
107
  "direct-pair-room.mjs": "92557b54307da2ca31f0a0d090e44b019a23b1ab32a7dfd6cbcb9d55fd761736",
105
108
  "lane-lifecycle.mjs": "9b5ae3d105c30e3ad37f1f1f792200da2dd4d2bb5c4979a2a69618373e58f1c8",
@@ -110,7 +113,7 @@
110
113
  "flow-conductor.mjs": "79e3f04fddc4ffa83dbda047a1e9bf01d73450079e9d63905a04f831261db543",
111
114
  "flow-worktree.mjs": "d9579a9b2eb8eff46c0708b2e3f546f5a6a3cded1ff726ab334cac9a48544e90",
112
115
  "flow-task-graph.mjs": "52f555219c6c940c5d33c6c429d132ab2ded46f9ac6d5207bd5ad972067493bf",
113
- "flow-models.mjs": "735192dd2a67a1c790c77aa8e34b4e42817bef27da72853bad10941c65fccb86",
116
+ "flow-models.mjs": "ef34d9a2b091280fd1cc289e77843bb4606f43534cca8001b823abe492fda45a",
114
117
  "flow-host-revert.mjs": "a6da239fc54723c74ed51fc03ae3203458b92d2a2e33c26576a9e62cd1031fea",
115
118
  "flow-preview.mjs": "b4f1fb6aeed49623c0264ef47d02ec0b244c055ba4386f5ba4c1ffdcd02824c5",
116
119
  "mockup-delivery.mjs": "fa112408a186d856b343157381188dc18502aba6cf7ef365459f6c5a074e9cd6",
@@ -124,6 +127,7 @@
124
127
  "past-work-search.mjs": "766f4e669364bd9c3c3667135c502719dbbaf550bf074bda4394ab106f33f415",
125
128
  "account-briefs.mjs": "6f5353800bdbb9e4d14c2581b71efa631fab0d47b8e1c32eac968b5e91eb8178",
126
129
  "voice-briefs.mjs": "255dbaa66a683fa8713f1383ccf8101df6c347ddd9708ac636720b549de31573",
130
+ "handoff-out.mjs": "33122cbf4bdf1d4153d0951f01085c2a16f39b96b152321291c86ce449960919",
127
131
  "review-check.mjs": "e16c45fcbbe731b67c5aa1796daffc711e4a5734dbedda282a5e01db0602722e",
128
132
  "flow-review-gate.mjs": "0ef351065269bf2cc6112707064024cf2b916890a80fb94404ebca3a7905754b",
129
133
  "flow-review-reflect.mjs": "61b39e32ad21501b76c1d9267dd5cdf35017afe7e1333b305e72392bc0304628",
@@ -140,7 +144,7 @@
140
144
  "service.mjs": "412ee48ad7b1dd281063f30f2519b077aa4cc92305e6780ac226293756f6e6f8",
141
145
  "supervisor-ready.mjs": "7d5e4ebe3c7a93e0888dd335fb4556a670e55a80a89b861c8a44feac159cd840",
142
146
  "presence.mjs": "2516ebb4474a8f45dd881530335c2174af9cde44be8eed6896ff8c62497b6e2b",
143
- "account.mjs": "9db39d24e69ff33a47a86a01bd82bbf596abcafc6953f6bb3d5a8048f8d71e04",
147
+ "account.mjs": "d1854d230d622f2b3c00eec44636985d8b924fca7c3ddb5b8cbf65d777fdc012",
144
148
  "auth-store.mjs": "c4d41ee7cdc34c2002c6dd50d32e53ace0a3f03a116ffa930ed94a437e247625",
145
149
  "key-shape.mjs": "a4113293d52eeba39b05e70a3e0345abc1ddc181b4f236b413bde0ae11a3f646",
146
150
  "supabase-key.mjs": "041bde6cf50b15f6426daaf738374abc9647861d8c1dd3d85cedc45684271888",
@@ -0,0 +1 @@
1
+ export const CROSS_ROTATION_TTL_SEC=45;export async function acquireCrossRotationLock({sb:r,holder:o,ttlSec:e=45}={}){if(!r?.rpc||!o)return!0;try{const{data:t,error:c}=await r.rpc("acquire_rotation_lock",{p_holder:o,p_ttl_sec:e});return!!c||!0===t}catch{return!0}}export async function releaseCrossRotationLock({sb:r,holder:o,log:e=r=>process.stderr.write(`\n ◇ rotation-lock: ${r}\n`)}={}){if(r?.rpc&&o)try{const{error:t}=await r.rpc("release_rotation_lock",{p_holder:o});t&&e(`release failed (never blocks): ${t?.message||t}`)}catch{}}export function crossRotationLock(r,o){return r?.rpc&&o?{acquire:()=>acquireCrossRotationLock({sb:r,holder:o}),release:()=>releaseCrossRotationLock({sb:r,holder:o})}:null}
@@ -0,0 +1 @@
1
+ import{BUILTIN_PROVIDER as r,effectiveProvider as e}from"./switch-provider.mjs";export const RUNTIME_SWITCH_RUNTIMES=Object.freeze(["claude","codex","hermes"]);export function validateRuntimeSwitch({runtime:n,provider:o,currentRuntime:t,currentProvider:i,registeredIds:u}){const c=String(n??"").trim();if(!RUNTIME_SWITCH_RUNTIMES.includes(c))return{ok:!1,error:"unknown runtime"};const d=c===(t||"claude");return"claude"!==c&&null!=o&&""!==o?{ok:!1,error:"provider not applicable"}:"claude"!==c||null==o||""===o||o===r||Array.isArray(u)&&u.includes(o)?d?"claude"!==c||e(o)===e(i)?{ok:!0,noop:!0}:{ok:!1,error:"same runtime — use provider switch"}:{ok:!0,noop:!1}:{ok:!1,error:"unknown provider"}}export function runtimeSwitchTarget({runtime:e,provider:n,model:o}={}){const t=String(e??"").trim();return{runtime:t,provider:"claude"===t&&n&&n!==r?String(n):null,model:"hermes"===t&&o?String(o):null}}