thinkpool-pair 0.7.414 → 0.7.416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/account.mjs +1 -1
- package/bridge-identity.mjs +1 -0
- package/bridge.mjs +1 -1
- package/cloud-session-transcript.mjs +1 -1
- package/handoff-in.mjs +1 -0
- package/handoff-out.mjs +1 -0
- package/hermes-acp-bootstrap.py +6 -0
- package/lane-work-snapshot.mjs +1 -1
- package/lane-worktree.mjs +1 -1
- package/package.json +6 -1
- package/publish-manifest.json +16 -11
- package/rotation-lock.mjs +1 -0
- package/switch-runtime.mjs +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{PEEK as e,cleanPtyTail as r,digestLogEvent as
|
|
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/handoff-in.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import r from"node:crypto";import{promisify as e}from"node:util";import t from"node:zlib";const n=e(t.gunzip),a=new Set(["claude","codex","hermes","opencode"]),o=(r,e)=>{if(r?.error)throw new Error(`${e}: ${r.error.message||r.error}`);return Array.isArray(r?.data)?r.data:[]},s=r=>a.has(String(r||"").toLowerCase())?String(r).toLowerCase():"claude";async function i(e,t){const a=Buffer.isBuffer(e)?e:Buffer.from(e||[]);if(!a.length)throw new Error("handoff transcript object is empty");if(null!=t?.transcript_bytes&&Number(t.transcript_bytes)!==a.byteLength)throw new Error(`handoff transcript length mismatch (${a.byteLength} != ${t.transcript_bytes})`);if(t?.transcript_sha256&&(o=a,r.createHash("sha256").update(o).digest("hex")!==t.transcript_sha256))throw new Error("handoff transcript sha256 mismatch");var o;const s=await n(a,{maxOutputLength:25165824}),i=[];for(const r of s.toString("utf8").split("\n"))if(r&&!(i.length>=1e4))try{const e=JSON.parse(r);e&&"object"==typeof e&&!Array.isArray(e)&&i.push(e)}catch{}return i}function c(r,{bridgeId:e,sourceGeneration:t}){const n=new Map;for(const a of r||[]){if(!a?.terminal_id||a.from_bridge_id===e)continue;if(Number(a.lease_generation)!==t)continue;const r=n.get(a.terminal_id);(!r||String(a.created_at||"")>String(r.created_at||""))&&n.set(a.terminal_id,a)}return n}export async function receiveHandoffBundles({sb:r,room:e,bridgeId:t,hostId:n,fencing:a,localIds:d=[],waitMs:f=5e4,pollMs:l=1e3,sleep:h=r=>new Promise(e=>setTimeout(e,r)),ops:u,log:m=()=>{}}={}){if(!e)throw new Error("handoff-in requires room");if(!t)throw new Error("handoff-in requires bridgeId");if(!n)throw new Error("handoff-in requires hostId");const p=Number(a);if(!Number.isSafeInteger(p)||p<1)return{records:[],warnings:[],foreignTerminalCount:0};const w=u||function(r){if(!r?.from||!r?.storage?.from)throw new Error("handoff-in requires a Supabase client or injected ops");return{listTerminals:async e=>o(await r.from("code_terminals").select("id,name,agent,kind,host_id,host_label,last_active_at").eq("session_code",e),"terminal roster"),listHandoffs:async(e,t)=>o(await r.from("code_session_handoffs").select("terminal_id,from_bridge_id,lease_generation,sdk_session_id,branch,sha,transcript_path,transcript_bytes,transcript_sha256,cwd,warm,checkpoint_status,checkpoint_error,created_at").eq("session_code",e).eq("lease_generation",t).order("created_at",{ascending:!1}).limit(64),"handoff rows"),downloadTranscript:async e=>{const t=await r.storage.from("code-handoffs").download(e);if(t?.error)throw new Error(t.error.message||String(t.error));if(!t?.data?.arrayBuffer)throw new Error("handoff transcript object is empty");return Buffer.from(await t.data.arrayBuffer())},readCloudSnapshot:async(e,t)=>{const n=await r.from("code_messages").select("payload,created_at").eq("session_code",e).eq("id",`tx:${t}`).maybeSingle();return(r=>{if(r?.error)throw new Error(`cloud transcript snapshot: ${r.error.message||r.error}`);return r?.data||null})(n)?.payload||null}}}(r),g=new Set(d||[]),_=(await w.listTerminals(e)||[]).filter(r=>r?.id&&"structured"===r.kind&&r.host_id!==n&&!g.has(r.id));if(!_.length)return{records:[],warnings:[],foreignTerminalCount:0};const y=p-1,b=Math.max(1,Math.ceil(Math.max(0,f)/Math.max(1,l))+1);let S=new Map;for(let r=0;r<b&&(S=c(await w.listHandoffs(e,y),{bridgeId:t,sourceGeneration:y}),!_.every(r=>S.has(r.id)));r++)r+1<b&&await h(Math.max(1,l));const E=[],$=[];for(const r of _){const t=S.get(r.id)||null;let n=null,a=null;if(t?.transcript_path)try{n=await i(await w.downloadTranscript(t.transcript_path),t),a="handoff-object"}catch(e){const t=`${r.id}: ${e?.message||e}`;E.push(t),m(`[handoff-in] ${t}; using cloud snapshot`)}if(!n)try{const t=await w.readCloudSnapshot(e,r.id);n=Array.isArray(t?.events)?t.events.filter(r=>r&&"object"==typeof r&&!Array.isArray(r)).slice(-1e4):[],a="cloud-snapshot"}catch(e){const t=`${r.id}: cloud snapshot failed: ${e?.message||e}`;E.push(t),m(`[handoff-in] ${t}`),n=[],a="empty-cold-start"}$.push({id:r.id,name:r.name||null,runtime:s(r.agent),log:n,cwd:t?.cwd||null,sessionId:null,branch:t?.branch||null,sha:t?.sha||null,checkpointStatus:t?.checkpoint_status||null,checkpointError:t?.checkpoint_error||null,source:a})}return{records:$,warnings:E,foreignTerminalCount:_.length}}
|
package/handoff-out.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:crypto";import{promisify as e}from"node:util";import n from"node:zlib";const r=e(n.gzip),o=20971520;export async function exportHandoffBundles({sb:e,room:n,terminals:s=[],env:a={},checkpointTerminal:i,log:l=()=>{}}){const{accountId:c,bridgeId:d,fencing:u}=a;if(!e)throw new Error("exportHandoffBundles requires sb (Supabase client)");if(!n)throw new Error("exportHandoffBundles requires room (session code)");if(!c)throw new Error("exportHandoffBundles requires env.accountId");if(!d)throw new Error("exportHandoffBundles requires env.bridgeId");let f=0,p=0,h=0;return await Promise.all(s.map(async s=>{const a=s?.id;if(a)try{if(function({session:t}){return!(!t||!0!==t.turnActive)}(s)&&!0!==s.handoffBoundary)return l(`[handoff-out] terminal ${a}: skipped (turn in flight)`),void p++;const m=s.session||{},y=s.transcript||[],w=function(t){if(!Array.isArray(t))return null;for(let e=t.length-1;e>=0;e--){const n=t[e]?.work;if(n&&"object"==typeof n){const t=n.identity?.sdkSessionId||null,e=n.git?.branch||null,r=n.git?.headSha||null,o=n.git?.worktreeState||null,s=n.capturedAt||null;return t||e||r?{sdkSessionId:t,branch:e,sha:r,worktree:o,capturedAt:s}:null}}return null}(y)||{},g=w.sdkSessionId;let b=w.branch,$=w.sha,_=null,k=null;if("function"==typeof i)try{const t=await i(s);t?.ok?(b=t.branch||b,$=t.sha||$,_="pushed"):(_="failed",k=String(t?.reason||"checkpoint unavailable").slice(0,500))}catch(t){_="failed",k=String(t?.message||t).slice(0,500)}const I=function(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"}(y);I||l(`[handoff-out] terminal ${a}: empty transcript, writing minimal bundle`);const S=await async function(t){let e=t.split("\n").filter(Boolean);const n=async()=>{const t=e.slice(0,Math.floor(.7*e.length)).join("\n")+"\n",n=await r(t,{level:9});return{compressed:n,bytes:n.byteLength,truncated:!0}};try{const s=await r(t,{level:9});if(s.byteLength<=o)return{compressed:s,bytes:s.byteLength,truncated:!1};const a=await n();if(a.bytes<=o)return a;e=e.slice(0,Math.floor(.3*e.length));const i=await n();return i.bytes<=o?i:{bytes:i.bytes,compressed:null,truncated:!0}}catch(t){return{bytes:0,compressed:null,truncated:!1,error:t?.message||String(t)}}}(I),{compressed:x,bytes:v,truncated:B}=S;let A=null,H=null,j=null;if(x){H=v,j=function(e){return t.createHash("sha256").update(e).digest("hex")}(x);const r=function({accountId:t,room:e,terminalId:n}){return`handoff/${t}/${e}/${n}.jsonl.gz`}({accountId:c,room:n,terminalId:a});try{const{error:t}=await e.storage.from("code-handoffs").upload(r,x,{contentType:"application/gzip",upsert:!0});if(t)throw t;A=r,l(`[handoff-out] terminal ${a}: uploaded transcript (${v} bytes${B?", truncated":""})`)}catch(t){l(`[handoff-out] terminal ${a}: upload failed: ${t?.message||t}`),A=null}}else l(`[handoff-out] terminal ${a}: transcript too large (${v} bytes cap exceeded), writing minimal bundle`);const q=Number(u)||0,z={session_code:n,terminal_id:a,from_bridge_id:d,lease_generation:q,sdk_session_id:g,branch:b,sha:$,transcript_path:A,transcript_bytes:H,transcript_sha256:j,cwd:s.cwd||m.cwd||null,warm:"failed"!==_&&!!(g&&b&&$),checkpoint_status:_,checkpoint_error:k};try{const{error:t}=await e.from("code_session_handoffs").insert(z);if(t)throw t;f++,l(`[handoff-out] terminal ${a}: wrote handoff row (warm=${z.warm})`)}catch(t){l(`[handoff-out] terminal ${a}: DB insert failed: ${t?.message||t}`),h++}}catch(t){l(`[handoff-out] terminal ${s.id||"?"}: error: ${t?.message||t}`),h++}else l("[handoff-out] skipping terminal with missing id")})),l(`[handoff-out] complete: written=${f}, skipped=${p}, errors=${h}`),{written:f,skipped:p,errors:h}}
|
package/hermes-acp-bootstrap.py
CHANGED
|
@@ -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
|
|
package/lane-work-snapshot.mjs
CHANGED
|
@@ -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"]),
|
|
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/lane-worktree.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as r}from"node:child_process";import e from"node:path";import t from"node:fs";const o=[".env.local",".env.development.local"];export function linkLocalEnvIntoWorktree({root:r,dir:n,fsImpl:i=t}={}){const a=[];for(const t of o){const o=e.join(r,t),s=e.join(n,t);try{if(!i.existsSync?.(o)||i.existsSync?.(s))continue;i.symlinkSync?.(o,s),a.push(t)}catch{}}return a}export function createManagedLaneWorktree({terminalId:r,cwd:o=process.cwd(),baseSha:i=null,git:a=n,fsImpl:s=t}={}){const c=String(r||""),l=c.replace(/[^a-zA-Z0-9]/g,"").slice(0,8);if(!l)throw new Error("terminal id required for managed worktree");const m=String(a(["rev-parse","--show-toplevel"],o)||"").trim();if(!m)throw new Error("not a git repository");const d=e.join(m,".thinkpool","worktrees",`terminal-${l}`),
|
|
1
|
+
import{execFileSync as r}from"node:child_process";import e from"node:path";import t from"node:fs";const o=[".env.local",".env.development.local"];export function linkLocalEnvIntoWorktree({root:r,dir:n,fsImpl:i=t}={}){const a=[];for(const t of o){const o=e.join(r,t),s=e.join(n,t);try{if(!i.existsSync?.(o)||i.existsSync?.(s))continue;i.symlinkSync?.(o,s),a.push(t)}catch{}}return a}export function createManagedLaneWorktree({terminalId:r,cwd:o=process.cwd(),baseSha:i=null,git:a=n,fsImpl:s=t}={}){const c=String(r||""),l=c.replace(/[^a-zA-Z0-9]/g,"").slice(0,8);if(!l)throw new Error("terminal id required for managed worktree");const m=String(a(["rev-parse","--show-toplevel"],o)||"").trim();if(!m)throw new Error("not a git repository");const d=e.join(m,".thinkpool","worktrees",`terminal-${l}`),h=`thinkpool/terminal/${l}`,p=null==i?function({root:r,git:e}){for(const t of["origin/HEAD","origin/main","origin/master","main","master","HEAD"])try{if(String(e(["rev-parse","--verify","--quiet",t],r)||"").trim())return t}catch{}return"HEAD"}({root:m,git:a}):function({root:r,baseSha:e,git:t}){const o=String(e||"").trim().toLowerCase();if(!/^[a-f0-9]{40}$/.test(o))throw new Error("exact base SHA required for checkpoint branch");let n="";try{n=String(t(["rev-parse","--verify",`${o}^{commit}`],r)||"").trim().toLowerCase()}catch{}if(n!==o)throw new Error("checkpoint base SHA is unavailable in this repository");return o}({root:m,baseSha:i,git:a});return s.mkdirSync(e.dirname(d),{recursive:!0}),a(["worktree","add","-b",h,d,p],m),linkLocalEnvIntoWorktree({root:m,dir:d,fsImpl:s}),{terminalId:c,root:m,dir:d,branch:h,...null==i?{}:{baseSha:p}}}export function checkpointManagedLaneWorktree({terminalId:r,managedWorktree:t,git:o=n}={}){const i=function(r,t){const o=String(r||""),n=o.replace(/[^a-zA-Z0-9]/g,"").slice(0,8),i=t&&"object"==typeof t?t:null;if(!o||!n||!i||i.terminalId!==o)return null;const a=e.resolve(String(i.root||"")),s=e.resolve(String(i.dir||"")),c=String(i.branch||""),l=e.join(a,".thinkpool","worktrees",`terminal-${n}`);return a&&s===l&&c===`thinkpool/terminal/${n}`?{id:o,short:n,root:a,dir:s,branch:c}:null}(r,t);if(!i)return{ok:!1,reason:"terminal does not own an isolated managed worktree"};let a=!1,s=null;try{if(String(o(["status","--porcelain=v1","--untracked-files=all"],i.dir)||"").trim()&&(o(["add","-A"],i.dir),o(["-c","user.name=thinkpool bridge","-c","user.email=bridge@thinkpool.local","commit","-m",`chore(handoff): checkpoint ${i.short}`],i.dir),a=!0),s=String(o(["rev-parse","HEAD"],i.dir)||"").trim().toLowerCase(),!/^[a-f0-9]{40}$/.test(s))throw new Error("checkpoint commit SHA is invalid");return o(["push","--set-upstream","origin",`HEAD:refs/heads/${i.branch}`],i.dir),{ok:!0,branch:i.branch,sha:s,committed:a,pushed:!0}}catch(r){return{ok:!1,branch:i.branch,...s?{sha:s}:{},committed:a,pushed:!1,reason:r?.message||String(r)}}}export function restoreManagedLaneWorktreeFromCheckpoint({terminalId:r,cwd:o=process.cwd(),branch:i,sha:a,git:s=n,fsImpl:c=t}={}){const l=String(r||""),m=l.replace(/[^a-zA-Z0-9]/g,"").slice(0,8),d=`thinkpool/terminal/${m}`,h=String(a||"").trim().toLowerCase();if(!m||i!==d||!/^[a-f0-9]{40}$/.test(h))return{ok:!1,reason:"handoff checkpoint identity is invalid"};try{const r=String(s(["rev-parse","--show-toplevel"],o)||"").trim();if(!r)throw new Error("destination is not a git repository");const t=e.join(r,".thinkpool","worktrees",`terminal-${m}`);if(c.mkdirSync(e.dirname(t),{recursive:!0}),s(["fetch","--no-tags","origin",`refs/heads/${d}`],r),String(s(["rev-parse","--verify","FETCH_HEAD^{commit}"],r)||"").trim().toLowerCase()!==h)throw new Error("pushed checkpoint does not match the handoff commit");return s(["worktree","add","-B",d,t,h],r),linkLocalEnvIntoWorktree({root:r,dir:t,fsImpl:c}),{ok:!0,managedWorktree:{terminalId:l,root:r,dir:t,branch:d,baseSha:h}}}catch(r){return{ok:!1,reason:r?.message||String(r)}}}export function removeManagedLaneWorktree({terminalId:r,managedWorktree:t,git:o=n}={}){const i=String(r||""),a=t&&"object"==typeof t?t:null;if(!i||!a||a.terminalId!==i)return{removed:!1,reason:"not managed by this terminal"};const s=e.resolve(String(a.root||"")),c=e.resolve(String(a.dir||"")),l=String(a.branch||""),m=e.join(s,".thinkpool","worktrees"),d=i.replace(/[^a-zA-Z0-9]/g,"").slice(0,8);if(!s||c===m||!c.startsWith(m+e.sep))return{removed:!1,reason:"path outside managed worktrees"};if(!d||!e.basename(c).includes(d)||!l.startsWith(`thinkpool/terminal/${d}`))return{removed:!1,reason:"ownership marker mismatch"};try{o(["worktree","remove",c,"--force"],s)}catch{}try{o(["branch","-D",l],s)}catch{}return{removed:!0,dir:c,branch:l}}function n(e,t){return r("git",e,{cwd:t,stdio:["ignore","pipe","pipe"],encoding:"utf8",timeout:3e4,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}})}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.416",
|
|
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,8 @@
|
|
|
124
127
|
"past-work-search.mjs",
|
|
125
128
|
"account-briefs.mjs",
|
|
126
129
|
"voice-briefs.mjs",
|
|
130
|
+
"handoff-out.mjs",
|
|
131
|
+
"handoff-in.mjs",
|
|
127
132
|
"review-check.mjs",
|
|
128
133
|
"flow-review-gate.mjs",
|
|
129
134
|
"flow-review-reflect.mjs",
|
package/publish-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": 1,
|
|
3
3
|
"package": "thinkpool-pair",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.416",
|
|
5
5
|
"kind": "minified",
|
|
6
|
-
"minifiedModules":
|
|
7
|
-
"sourceModuleBytes":
|
|
8
|
-
"artifactModuleBytes":
|
|
6
|
+
"minifiedModules": 141,
|
|
7
|
+
"sourceModuleBytes": 1885969,
|
|
8
|
+
"artifactModuleBytes": 855898,
|
|
9
9
|
"files": {
|
|
10
|
-
"package.json": "
|
|
10
|
+
"package.json": "72a8b838920ded3d70ffbbfaf57f348ad3b9c2c3b0d10d100894e12c5bc260eb",
|
|
11
11
|
"LICENSE": "8a030197ca1cad9669909ca14c9ad15cf48d9fe551da526ccaf1fca993eddb23",
|
|
12
|
-
"bridge.mjs": "
|
|
12
|
+
"bridge.mjs": "21be572b46d31791b95af67a08f108043539a41db02fb7995215ffa5d67e0b88",
|
|
13
13
|
"bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
|
|
14
14
|
"command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
|
|
15
15
|
"abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
"codex-app-server.mjs": "d50e30ba6cb2a383c90d89186bc7a13f4caf900c977dd501572468986009470e",
|
|
37
37
|
"codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
|
|
38
38
|
"codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
|
|
39
|
-
"lane-worktree.mjs": "
|
|
40
|
-
"lane-work-snapshot.mjs": "
|
|
39
|
+
"lane-worktree.mjs": "7183930e6d8a76d793bf2fe2ea578c0aca5589044ace739143c04697ea5b51ee",
|
|
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": "
|
|
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": "
|
|
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",
|
|
@@ -124,6 +127,8 @@
|
|
|
124
127
|
"past-work-search.mjs": "766f4e669364bd9c3c3667135c502719dbbaf550bf074bda4394ab106f33f415",
|
|
125
128
|
"account-briefs.mjs": "6f5353800bdbb9e4d14c2581b71efa631fab0d47b8e1c32eac968b5e91eb8178",
|
|
126
129
|
"voice-briefs.mjs": "255dbaa66a683fa8713f1383ccf8101df6c347ddd9708ac636720b549de31573",
|
|
130
|
+
"handoff-out.mjs": "2e568ceeb2b570ae4c0991b00147fa9bb369f33566a52e263c8d3693838c50ae",
|
|
131
|
+
"handoff-in.mjs": "981c368589460176c7308b6497216192e744cf017c4cae83f4c236328714e360",
|
|
127
132
|
"review-check.mjs": "e16c45fcbbe731b67c5aa1796daffc711e4a5734dbedda282a5e01db0602722e",
|
|
128
133
|
"flow-review-gate.mjs": "0ef351065269bf2cc6112707064024cf2b916890a80fb94404ebca3a7905754b",
|
|
129
134
|
"flow-review-reflect.mjs": "61b39e32ad21501b76c1d9267dd5cdf35017afe7e1333b305e72392bc0304628",
|
|
@@ -140,7 +145,7 @@
|
|
|
140
145
|
"service.mjs": "412ee48ad7b1dd281063f30f2519b077aa4cc92305e6780ac226293756f6e6f8",
|
|
141
146
|
"supervisor-ready.mjs": "7d5e4ebe3c7a93e0888dd335fb4556a670e55a80a89b861c8a44feac159cd840",
|
|
142
147
|
"presence.mjs": "2516ebb4474a8f45dd881530335c2174af9cde44be8eed6896ff8c62497b6e2b",
|
|
143
|
-
"account.mjs": "
|
|
148
|
+
"account.mjs": "d1854d230d622f2b3c00eec44636985d8b924fca7c3ddb5b8cbf65d777fdc012",
|
|
144
149
|
"auth-store.mjs": "c4d41ee7cdc34c2002c6dd50d32e53ace0a3f03a116ffa930ed94a437e247625",
|
|
145
150
|
"key-shape.mjs": "a4113293d52eeba39b05e70a3e0345abc1ddc181b4f236b413bde0ae11a3f646",
|
|
146
151
|
"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}}
|