thinkpool-pair 0.7.445 → 0.7.448
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/README.md +25 -0
- package/bridge.mjs +1 -1
- package/browser-job.mjs +1 -0
- package/codex-session.mjs +1 -1
- package/cross-terminal.mjs +1 -1
- package/design-edit.mjs +1 -1
- package/durable-command-journal.mjs +1 -1
- package/flow-preview.mjs +1 -1
- package/host-control-channels.mjs +1 -0
- package/lane-lifecycle.mjs +1 -1
- package/package.json +4 -1
- package/publish-manifest.json +19 -16
- package/recovery-diagnostics.mjs +1 -0
- package/thinkpool-capabilities.json +39 -12
- package/update-gate.mjs +1 -1
- package/viewport.mjs +1 -1
package/browser-job.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import o from"node:net";import{setTimeout as t}from"node:timers/promises";export const BROWSER_JOB_PORT=47683;export async function acquireBrowserJob({port:r=47683,timeoutMs:e=12e4,signal:s}={}){if(!Number.isInteger(r)||r<1||r>65535)throw new Error("Invalid browser job port.");const i=Date.now()+e;for(;;){s?.throwIfAborted();const e=o.createServer(o=>o.destroy());try{await new Promise((o,t)=>{e.once("error",t),e.listen({host:"127.0.0.1",port:r,exclusive:!0},o)})}catch(o){if(e.close(),"EADDRINUSE"!==o.code)throw o;if(Date.now()>=i)throw new Error("Browser job queue timed out. Another job still holds this host’s browser slot.");await t(Math.min(50,Math.max(1,i-Date.now())),void 0,{signal:s});continue}let n=!1;const a=async()=>{n||(n=!0,await new Promise((o,t)=>e.close(r=>r?t(r):o())))};return s?.aborted&&(await a(),s.throwIfAborted()),a}}export async function withBrowserJob(o,t){const r=await acquireBrowserJob(t);try{return await o()}finally{await r()}}
|
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 c}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as u}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as d,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as w}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as b}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as S}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as M,codexReasoningEfforts as C,normalizeCodexReasoningEffort as _,normalizeReasoningEffortList as T}from"./reasoning-effort.mjs";const k=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const I=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=M(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:c=n.tmpdir(),fsImpl:u=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||u.mkdtempSync(o.join(c,"thinkpool-codex-one-shot-"));let x=null;try{i&&(x=o.join(h,"output-schema.json"),u.writeFileSync(x,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...x?["--output-schema",x]:[],m];return await S(()=>new Promise((t,r)=>{let n,o,s="",i="",l=!1;const c=(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 c(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=>c(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?c(null,t):c(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}c(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(a)||22e3))}),p)}finally{if(f)try{u.rmSync(h,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function normalizeCodexServiceTier(e){const t=String(e||"").trim().toLowerCase();return"priority"===t?"fast":"fast"===t||"default"===t?t:null}export function readCodexDefaultServiceTier({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*service_tier\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return normalizeCodexServiceTier(r?.[1])}catch{return null}}const E=e=>[...new Set((Array.isArray(e)?e:[]).map(e=>normalizeCodexServiceTier("string"==typeof e?e:e?.id)).filter(Boolean))];export function codexModelSupportsFastMode({models:e=[],model:t}={}){const r=(Array.isArray(e)?e:[]).find(e=>e?.value===t);return E(r?.serviceTiers).includes("fast")}const F=e=>"fast"===normalizeCodexServiceTier(e)?"priority":normalizeCodexServiceTier(e);export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:T(e.supported_reasoning_levels),serviceTiers:E(e.service_tiers||e.additional_speed_tiers)}))}catch{return[]}}export function codexModelsFromNativeList(e=[]){return(Array.isArray(e)?e:[]).filter(e=>e?.model&&!0!==e.hidden).map(e=>({value:e.model,displayName:e.displayName||e.model,description:e.description||"",reasoningEfforts:T((e.supportedReasoningEfforts||[]).map(e=>e?.reasoningEffort||e)),serviceTiers:E(e.additionalSpeedTiers)}))}export async function loadCodexModelCatalog({cwd:e=process.cwd(),env:t=process.env,home:s=t.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:a=r,appServerFactory:i=l,timeoutMs:c=1e4,settleMs:u=1e3,maxAttempts:d=3,sleepImpl:p=e=>new Promise(t=>setTimeout(t,e))}={}){const m=readCodexModels({home:s,fsImpl:a});let f=null;try{f=i({cwd:e,env:t,initializeTimeoutMs:c}),await f.start();let r=null,n=null;const o=Math.max(2,Number(d)||3);for(let e=0;e<o;e++){try{const e=await f.request("model/list",{includeHidden:!1},c),t=codexModelsFromNativeList(e?.data);(!r||t.length>=r.length)&&(r=t)}catch(e){n=e}e+1<o&&await p(Math.max(0,Number(u)||0))}if(!r)throw n||new Error("native catalog returned no response");return{models:r,source:"model/list",attempts:o,...n?{reason:n?.message||String(n)}:{}}}catch(e){return{models:m,source:"disk-cache",reason:e?.message||String(e)}}finally{try{f?.end()}catch{}}}const N=new Map;function O(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=N.get(n);if(s)return s;const a=o.join(t,"sessions");try{const t=`${e}.jsonl`,s=r.readdirSync(a,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!s)return null;const i=o.join(s.parentPath||s.path||a,s.name);return N.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r}={}){if(!e)return!1;const a=O(e,{home:t,fsImpl:s});if(!a)return!0;try{return Number(s.statSync(a).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r,tailBytes:a=4194304}={}){const i=O(e,{home:t,fsImpl:s});if(!i)return null;let l;try{const e=Number(s.statSync(i).size)||0,t=Math.min(e,Math.max(65536,a)),r=Buffer.alloc(t);l=s.openSync(i,"r"),s.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},s=r.total_token_usage||{},a=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(a)||a<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(a),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(s.input_tokens)||0),cached_input_tokens:Math.max(0,Number(s.cached_input_tokens)||0),output_tokens:Math.max(0,Number(s.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{s.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return k.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 and shared work-routing contract. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals. Use open_main_terminal or spawn_terminal only when the role and runtime policy permit it. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate.";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,c=f({promptIndex:s,forceFull:a,turnCause:i}),u=[o?d:"",c?t:"",p({text:e,promptIndex:s,forceFull:a,turnCause:i}),c?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return u.length?`<thinkpool-context>\n${u.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,serviceTier:r,effort:n,sandbox:s,approvalPolicy:a="untrusted",providerConfig:i,mcpUrl:l,writableDirs:c=[],images:u=[],prompt:d}){const p=normalizeCodexSandbox(s),m=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===p?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${p}"`]]:["exec","--json","--sandbox",p,"--skip-git-repo-check"]];t&&m.push("-m",t);const f=normalizeCodexServiceTier(r);f&&(m.push("-c",`service_tier=${JSON.stringify(f)}`),"fast"===f&&m.push("-c","features.fast_mode=true")),m.push("-c",`approval_policy="${a}"`);const h=M(n);if(h&&m.push("-c",`model_reasoning_effort="${h}"`),i&&m.push("-c",i),!e)for(const e of c)m.push("--add-dir",e);l&&(m.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(l)}`),m.push("-c","mcp_servers.thinkpool.required=true"));let x=!1;for(const e of u)o.isAbsolute(String(e||""))&&(m.push("--image",String(e)),x=!0);return x&&m.push("--"),e&&m.push(e),m.push(String(d??"")),m}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const A=/\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:A.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=[],serviceTier:h=null,effort:b="high",resume:T,env:k,sandbox:E,mode:N="default",onEvent:O,providerConfig:A,terminalRolePrompt:P,rolePrompt:D,roomContext:U,mcpServers:j,prepareCwd:$=null,requestPermission:L,appServerGate:q=i,appServerFactory:z=l,mcpHttpFactory:X=u,spawnImpl:G=t,admitStart:H=null,stallOptions:B={},codexHost:W={}}){let J=CODEX_MODE_CONFIG[N]?N:"default",K=codexConfigForMode(J);E=normalizeCodexSandbox(E||K.sandbox);const Y=codexPublishAccess({cwd:e,env:{...process.env,...k||{}}}),Q=Y.env,V=T||null,Z=!V||codexThreadCanResume(V,{home:Q.CODEX_HOME||o.join(n.homedir(),".codex")});let ee=Z?V:null,te=ee?1:0,re=0,ne=!0;const oe=m(U);let se=null,ae=!1,ie=!1,le=!1;const ce="function"==typeof B.now?B.now:Date.now,ue="function"==typeof B.setInterval?B.setInterval:setInterval,de="function"==typeof B.clearInterval?B.clearInterval:clearInterval,pe=Number.isFinite(B.stallMs)?Math.max(1,Number(B.stallMs)):Math.max(3e4,parseInt(Q.TP_STALL_MS,10)||9e4),me=Number.isFinite(B.forceStopMs)?Math.max(pe,Number(B.forceStopMs)):Math.max(3*pe,parseInt(Q.TP_FORCE_STOP_MS,10)||3e5),fe=Number.isFinite(B.longReasoningForceStopMs)?Math.max(me,Number(B.longReasoningForceStopMs)):Math.max(me,parseInt(Q.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),he=Number.isFinite(B.intervalMs)?Math.max(1,Number(B.intervalMs)):5e3;let xe=ce(),ge=!1,ye=!1,ve=!1,we=!1,be=!0,Se=0,Me=0,Ce=null,_e=d||null;const Te=new Set((Array.isArray(p)?p:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));let ke=normalizeCodexServiceTier(h);"fast"!==ke||codexModelSupportsFastMode({models:p,model:_e})||(ke="default");let Ie=_(b,{models:p,model:_e}),Ee=ee?readCodexThreadUsage(ee):null,Fe=null;const Ne=q({env:Q});let Oe=!Ne.enabled,Ae=null,Re=!1;const Pe=new WeakSet;let De=null,Ue=null,je=!1;const $e=new Set,Le=new Map,qe=new Set,ze=new Map;let Xe=Promise.resolve(),Ge=Promise.resolve();const He=[],Be=y(e=>{try{O?.(e)}catch{}}),We=()=>{xe=ce(),ge=!1},Je=e=>{We(),Be(e)};Je.cancel=()=>Be.cancel();const Ke=new c({onEvent:Je,model:d||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Ee=t),t||Ee}}),Ye=()=>{const e=[...ze.values()];ze.clear();for(const t of e)t.text&&Je({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},Qe=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Ye(),le=!1),Ke.push(e)},Ve=e=>{Ye(),le=!1,Je(e)},Ze=({retry:e=!1,recoveryAttempt:t=0}={})=>{le=!0,xe=ce(),ge=!1,ve=!1,we=!1,be=!0,Se=Math.max(0,Number(t)||0),e||(ye=!1)},et=e=>Je({kind:"note",text:e}),tt=(e=De)=>{const t=String(e||"");if(t)for($e.add(t);$e.size>64;)$e.delete($e.values().next().value)},rt=(e={})=>{const t=String(e.turnId||"");return!(!t||ie||ae||$e.has(t)||t!==String(De||""))};async function nt(){const e=j?.thinkpool;return e?(Fe||(Fe=X({sdkServer:e})),Fe):null}function ot(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&ee&&String(t.threadId)!==String(ee))return;(le||je)&&We();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Ee=e,je){const{used:t,max:r}=e.context;Je({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(ie||ae)return void tt(e);if(Ue){const t=Ue;return Ue=null,void t.resolve(e)}return De||(De=e),void We()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||rt(t)){if(We(),"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 Je({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||De||"active"}`,r=ze.get(e)||{cid:`codex-stream:${ee||"thread"}:${e}`,text:""};return r.text+=String(t.delta),ze.set(e,r),void Je({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&&(Le.set(r.id,r),r.id&&I(r)&&qe.add(r.id)),"item/completed"===e&&(r.id&&qe.delete(r.id),Le.set(r.id,r),"agentMessage"===r.type&&ze.has(r.id))){const e=ze.get(r.id);return ze.delete(r.id),void Je({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void Qe({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";Qe({type:"error",message:e})}}}async function st(e,t={}){if(!rt(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,Le.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";Me++;try{n=await(L?.(r))}catch{}finally{Me=Math.max(0,Me-1),We()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function at(){if(Oe)return!1;if(!1===Ae?.alive)return Re=!1,Oe=!0,!1;if(Ae&&Re)return!0;let t;try{t=await nt()}catch(e){et(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Ae){const r=z({cwd:e,env:Q,args:a({providerConfig:A,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(J,E,Y.writableDirs),serviceTier:ke}),onNotification:ot,onServerRequest:st,codexHost:{...W,log:et},onClose:e=>{ie||Oe||Pe.has(r)||(Re=!1,Oe=!0,et(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Ae=r,await Ae.start(),We()}return ee=await Ae.startThread({threadId:te>0?ee:null,cwd:e,model:_e,serviceTier:F(ke),sandbox:E,approvalPolicy:K.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),We(),t?.url&&(await Ae.waitForMcpServer({threadId:ee,name:"thinkpool"}),We()),Re=!0,Qe({type:"thread.started",thread_id:ee}),!0}catch{Oe=!0;try{Ae?.end()}catch{}return Ae=null,Re=!1,!1}}function it({force:e=!1}={}){const t=Ae,r=De;if(t&&e){tt(r),Oe=!0,Re=!1;try{t.end()}catch{}Ae===t&&(Ae=null)}else if(t&&r)tt(r),Promise.resolve(t.interrupt({threadId:ee,turnId:r})).catch(()=>{Oe=!0,Re=!1;try{t.end()}catch{}Ae===t&&(Ae=null)});else if(t){Oe=!0,Re=!1;try{t.end()}catch{}Ae===t&&(Ae=null)}if(se){const e=se;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(se===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function lt(e,t={}){if(ie||ae||we)return!0;if(!await at())return!1;if(ie||ae||we)return!0;Ke.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{if(be=!1,De=await Ae.startTurn({threadId:ee,input:e,images:Array.isArray(t.images)?t.images:[],model:_e,serviceTier:F(ke),effort:M(Ie),approvalPolicy:K.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(_e,Ie)}),We(),ae||ie){tt(De);try{await Ae.interrupt({threadId:ee,turnId:De})}catch{}return!0}const r=await Ae.waitForTurn(De);tt(r?.turn?.id||De);const n=r?.turn?.status;ae?Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ve||we)||("interrupted"===n?Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ve=!1,Qe({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(ve=!1,Qe({type:"turn.completed"})))}catch(e){Oe=!0,Re=!1,tt(De);try{Ae?.end()}catch{}Ae=null,ie||ve||we||Qe({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Ye(),De=null,le=!1,qe.clear(),Le.clear(),ze.clear()}return!0}async function ct(e){if(ie||ae||we)return!0;if(!await at())return!1;if(ie||ae||we)return!0;Ke.setUsageBaseline(ee?readCodexThreadUsage(ee):null),le=!0;try{be=!1;const t=await Ae.startReview({threadId:ee,target:x(e)});if(De=t?.turn?.id||t?.turnId,!De)throw new Error("Codex review/start returned no turn id");if(We(),ae||ie){tt(De);try{await Ae.interrupt({threadId:ee,turnId:De})}catch{}return!0}const r=await Ae.waitForTurn(De);tt(r?.turn?.id||De);const n=r?.turn?.status;ae?Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(ve||we)||("interrupted"===n?Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(ve=!1,Qe({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(ve=!1,Qe({type:"turn.completed"})))}catch(e){Oe=!0,Re=!1,tt(De);try{Ae?.end()}catch{}Ae=null,ie||ve||we||Qe({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Ye(),De=null,le=!1,qe.clear(),Le.clear(),ze.clear()}return!0}async function ut(t,n={}){if(ie||ae||we)return;let o;try{o=await nt()}catch(e){et(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){et(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===te?null:ee,model:_e,serviceTier:ke,effort:Ie,sandbox:E,approvalPolicy:K.approvalPolicy,providerConfig:A,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(J,E,Y.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});Ke.setUsageBaseline(ee?readCodexThreadUsage(ee):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:Q,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),le=!0,be=!1;try{se=G("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return le=!1,e.codexExecFailure={spawnFailure:!0,hostRetrySafe:!0,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},void n(e)}if(null!=o)try{r.closeSync(o)}catch{}const l=se,c=[];s.createInterface({input:l.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}We(),c.push(t.type),"item.started"===t.type&&t.item?.id&&I(t.item)&&qe.add(t.item.id),"item.completed"===t.type&&t.item?.id&&qe.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(ee=ee||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(d=!0,ve=!1),Qe(t)});let u="",d=!1,p=!1;l.stderr.on("data",e=>{u+=e.toString(),u.length>800&&(u=u.slice(-800))}),l.on("error",e=>{p||(p=!0,se===l&&(se=null),le=!1,qe.clear(),e.codexExecFailure={spawnFailure:!0,hostRetrySafe:0===c.length,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},n(e))}),l.on("close",e=>{if(p)return;p=!0,se===l&&(se=null),le=!1,qe.clear();const r=`codex exec exited ${e}${u?": "+u.trim().slice(-300):""}`,o=0!==e&&null!=e&&!d&&codexExecFailedBeforeTurn({stderr:u,eventTypes:c});if(!d&&0!==e&&null!=e){const e=new Error(r);return e.codexExecFailure={preTurnInitFailure:o,hostRetrySafe:0===c.length,message:r,sawTerminalResult:!1},void n(e)}ae&&!d&&Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await S(i,{...W,log:et,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe&&!0!==e?.codexExecFailure?.preTurnInitFailure})}catch(e){se=null,le=!1,qe.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)Ve({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!ae&&!we)return{preTurnInitFailure:!0,message:t.message};(!ve&&!we||ae)&&Ve(ae?{kind:"result",subtype:"aborted",sessionId:ee,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 dt(){if(ie)return;const e=He.shift();e&&(Ge=Ge.then(async()=>{const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);Ze({recoveryAttempt:t}),t>0&&Be({kind:"active-status",sinceMs:0,message:"recovering the stalled turn from current state"});const r=e.options?.turnCause||null,n=f({promptIndex:e.promptIndex,forceFull:e.forceFullReminder,turnCause:r}),o=buildCodexPrompt({text:e.text,terminalRolePrompt:P,rolePrompt:D,roomContext:()=>oe({force:n}),firstTurn:0===te,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!ie&&!ae&&!we;){if(!(s?await ct(e.text):await lt(o,e.options)))if(s)ve=!1,Ve({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{ve&&(ve=!1,a=!0,Ze({retry:!0,recoveryAttempt:t}),Be({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await ut(o,e.options);if(r?.preTurnInitFailure){if(!(i||ie||ae||we)){i=!0,Ze({retry:!0,recoveryAttempt:t}),Be({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}Ve({kind:"error",message:r.message,recoverable:!0})}}if(!ve||a||we||ie||ae)break;ve=!1,a=!0,Ze({retry:!0,recoveryAttempt:t}),Be({kind:"note",text:"retrying the stalled turn on a fresh connection"})}te++,dt()}))}return V&&!Z&&queueMicrotask(()=>et("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(Q.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Ne.enabled||queueMicrotask(()=>et(`Codex App Server disabled: ${Ne.reason}`)),Ce=ue(()=>{const e=ce()-xe,t=v({turnActive:le,awaitingUser:Me,quietMs:e,stallMs:pe,forceStopMs:["max","ultra"].includes(Ie)?fe:me,stalledSent:ge,stallRetried:ye});if("none"===t)return;if("retry"===t&&!be)return we=!0,ve=!1,Se>0?(Be({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."}),Be({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):qe.size>0?Be({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."}):(He.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:re++,forceFullReminder:!0}),Be({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."})),Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void it({force:!0});const r=w(t,e);if(r&&Be(r),"status"!==t){if("retry"===t)return ye=!0,ve=!0,xe=ce(),ge=!1,void it();we=!0,ve=!1,He.length=0,Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),it({force:!0})}else ge=!0},he),Ce.unref?.(),{get sessionId(){return ee},get usageSnapshot(){return Ee},get turnActive(){return le||je},get canSteer(){return!(!Ae||!1===Ae.alive||!Re||Oe)},get started(){return le||je||!!se||!(!Ae||!1===Ae.alive)},sendTurn(t,r={}){if(ie)return!1;if(!le&&0===He.length&&!Re&&!se){const e="function"==typeof H?H():{ok:!0};if(!1===e?.ok)return Je({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=re++,o=ne;if(ne=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!le&&0===te&&$)try{e=$()||e}catch{}if(le&&Ae&&De){We();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:P,rolePrompt:D,roomContext:()=>oe({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=De;return Xe=Xe.then(()=>Ae.steer({threadId:ee,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).catch(e=>{"rejected"===e?.delivery||"not_sent"===e?.delivery?(He.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),le||1!==He.length||dt()):Je({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return He.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),le||je||(ae=!1,Ze({recoveryAttempt:0})),1!==He.length||je||dt(),!0},async park(){if(ie||le||je||He.length)return!1;const e=Ge;try{await e}catch{}try{await Xe}catch{}if(ie||le||je||He.length||se||De)return!1;const t=Ae;if(t){Pe.add(t),Ae=null,Re=!1,Le.clear(),ze.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=Ge,t=le&&!De&&!se;ae=!0,ve=!1,we=!1,He.length=0,tt(De);let r=Promise.resolve();if(Ae&&De&&(r=Ae.interrupt({threadId:ee,turnId:De}).catch(()=>{})),se){try{se.kill("SIGTERM")}catch{}setTimeout(()=>{try{se?.kill("SIGKILL")}catch{}},1500)}else t&&Ve({kind:"result",subtype:"aborted",sessionId:ee,model:_e,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(ie=!0,le=!1,Ce&&de(Ce),Je.cancel(),He.length=0,tt(De),se)try{se.kill()}catch{}try{Ae?.end()}catch{}Fe?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||le)return!1;const t=String(e);if(!Te.has(t))return!1;_e=t,Ke.setModel(_e);const r=_(Ie,{models:p,model:_e});return r!==Ie&&(Ie=r,Je({kind:"effort",level:Ie})),"fast"!==ke||codexModelSupportsFastMode({models:p,model:_e})||(ke="default"),!0},setServiceTier(e){if(le||je||He.length)return!1;const t=normalizeCodexServiceTier(e);if(!t)return!1;if("fast"===t&&!codexModelSupportsFastMode({models:p,model:_e}))return!1;if(t===ke)return!0;ke=t;const r=Ae;if(r){Pe.add(r),Ae=null,Re=!1,Le.clear(),ze.clear();try{r.end()}catch{}}return!0},setMode:e=>!(le||!CODEX_MODE_CONFIG[e]||(J=e,K=codexConfigForMode(J),E=K.sandbox,ee=null,te=0,Ee=null,Re=!1,0)),setEffort:e=>!(le||!C({models:p,model:_e}).includes(e)||(Ie=e,Je({kind:"effort",level:Ie}),0)),get serviceTier(){return ke},get supportsFastMode(){return codexModelSupportsFastMode({models:p,model:_e})},clearContext:()=>!le&&(ee=null,te=0,Ee=null,Re=!1,!0),async compactContext(){if(le||je)return!1;ae=!1,je=!0;let e=null,t=!1;try{if(!await at()||le)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{Ue=null,r(new Error("Codex did not start the compaction turn"))},5e3),Ue={resolve:t,reject:r}});await Ae.compact({threadId:ee}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Ae.waitForTurn(n);tt(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)?(et(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(et(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),Ue=null,je=!1,He.length&&dt()}},async accountUsage(){if(!await at())return null;const[e,t]=await Promise.all([Ae.accountUsage().catch(()=>null),Ae.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 d}from"./codex-event-mapper.mjs";import{startCodexMcpHttp as c}from"./codex-mcp-http.mjs";import{CODEX_THINKPOOL_FIRST_TURN_PREAMBLE as u,buildThinkPoolTurnGuidance as p,createRoomContextSelector as m,usesFullThinkPoolReminder as f}from"./thinkpool-room-prompt.mjs";import{questionAnswerResponse as h}from"./question-response.mjs";import{codexReviewTarget as x,isCodexCompactCommand as g}from"./codex-commands.mjs";import{createCumulativeEventRelay as y}from"./cumulative-event-relay.mjs";import{stallDecision as v,stallEvent as w}from"./turn-stall.mjs";import{codexCatastrophicHookCliArgs as b}from"./catastrophic-command-guard.mjs";import{retryCodexSpawnAfterHostHeal as S}from"./codex-host-heal.mjs";import{codexNativeReasoningEffort as C,codexReasoningEfforts as M,normalizeCodexReasoningEffort as _,normalizeReasoningEffortList as T}from"./reasoning-effort.mjs";const k=new Set(["read-only","workspace-write","danger-full-access"]);export const CODEX_MODE_CONFIG={default:{sandbox:"workspace-write",approvalPolicy:"untrusted"},acceptEdits:{sandbox:"workspace-write",approvalPolicy:"on-request"},plan:{sandbox:"read-only",approvalPolicy:"never"},review:{sandbox:"read-only",approvalPolicy:"never"},bypassPermissions:{sandbox:"danger-full-access",approvalPolicy:"never"}};export const CODEX_STALL_CONTINUATION_PROMPT=["The previous Codex turn was force-stopped after becoming unresponsive.","Continue the same person-authored task from the current conversation and workspace state.","First inspect the current workspace state and any partial work.","Do not repeat commands, edits, external writes, messages, pushes, deployments, or other actions that may already have completed.","Finish only the remaining work, verify the result, and report any uncertainty."].join(" ");export const CODEX_STALL_RETRY_SUGGESTION="Continue from the current workspace state. Inspect partial work first and do not repeat completed actions.";const I=e=>{const t=String(e?.type||"");return["commandExecution","command_execution","fileChange","file_change","mcpToolCall","mcp_tool_call","webSearch","web_search"].includes(t)};export function codexConfigForMode(e){return CODEX_MODE_CONFIG[e]||CODEX_MODE_CONFIG.default}export function codexDefaultCollaborationMode(e,t){if(!e)return null;const r=C(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:d=n.tmpdir(),fsImpl:c=r,spawnImpl:u=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(d,"thinkpool-codex-one-shot-"));let x=null;try{i&&(x=o.join(h,"output-schema.json"),c.writeFileSync(x,`${JSON.stringify(i)}\n`,{mode:384}));const e=["exec","--json","--ephemeral","--ignore-rules","--sandbox","read-only","--skip-git-repo-check","--color","never","-c",'approval_policy="never"',"-c",'model_reasoning_effort="low"',...s?["-m",String(s)]:[],...x?["--output-schema",x]:[],m];return await S(()=>new Promise((t,r)=>{let n,o,s="",i="",l=!1;const d=(e,n)=>{l||(l=!0,o&&clearTimeout(o),e?r(e):t(n))};try{n=u("codex",e,{cwd:h,env:process.env,stdio:["ignore","pipe","pipe"]})}catch(e){return void d(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=>d(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?d(null,t):d(new Error(`Codex cleanup failed${null==e?"":` (${e})`}${i.trim()?`: ${i.trim().slice(-240)}`:""}`))}),o=setTimeout(()=>{try{n.kill("SIGTERM")}catch{}d(new Error("Codex cleanup timed out"))},Math.max(1e3,Number(a)||22e3))}),p)}finally{if(f)try{c.rmSync(h,{recursive:!0,force:!0})}catch{}}}export function codexWritableDirsForSandbox(e,t=[]){return"read-only"===normalizeCodexSandbox(e)?[]:t}export function codexExtraWritableDirsForMode(e,t,r=[]){return"review"===e?[]:codexWritableDirsForSandbox(t,r)}export function readCodexDefaultModel({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*model\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return r?.[1]?.trim()||null}catch{return null}}export function normalizeCodexServiceTier(e){const t=String(e||"").trim().toLowerCase();return"priority"===t?"fast":"fast"===t||"default"===t?t:null}export function readCodexDefaultServiceTier({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=t.readFileSync(o.join(e,"config.toml"),"utf8").split(/^\s*\[/m,1)[0].match(/^\s*service_tier\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/m);return normalizeCodexServiceTier(r?.[1])}catch{return null}}const E=e=>[...new Set((Array.isArray(e)?e:[]).map(e=>normalizeCodexServiceTier("string"==typeof e?e:e?.id)).filter(Boolean))];export function codexModelSupportsFastMode({models:e=[],model:t}={}){const r=(Array.isArray(e)?e:[]).find(e=>e?.value===t);return E(r?.serviceTiers).includes("fast")}const F=e=>"fast"===normalizeCodexServiceTier(e)?"priority":normalizeCodexServiceTier(e);export function readCodexModels({home:e=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:t=r}={}){try{const r=JSON.parse(t.readFileSync(o.join(e,"models_cache.json"),"utf8"));return(Array.isArray(r?.models)?r.models:[]).filter(e=>e?.slug&&"hide"!==e.visibility).sort((e,t)=>(Number(e.priority)||999)-(Number(t.priority)||999)).map(e=>({value:e.slug,displayName:e.display_name||e.slug,description:e.description||"",reasoningEfforts:T(e.supported_reasoning_levels),serviceTiers:E(e.service_tiers||e.additional_speed_tiers)}))}catch{return[]}}export function codexModelsFromNativeList(e=[]){return(Array.isArray(e)?e:[]).filter(e=>e?.model&&!0!==e.hidden).map(e=>({value:e.model,displayName:e.displayName||e.model,description:e.description||"",reasoningEfforts:T((e.supportedReasoningEfforts||[]).map(e=>e?.reasoningEffort||e)),serviceTiers:E(e.additionalSpeedTiers)}))}export async function loadCodexModelCatalog({cwd:e=process.cwd(),env:t=process.env,home:s=t.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:a=r,appServerFactory:i=l,timeoutMs:d=1e4,settleMs:c=1e3,maxAttempts:u=3,sleepImpl:p=e=>new Promise(t=>setTimeout(t,e))}={}){const m=readCodexModels({home:s,fsImpl:a});let f=null;try{f=i({cwd:e,env:t,initializeTimeoutMs:d}),await f.start();let r=null,n=null;const o=Math.max(2,Number(u)||3);for(let e=0;e<o;e++){try{const e=await f.request("model/list",{includeHidden:!1},d),t=codexModelsFromNativeList(e?.data);(!r||t.length>=r.length)&&(r=t)}catch(e){n=e}e+1<o&&await p(Math.max(0,Number(c)||0))}if(!r)throw n||new Error("native catalog returned no response");return{models:r,source:"model/list",attempts:o,...n?{reason:n?.message||String(n)}:{}}}catch(e){return{models:m,source:"disk-cache",reason:e?.message||String(e)}}finally{try{f?.end()}catch{}}}const N=new Map;function O(e,{home:t,fsImpl:r}){if(!e)return null;const n=`${t}\0${e}`,s=N.get(n);if(s)return s;const a=o.join(t,"sessions");try{const t=`${e}.jsonl`,s=r.readdirSync(a,{recursive:!0,withFileTypes:!0}).find(e=>e.isFile()&&e.name.endsWith(t));if(!s)return null;const i=o.join(s.parentPath||s.path||a,s.name);return N.set(n,i),i}catch{return null}}export function codexThreadCanResume(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r}={}){if(!e)return!1;const a=O(e,{home:t,fsImpl:s});if(!a)return!0;try{return Number(s.statSync(a).size)>0}catch{return!0}}export function readCodexThreadUsage(e,{home:t=process.env.CODEX_HOME||o.join(n.homedir(),".codex"),fsImpl:s=r,tailBytes:a=4194304}={}){const i=O(e,{home:t,fsImpl:s});if(!i)return null;let l;try{const e=Number(s.statSync(i).size)||0,t=Math.min(e,Math.max(65536,a)),r=Buffer.alloc(t);l=s.openSync(i,"r"),s.readSync(l,r,0,t,e-t);const n=r.toString("utf8").split("\n");for(let e=n.length-1;e>=0;e--){let t;try{t=JSON.parse(n[e])}catch{continue}if("token_count"!==t?.payload?.type||!t.payload.info)continue;const r=t.payload.info,o=r.last_token_usage||{},s=r.total_token_usage||{},a=Number(o.total_tokens),i=Number(r.model_context_window);return!Number.isFinite(a)||a<0||!Number.isFinite(i)||i<=0?null:{context:{used:Math.floor(a),max:Math.floor(i)},total:{input_tokens:Math.max(0,Number(s.input_tokens)||0),cached_input_tokens:Math.max(0,Number(s.cached_input_tokens)||0),output_tokens:Math.max(0,Number(s.output_tokens)||0)},rateLimits:t.payload.rate_limits||null}}}catch{return null}finally{if(null!=l)try{s.closeSync(l)}catch{}}return null}export function codexUsageSnapshotFromAppServer(e){const t=Number(e?.last?.totalTokens),r=Number(e?.modelContextWindow);if(!Number.isFinite(t)||t<0||!Number.isFinite(r)||r<=0)return null;const n=e?.total||{};return{context:{used:Math.floor(t),max:Math.floor(r)},total:{input_tokens:Math.max(0,Number(n.inputTokens)||0),cached_input_tokens:Math.max(0,Number(n.cachedInputTokens)||0),output_tokens:Math.max(0,Number(n.outputTokens)||0)},rateLimits:e?.rateLimits||null}}export function normalizeCodexSandbox(e){return k.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 and shared work-routing contract. Internal Codex collaboration workers and hidden in-process subagents are not thinkpool terminals. Use open_main_terminal or spawn_terminal only when the role and runtime policy permit it. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate.";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,d=f({promptIndex:s,forceFull:a,turnCause:i}),c=[o?u:"",d?t:"",p({text:e,promptIndex:s,forceFull:a,turnCause:i}),d?r||CODEX_ROOM_CASCADE_REMINDER:"",l].map(e=>String(e||"").trim()).filter(Boolean);return c.length?`<thinkpool-context>\n${c.join("\n\n")}\n</thinkpool-context>\n\n${String(e??"")}`:String(e??"")}export function buildCodexExecArgs({sessionId:e,model:t,serviceTier:r,effort:n,sandbox:s,approvalPolicy:a="untrusted",providerConfig:i,mcpUrl:l,writableDirs:d=[],images:c=[],prompt:u}){const p=normalizeCodexSandbox(s),m=[...b(),...e?["exec","resume","--json","--skip-git-repo-check",..."danger-full-access"===p?["--dangerously-bypass-approvals-and-sandbox"]:["-c",`sandbox_mode="${p}"`]]:["exec","--json","--sandbox",p,"--skip-git-repo-check"]];t&&m.push("-m",t);const f=normalizeCodexServiceTier(r);f&&(m.push("-c",`service_tier=${JSON.stringify(f)}`),"fast"===f&&m.push("-c","features.fast_mode=true")),m.push("-c",`approval_policy="${a}"`);const h=C(n);if(h&&m.push("-c",`model_reasoning_effort="${h}"`),i&&m.push("-c",i),!e)for(const e of d)m.push("--add-dir",e);l&&(m.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(l)}`),m.push("-c","mcp_servers.thinkpool.required=true"));let x=!1;for(const e of c)o.isAbsolute(String(e||""))&&(m.push("--image",String(e)),x=!0);return x&&m.push("--"),e&&m.push(e),m.push(String(u??"")),m}export function codexExecFailedBeforeTurn({stderr:e="",eventTypes:t=[]}={}){if((Array.isArray(t)?t.map(e=>String(e||"")):[]).some(e=>e&&"thread.started"!==e))return!1;const r=String(e||"");return/timed out handshaking with MCP server/i.test(r)||/thread\/resume failed/i.test(r)&&/failed to initialize session/i.test(r)}const A=/\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:A.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:u,models:p=[],serviceTier:h=null,effort:b="high",resume:T,env:k,sandbox:E,mode:N="default",onEvent:O,onCommandComplete:A,providerConfig:P,terminalRolePrompt:D,rolePrompt:U,roomContext:j,mcpServers:$,prepareCwd:L=null,requestPermission:q,appServerGate:z=i,appServerFactory:X=l,mcpHttpFactory:G=c,spawnImpl:H=t,admitStart:B=null,stallOptions:W={},codexHost:J={}}){let K=CODEX_MODE_CONFIG[N]?N:"default",Y=codexConfigForMode(K);E=normalizeCodexSandbox(E||Y.sandbox);const Q=codexPublishAccess({cwd:e,env:{...process.env,...k||{}}}),V=Q.env,Z=T||null,ee=!Z||codexThreadCanResume(Z,{home:V.CODEX_HOME||o.join(n.homedir(),".codex")});let te=ee?Z:null,re=te?1:0,ne=0,oe=!0;const se=m(j);let ae=null,ie=!1,le=!1,de=!1;const ce="function"==typeof W.now?W.now:Date.now,ue="function"==typeof W.setInterval?W.setInterval:setInterval,pe="function"==typeof W.clearInterval?W.clearInterval:clearInterval,me=Number.isFinite(W.stallMs)?Math.max(1,Number(W.stallMs)):Math.max(3e4,parseInt(V.TP_STALL_MS,10)||9e4),fe=Number.isFinite(W.forceStopMs)?Math.max(me,Number(W.forceStopMs)):Math.max(3*me,parseInt(V.TP_FORCE_STOP_MS,10)||3e5),he=Number.isFinite(W.longReasoningForceStopMs)?Math.max(fe,Number(W.longReasoningForceStopMs)):Math.max(fe,parseInt(V.TP_LONG_REASONING_FORCE_STOP_MS,10)||12e5),xe=Number.isFinite(W.intervalMs)?Math.max(1,Number(W.intervalMs)):5e3;let ge=ce(),ye=!1,ve=!1,we=!1,be=!1,Se=!0,Ce=0,Me=0,_e=null,Te=u||null;const ke=new Set((Array.isArray(p)?p:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));let Ie=normalizeCodexServiceTier(h);"fast"!==Ie||codexModelSupportsFastMode({models:p,model:Te})||(Ie="default");let Ee=_(b,{models:p,model:Te}),Fe=te?readCodexThreadUsage(te):null,Ne=null;const Oe=z({env:V});let Ae=!Oe.enabled,Re=null,Pe=!1;const De=new WeakSet;let Ue=null,je=null,$e=!1;const Le=new Set,qe=new Map,ze=new Set,Xe=new Map;let Ge=Promise.resolve(),He=Promise.resolve();const Be=[];let We=null;const Je=new Set,Ke=y(e=>{try{O?.(e)}catch{}}),Ye=()=>{ge=ce(),ye=!1},Qe=e=>{if(Ye(),"result"===e.kind){const t=new Set(We?.completed?[]:We?.cids||[]);if(We&&(We.completed=!0),"aborted"===e.subtype&&ie){for(const e of Je)t.add(e);Je.clear()}e={...e,commandCids:[...t]}}Ke(e)};Qe.cancel=()=>Ke.cancel();const Ve=new d({onEvent:Qe,model:u||null,usageSnapshotForSession:e=>{const t=readCodexThreadUsage(e);return t&&(Fe=t),t||Fe}}),Ze=()=>{const e=[...Xe.values()];Xe.clear();for(const t of e)t.text&&Qe({kind:"assistant",blocks:[{type:"text",text:t.text}],parentToolUseId:null,replacesCid:t.cid})},et=e=>{"turn.completed"!==e?.type&&"turn.failed"!==e?.type||(Ze(),de=!1),Ve.push(e)},tt=e=>{Ze(),de=!1,Qe(e)},rt=({retry:e=!1,recoveryAttempt:t=0}={})=>{de=!0,ge=ce(),ye=!1,we=!1,be=!1,Se=!0,Ce=Math.max(0,Number(t)||0),e||(ve=!1)},nt=e=>Qe({kind:"note",text:e}),ot=(e=Ue)=>{const t=String(e||"");if(t)for(Le.add(t);Le.size>64;)Le.delete(Le.values().next().value)},st=(e={})=>{const t=String(e.turnId||"");return!(!t||le||ie||Le.has(t)||t!==String(Ue||""))};async function at(){const e=$?.thinkpool;return e?(Ne||(Ne=G({sdkServer:e})),Ne):null}function it(e,t={}){if("thread/tokenUsage/updated"===e){if(t.threadId&&te&&String(t.threadId)!==String(te))return;(de||$e)&&Ye();const e=codexUsageSnapshotFromAppServer(t.tokenUsage);if(!e)return;if(Fe=e,$e){const{used:t,max:r}=e.context;Qe({kind:"usage",ctx:{used:t,max:r,pct:Math.min(100,Math.max(0,Math.round(t/r*100))),over:t>r,model:Te}})}return}if("turn/started"===e&&t.turn?.id){const e=String(t.turn.id);if(le||ie)return void ot(e);if(je){const t=je;return je=null,void t.resolve(e)}return Ue||(Ue=e),void Ye()}if("turn/plan/updated"!==e&&"item/agentMessage/delta"!==e&&"item/started"!==e&&"item/completed"!==e&&"error"!==e||st(t)){if(Ye(),"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 Qe({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||Ue||"active"}`,r=Xe.get(e)||{cid:`codex-stream:${te||"thread"}:${e}`,text:""};return r.text+=String(t.delta),Xe.set(e,r),void Qe({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&&(qe.set(r.id,r),r.id&&I(r)&&ze.add(r.id)),"item/completed"===e&&(r.id&&ze.delete(r.id),qe.set(r.id,r),"agentMessage"===r.type&&Xe.has(r.id))){const e=Xe.get(r.id);return Xe.delete(r.id),void Qe({kind:"assistant",blocks:[{type:"text",text:r.text||e.text}],parentToolUseId:null,replacesCid:e.cid})}return void et({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";et({type:"error",message:e})}}}async function lt(e,t={}){if(!st(t))return"item/tool/requestUserInput"===e?{answers:{}}:{decision:"decline"};const r=codexApprovalCard(e,t,qe.get(t.itemId));if(!r)throw new Error(`Unsupported Codex App Server request: ${e}`);let n="deny";Me++;try{n=await(q?.(r))}catch{}finally{Me=Math.max(0,Me-1),Ye()}return"item/tool/requestUserInput"===e?codexUserInputResponse(n,r.questions):codexApprovalResponse(n)}async function dt(){if(Ae)return!1;if(!1===Re?.alive)return Pe=!1,Ae=!0,!1;if(Re&&Pe)return!0;let t;try{t=await at()}catch(e){nt(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{if(!Re){const r=X({cwd:e,env:V,args:a({providerConfig:P,mcpUrl:t?.url,writableDirs:codexExtraWritableDirsForMode(K,E,Q.writableDirs),serviceTier:Ie}),onNotification:it,onServerRequest:lt,codexHost:{...J,log:nt},onClose:e=>{le||Ae||De.has(r)||(Pe=!1,Ae=!0,nt(`Codex App Server closed; later turns will use codex exec: ${e?.message||e}`))}});Re=r,await Re.start(),Ye()}return te=await Re.startThread({threadId:re>0?te:null,cwd:e,model:Te,serviceTier:F(Ie),sandbox:E,approvalPolicy:Y.approvalPolicy,config:{"features.default_mode_request_user_input":!0}}),Ye(),t?.url&&(await Re.waitForMcpServer({threadId:te,name:"thinkpool"}),Ye()),Pe=!0,et({type:"thread.started",thread_id:te}),!0}catch{Ae=!0;try{Re?.end()}catch{}return Re=null,Pe=!1,!1}}function ct({force:e=!1}={}){const t=Re,r=Ue;if(t&&e){ot(r),Ae=!0,Pe=!1;try{t.end()}catch{}Re===t&&(Re=null)}else if(t&&r)ot(r),Promise.resolve(t.interrupt({threadId:te,turnId:r})).catch(()=>{Ae=!0,Pe=!1;try{t.end()}catch{}Re===t&&(Re=null)});else if(t){Ae=!0,Pe=!1;try{t.end()}catch{}Re===t&&(Re=null)}if(ae){const e=ae;try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(ae===e)try{e.kill("SIGKILL")}catch{}},1500)}}async function ut(e,t={}){if(le||ie||be)return!0;if(!await dt())return!1;if(le||ie||be)return!0;Ve.setUsageBaseline(te?readCodexThreadUsage(te):null),de=!0;try{if(Se=!1,Ue=await Re.startTurn({threadId:te,input:e,images:Array.isArray(t.images)?t.images:[],model:Te,serviceTier:F(Ie),effort:C(Ee),approvalPolicy:Y.approvalPolicy,collaborationMode:codexDefaultCollaborationMode(Te,Ee)}),Ye(),ie||le){ot(Ue);try{await Re.interrupt({threadId:te,turnId:Ue})}catch{}return!0}const r=await Re.waitForTurn(Ue);ot(r?.turn?.id||Ue);const n=r?.turn?.status;ie?tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(we||be)||("interrupted"===n?tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(we=!1,et({type:"turn.failed",message:r?.turn?.error?.message||"codex turn failed"})):(we=!1,et({type:"turn.completed"})))}catch(e){Ae=!0,Pe=!1,ot(Ue);try{Re?.end()}catch{}Re=null,le||we||be||et({type:"turn.failed",message:`codex app-server turn failed: ${e?.message||e}`})}finally{Ze(),Ue=null,de=!1,ze.clear(),qe.clear(),Xe.clear()}return!0}async function pt(e){if(le||ie||be)return!0;if(!await dt())return!1;if(le||ie||be)return!0;Ve.setUsageBaseline(te?readCodexThreadUsage(te):null),de=!0;try{Se=!1;const t=await Re.startReview({threadId:te,target:x(e)});if(Ue=t?.turn?.id||t?.turnId,!Ue)throw new Error("Codex review/start returned no turn id");if(Ye(),ie||le){ot(Ue);try{await Re.interrupt({threadId:te,turnId:Ue})}catch{}return!0}const r=await Re.waitForTurn(Ue);ot(r?.turn?.id||Ue);const n=r?.turn?.status;ie?tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"interrupted"===n&&(we||be)||("interrupted"===n?tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:r?.turn?.durationMs,denials:0,resultText:null}):"failed"===n?(we=!1,et({type:"turn.failed",message:r?.turn?.error?.message||"codex review failed"})):(we=!1,et({type:"turn.completed"})))}catch(e){Ae=!0,Pe=!1,ot(Ue);try{Re?.end()}catch{}Re=null,le||we||be||et({type:"turn.failed",message:`codex review failed: ${e?.message||e}`})}finally{Ze(),Ue=null,de=!1,ze.clear(),qe.clear(),Xe.clear()}return!0}async function mt(t,n={}){if(le||ie||be)return;let o;try{o=await at()}catch(e){nt(`thinkpool peer tools unavailable: ${e?.message||e}`)}try{await(o?.handoff?.())}catch(e){nt(`thinkpool peer handoff failed: ${e?.message||e}`)}const a=buildCodexExecArgs({sessionId:0===re?null:te,model:Te,serviceTier:Ie,effort:Ee,sandbox:E,approvalPolicy:Y.approvalPolicy,providerConfig:P,mcpUrl:o?.url,writableDirs:codexExtraWritableDirsForMode(K,E,Q.writableDirs),images:Array.isArray(n.images)?n.images:[],prompt:t});Ve.setUsageBaseline(te?readCodexThreadUsage(te):null);const i=()=>new Promise((t,n)=>{let o;try{o=r.openSync("/dev/null","r")}catch{}const i={cwd:e,env:V,stdio:["ignore","pipe","pipe"]};null!=o&&(i.stdio=[o,"pipe","pipe"]),de=!0,Se=!1;try{ae=H("codex",a,i)}catch(e){if(null!=o)try{r.closeSync(o)}catch{}return de=!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=ae,d=[];s.createInterface({input:l.stdout,crlfDelay:1/0}).on("line",e=>{if(!e.trim())return;let t;try{t=JSON.parse(e)}catch{return}Ye(),d.push(t.type),"item.started"===t.type&&t.item?.id&&I(t.item)&&ze.add(t.item.id),"item.completed"===t.type&&t.item?.id&&ze.delete(t.item.id),"thread.started"===t.type&&t.thread_id&&(te=te||t.thread_id),"turn.completed"!==t.type&&"turn.failed"!==t.type||(u=!0,we=!1),et(t)});let c="",u=!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,ae===l&&(ae=null),de=!1,ze.clear(),e.codexExecFailure={spawnFailure:!0,hostRetrySafe:0===d.length,message:`codex failed to start: ${e.message}`,sawTerminalResult:!1},n(e))}),l.on("close",e=>{if(p)return;p=!0,ae===l&&(ae=null),de=!1,ze.clear();const r=`codex exec exited ${e}${c?": "+c.trim().slice(-300):""}`,o=0!==e&&null!=e&&!u&&codexExecFailedBeforeTurn({stderr:c,eventTypes:d});if(!u&&0!==e&&null!=e){const e=new Error(r);return e.codexExecFailure={preTurnInitFailure:o,hostRetrySafe:0===d.length,message:r,sawTerminalResult:!1},void n(e)}ie&&!u&&tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),t({preTurnInitFailure:!1})})});try{return await S(i,{...J,log:nt,shouldHeal:e=>!0===e?.codexExecFailure?.hostRetrySafe&&!0!==e?.codexExecFailure?.preTurnInitFailure})}catch(e){ae=null,de=!1,ze.clear();const t=e?.codexExecFailure||e?.cause?.codexExecFailure||{};if("TP_CODEX_HOST_MISSING"===e?.code)tt({kind:"error",message:e.message,recoverable:!0});else{if(t.preTurnInitFailure&&!ie&&!be)return{preTurnInitFailure:!0,message:t.message};(!we&&!be||ie)&&tt(ie?{kind:"result",subtype:"aborted",sessionId:te,model:Te,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 ft(){if(le)return;const e=Be.shift();e&&(He=He.then(async()=>{We={cids:new Set(e.options?.commandCid?[e.options.commandCid]:[]),completed:!1},Je.delete(e.options?.commandCid);const t=Math.max(0,Number(e.options?._stallRecoveryAttempt)||0);rt({recoveryAttempt:t}),t>0&&Ke({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:D,rolePrompt:U,roomContext:()=>se({force:n}),firstTurn:0===re,promptIndex:e.promptIndex,forceFullReminder:e.forceFullReminder,turnCause:r}),s=/^\s*\/review(?:\s|$)/i.test(String(e.text||""));let a=!1,i=!1;for(;!le&&!ie&&!be;){if(!(s?await pt(e.text):await ut(o,e.options)))if(s)we=!1,tt({kind:"error",message:"Codex review is unavailable without the tested App Server runtime.",recoverable:!0});else{we&&(we=!1,a=!0,rt({retry:!0,recoveryAttempt:t}),Ke({kind:"note",text:"retrying the stalled turn on a fresh connection"}));const r=await mt(o,e.options);if(r?.preTurnInitFailure){if(!(i||le||ie||be)){i=!0,rt({retry:!0,recoveryAttempt:t}),Ke({kind:"note",text:"Codex initialization failed before the turn started; retrying once with a fresh thinkpool connection."});continue}tt({kind:"error",message:r.message,recoverable:!0})}}if(!we||a||be||le||ie)break;we=!1,a=!0,rt({retry:!0,recoveryAttempt:t}),Ke({kind:"note",text:"retrying the stalled turn on a fresh connection"})}re++,ft()}))}return Z&&!ee&&queueMicrotask(()=>nt("The stopped Codex turn had no resumable context; continuing in a fresh thread.")),["0","false","off"].includes(String(V.TP_CODEX_APP_SERVER||"").trim().toLowerCase())||Oe.enabled||queueMicrotask(()=>nt(`Codex App Server disabled: ${Oe.reason}`)),_e=ue(()=>{const e=ce()-ge,t=v({turnActive:de,awaitingUser:Me,quietMs:e,stallMs:me,forceStopMs:["max","ultra"].includes(Ee)?he:fe,stalledSent:ye,stallRetried:ve});if("none"===t)return;if("retry"===t&&!Se)return be=!0,we=!1,Ce>0?(Ke({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."}),Ke({kind:"suggestion",text:CODEX_STALL_RETRY_SUGGESTION})):ze.size>0?Ke({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."}):(Be.unshift({text:CODEX_STALL_CONTINUATION_PROMPT,options:{_stallRecoveryAttempt:1},promptIndex:ne++,forceFullReminder:!0}),Ke({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."})),tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),void ct({force:!0});const r=w(t,e);if(r&&Ke(r),"status"!==t){if("retry"===t)return ve=!0,we=!0,ge=ce(),ye=!1,void ct();be=!0,we=!1,Be.length=0,tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:1,durationMs:void 0,denials:0,resultText:null}),ct({force:!0})}else ye=!0},xe),_e.unref?.(),{get sessionId(){return te},get usageSnapshot(){return Fe},get turnActive(){return de||$e},get canSteer(){return!(!Re||!1===Re.alive||!Pe||Ae)},get started(){return de||$e||!!ae||!(!Re||!1===Re.alive)},sendTurn(t,r={}){if(le)return!1;if(!de&&0===Be.length&&!Pe&&!ae){const e="function"==typeof B?B():{ok:!0};if(!1===e?.ok)return Qe({kind:"error",message:e.reason||"Host memory is critically low. This Codex runtime was not started.",recoverable:!0}),!1}const n=ne++,o=oe;if(oe=g(t)||/^\s*\/(?:reset|clear)\b/i.test(String(t||"")),!de&&0===re&&L)try{e=L()||e}catch{}if(de&&Re&&Ue){Ye();const e=r?.turnCause||null,s=f({promptIndex:n,forceFull:o,turnCause:e}),a=buildCodexPrompt({text:t,terminalRolePrompt:D,rolePrompt:U,roomContext:()=>se({force:s}),promptIndex:n,forceFullReminder:o,turnCause:e}),i=Ue,l=We;return Ge=Ge.then(()=>Re.steer({threadId:te,turnId:i,input:a,images:Array.isArray(r.images)?r.images:[]})).then(e=>{if(e?.turnId!==i)throw new Error("Codex steer did not acknowledge the expected turn");if(r.commandCid&&l&&(l.cids.add(r.commandCid),l.completed))try{A?.(r.commandCid)}catch{}}).catch(e=>{if("rejected"===e?.delivery||"not_sent"===e?.delivery){if(ie||le)return;r.commandCid&&Je.add(r.commandCid),Be.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),de||1!==Be.length||ft()}else Qe({kind:"error",message:"Codex steering delivery is uncertain; the message was not replayed automatically.",recoverable:!0})}),!0}return r.commandCid&&Je.add(r.commandCid),Be.push({text:t,options:r,promptIndex:n,forceFullReminder:o}),de||$e||(ie=!1,rt({recoveryAttempt:0})),1!==Be.length||$e||ft(),!0},async park(){if(le||de||$e||Be.length)return!1;const e=He;try{await e}catch{}try{await Ge}catch{}if(le||de||$e||Be.length||ae||Ue)return!1;const t=Re;if(t){De.add(t),Re=null,Pe=!1,qe.clear(),Xe.clear();try{t.end()}catch{return!1}}return!0},abort(){const e=He,t=de&&!Ue&&!ae;ie=!0,we=!1,be=!1,Be.length=0,ot(Ue);let r=Promise.resolve();if(Re&&Ue&&(r=Re.interrupt({threadId:te,turnId:Ue}).catch(()=>{})),ae){try{ae.kill("SIGTERM")}catch{}setTimeout(()=>{try{ae?.kill("SIGKILL")}catch{}},1500)}else t&&tt({kind:"result",subtype:"aborted",sessionId:te,model:Te,costUsd:null,usage:null,numTurns:0,durationMs:void 0,denials:0,resultText:null});return r.then(()=>e).catch(()=>e).then(()=>{})},end(){if(le=!0,de=!1,_e&&pe(_e),Qe.cancel(),Be.length=0,ot(Ue),ae)try{ae.kill()}catch{}try{Re?.end()}catch{}Ne?.then(e=>e.close()).catch(()=>{})},setModel(e){if(!e||de)return!1;const t=String(e);if(!ke.has(t))return!1;Te=t,Ve.setModel(Te);const r=_(Ee,{models:p,model:Te});return r!==Ee&&(Ee=r,Qe({kind:"effort",level:Ee})),"fast"!==Ie||codexModelSupportsFastMode({models:p,model:Te})||(Ie="default"),!0},setServiceTier(e){if(de||$e||Be.length)return!1;const t=normalizeCodexServiceTier(e);if(!t)return!1;if("fast"===t&&!codexModelSupportsFastMode({models:p,model:Te}))return!1;if(t===Ie)return!0;Ie=t;const r=Re;if(r){De.add(r),Re=null,Pe=!1,qe.clear(),Xe.clear();try{r.end()}catch{}}return!0},setMode:e=>!(de||!CODEX_MODE_CONFIG[e]||(K=e,Y=codexConfigForMode(K),E=Y.sandbox,te=null,re=0,Fe=null,Pe=!1,0)),setEffort:e=>!(de||!M({models:p,model:Te}).includes(e)||(Ee=e,Qe({kind:"effort",level:Ee}),0)),get serviceTier(){return Ie},get supportsFastMode(){return codexModelSupportsFastMode({models:p,model:Te})},clearContext:()=>!de&&(te=null,re=0,Fe=null,Pe=!1,!0),async compactContext(){if(de||$e)return!1;ie=!1,$e=!0;let e=null,t=!1;try{if(!await dt()||de)return!1;const r=new Promise((t,r)=>{e=setTimeout(()=>{je=null,r(new Error("Codex did not start the compaction turn"))},5e3),je={resolve:t,reject:r}});await Re.compact({threadId:te}),t=!0;const n=await r;e&&clearTimeout(e);const o=await Re.waitForTurn(n);ot(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)?(nt(`Native Codex compaction status is uncertain; existing context was preserved: ${e?.message||e}`),null):(nt(`Native Codex compaction unavailable; using bounded recap fallback: ${e?.message||e}`),!1)}finally{e&&clearTimeout(e),je=null,$e=!1,Be.length&&ft()}},async accountUsage(){if(!await dt())return null;const[e,t]=await Promise.all([Re.accountUsage().catch(()=>null),Re.accountRateLimits().catch(()=>null)]);return{usage:e,limits:t}}}}
|
package/cross-terminal.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e}from"node:crypto";import{BLOCKED_REASON as t,projectLaneLifecycle as r}from"./lane-lifecycle.mjs";import{normalizeCascadeMetadata as n}from"./worker-completion.mjs";export const PEEK={defaultLines:20,maxLines:200,perTurnCap:10,rosterPerTurnCap:1,lineCap:200,finalAssistantCap:24e3,rosterPreview:100};export const readTerminalBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0}={},n=PEEK)=>{const o=Math.max(0,Number(t)||0),i=Math.max(0,Number(r)||0);return e?o>=n.perTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`Cross-terminal transcript read limit reached for this turn (${n.perTurnCap}). Stop polling and continue with the worker results already collected; a fresh accepted room turn resets the allowance.`}:{ok:!0,targetedCount:o+1,rosterCount:i}:i>=n.rosterPerTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`The full terminal roster was already read this turn (${n.rosterPerTurnCap}/${n.rosterPerTurnCap}). Use the current ROOM NOW snapshot or read one relevant terminal by ref or name.`}:{ok:!0,targetedCount:o,rosterCount:i+1}};export const readTerminalTurnBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0,budgetTurnRev:n=null,currentTurnRev:o=0}={},i=PEEK)=>{const s=Math.max(0,Number(o)||0),a=(null==n?null:Number.isFinite(Number(n))?Number(n):null)===s;return{...readTerminalBudgetDecision({targeted:e,targetedCount:a?t:0,rosterCount:a?r:0},i),budgetTurnRev:s}};export const LANE_STATUS={stuckAfterMs:9e4,repeatedFailureCount:2,failureWindow:12};const o=e=>!!e&&("error"===e.kind||"tool_result"===e.kind&&e.isError||"result"===e.kind&&e.subtype&&"success"!==e.subtype);export const laneBusyOf=(e={})=>"boolean"==typeof e?.busy?e.busy:"boolean"==typeof e?._busyAnn?e._busyAnn:e?.session?.turnActive??!1;export const settleLaneBusy=e=>!(!e||!1===e._busyAnn||(e._busyAnn=!1,0));export const settleLaneControl=(e,t=Date.now())=>!(!e||!0!==e._busyAnn||!settleLaneBusy(e)||(e._settledTurnRev=Number(e._turnRev)||null,e._turnSettledAt=Number(t)||Date.now(),e._turnStart=null,0));export const laneStatusOf=(e={},n=Date.now(),i=LANE_STATUS)=>{const s=Array.isArray(e?.log)?e.log:[],a=s.slice(-i.failureWindow);!e?.lastEvent||e.lastEvent===s[s.length-1]||e.lastEvent.cid&&e.lastEvent.cid===s[s.length-1]?.cid||a.push(e.lastEvent);let l=null;for(let e=a.length-1;e>=0;e--)if(Number.isFinite(a[e]?.ts)){l=a[e].ts;break}const c=Number.isFinite(e?.lastActionAt)?e.lastActionAt:l??null,d=null==c?null:Math.max(0,n-c),p=laneBusyOf(e),u=e?.pending instanceof Map?e.pending.size:Math.max(0,Number(e?.pendingCount)||0),m=e?.pending instanceof Map&&[...e.pending.values()].some(e=>"ask"===e?.payload?.risk)?t.NEEDS_INPUT:t.NEEDS_DECISION,h=((e,t=LANE_STATUS)=>{let r=0,n=0;for(let i=(Array.isArray(e)?e.length:0)-1;i>=0&&n<t.failureWindow;i--){const t=e[i];if(t&&"object"==typeof t){if(n++,"result"===t.kind&&(!t.subtype||"success"===t.subtype)||"you"===t.kind)break;o(t)&&r++}}return r})(a,i);let f=null,g=null;return u>0?(f="BLOCKED",g="waiting for input"):e?.stalled||p&&!e?.compacting&&null!=d&&d>=i.stuckAfterMs?(f="STUCK",g="no events while busy"):h>=i.repeatedFailureCount&&(f="BLOCKED",g="repeated errors"),{busy:!!p,lastActionAt:c,lastActionAgeMs:d,statusFlag:f,statusReason:g,lifecycle:r({events:a,busy:!!p,pendingCount:u,pendingReason:m,stalled:"STUCK"===f,failed:h>=i.repeatedFailureCount&&!p,rawStatus:f||(p?"busy":"idle")})}};export const formatLaneAge=e=>{if(!Number.isFinite(e))return"no activity";const t=Math.max(0,Math.floor(e/1e3));if(t<60)return`${t}s ago`;const r=Math.floor(t/60);if(r<60)return`${r}m ago`;const n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`};const i=e=>`${e.lifecycle?.state||"Blocked"}${e.lifecycle?.reason?` (${e.lifecycle.reason})`:""} · ${formatLaneAge(e.lastActionAgeMs)}`,s=(e,t)=>{const r=String(null==e?"":e).replace(/\s+/g," ").trim();return r.length>t?r.slice(0,t-1)+"…":r};export const digestLogEvent=(e,t=PEEK.lineCap)=>{if(!e||"object"!=typeof e)return"";switch(e.kind){case"you":return s(`${e.by||"someone"}: ${e.text||""}`,t);case"assistant":{const r=[];for(const t of Array.isArray(e.blocks)?e.blocks:[])"text"===t?.type&&String(t.text||"").trim()?r.push(String(t.text).trim()):"tool_use"===t?.type&&t.name&&r.push(`→ ${t.name}`);return r.length?s(`assistant: ${r.join(" ")}`,t):""}case"tool_result":return e.isError?s("⎿ error",t):"";case"result":return e.subtype&&"success"!==e.subtype?s(`turn ended — ${e.subtype}`,t):"";case"error":return s(`error: ${e.message||""}`,t);default:return""}};const a=e=>"assistant"===e?.kind&&(Array.isArray(e.blocks)?e.blocks:[]).some(e=>"text"===e?.type&&String(e.text||"").trim());export const cleanPtyTail=(e,t)=>String(e||"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[@-_][0-?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").split(/\r?\n/).map(e=>e.replace(/\s+$/,"")).filter(e=>e.trim()).slice(-Math.max(1,t));export const siblingRef=e=>String(e).slice(0,8);export const siblingsOf=({selfId:e,sessions:t,terms:r,names:n={},now:o=Date.now()}={})=>{const i=e=>n&&n[e]||"",s=[];for(const[r,n]of t||new Map)r!==e&&s.push({id:r,ref:siblingRef(r),name:i(r),kind:"agent",cmd:n?.cmd||"claude",log:Array.isArray(n?.log)?n.log:[],...laneStatusOf(n,o)});for(const[t,n]of r||new Map)t!==e&&s.push({id:t,ref:siblingRef(t),name:i(t),kind:"shell",cmd:n?.cmd||"shell",buf:n?.buf||"",...laneStatusOf(n,o)});return s};export const isOrdinalRef=e=>/^(terminal\s*)?\d+$/i.test(e)||/^t\d+$/i.test(e);export const resolveSibling=(e,t)=>{const r=String(t||"").toLowerCase().trim();if(!r)return null;const n=Array.isArray(e)?e:[];return n.find(e=>String(e.id).toLowerCase()===r)||n.find(e=>String(e.name||"").toLowerCase()===r)||n.find(e=>String(e.ref).toLowerCase()===r)||(isOrdinalRef(r)?null:n.find(e=>r.length>=4&&String(e.id).toLowerCase().startsWith(r))||n.find(e=>e.name&&String(e.name).toLowerCase().includes(r))||n.find(e=>String(e.cmd).toLowerCase().includes(r))||null)};const l=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const formatPeek=({selfId:e,sessions:t,terms:r,names:n={},terminal:o,lines:c,now:d=Date.now()}={})=>{const p=Math.min(PEEK.maxLines,Math.max(1,Number(c)||PEEK.defaultLines)),u=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:d});if(!u.length)return"No other terminals are open in this room right now.";if(!o)return`Other terminals in this room (call read_terminal with a ref or name to read one):\n${u.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,PEEK.rosterPreview)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${e.cmd} · ${i(e)} — ${s(t,PEEK.rosterPreview)}`}).join("\n")}`;const m=resolveSibling(u,o);if(!m)return`No terminal matches "${o}". Call read_terminal with no argument to list the open terminals (refer to one by its name or ref).${isOrdinalRef(String(o||"").toLowerCase().trim())?" On-screen numbers like \"Terminal N\" shift when a terminal closes, so they can't identify a lane — use a terminal's name or ref instead.":""}`;let h;if("agent"===m.kind){let e=-1;if("Done"===m.lifecycle?.state)for(let t=m.log.length-1;t>=0;t--)if(a(m.log[t])){e=t;break}h=m.log.map((t,r)=>digestLogEvent(t,r===e?PEEK.finalAssistantCap:PEEK.lineCap)).filter(Boolean).slice(-p).join("\n")||"(no activity yet)"}else h=cleanPtyTail(m.buf,p).join("\n")||"(no output yet)";return`Terminal ${l(m)} (${m.kind} · ${m.cmd}) · ${i(m)} — last ${p} lines:\n${h}`};export const nativeClaudeProviderAccessFailed=e=>{let t="";try{t="string"==typeof e?e:JSON.stringify(e||"")}catch{t=String(e||"")}return/organization has disabled (?:claude(?: subscription access)?|subscription access)|(?:claude )?subscription access (?:has been |is )?disabled|invalid anthropic (?:api )?key|invalid x-api-key|not logged in to claude|(?:run|use) \/login[^\n]{0,80}claude|no valid anthropic (?:api )?key/i.test(t)};const c=Object.freeze(["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"]);export const preferredHermesClaudeReviewModel=(e=[])=>{const t=new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));for(const e of c)if(t.has(e))return e;for(const e of t)if(/^nous:anthropic\/claude-sonnet-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-haiku-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-opus-/i.test(e))return e;return null};export const nativeClaudeFallbackHint=({parentRuntime:e,parentModels:t,targetRuntime:r,targetLog:n}={})=>{if("claude"!==r||!nativeClaudeProviderAccessFailed(n))return"";const o="thinkpool provider status: native Claude access failed at the account/provider level. Do not retry Sonnet, Opus, Haiku, or another model alias on the same native Claude provider during this task.",i="hermes"===e?preferredHermesClaudeReviewModel(t):null;return i?`\n\n${o} For a visible manual review fallback, spawn exactly one worker with runtime="hermes", model=${JSON.stringify(i)}, mode="default", and sliceType="review". This Hermes lane is permission-gated, not structurally read-only: instruct it not to edit, deny/stop any write request, and do not treat its review as a Flow safety proof. Never use Hermes delegate_task.`:`\n\n${o} Use a different available runtime/provider once, or report the blocker.`};export const ROOMNOW={laneCap:8,previewLen:70,wtCap:10};export const formatRoomNow=({selfId:e,sessions:t,terms:r,names:n={},worktrees:o=[],now:a=Date.now()}={},c=ROOMNOW)=>{const d=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:a}),p=[];if(d.length){const e=d.slice(0,c.laneCap),t=e.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,c.previewLen)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${i(e)} — ${s(t,c.previewLen)}`});d.length>e.length&&t.push(`- … +${d.length-e.length} more (read_terminal lists all)`),p.push(`Other lanes in this room right now (this is the roster; use targeted read_terminal only for needed detail):\n${t.join("\n")}`)}const u=Array.isArray(o)?o:[],m=u.slice(0,c.wtCap);if(m.length){const e=m.map(e=>`- ${s(e,c.previewLen+30)}`);u.length>m.length&&e.push(`- … +${u.length-m.length} more (\`git worktree list\` shows all)`),p.push(`Active git worktrees on this host (code edits go in your OWN worktree — never one another lane is on):\n${e.join("\n")}`)}return p.length?`ROOM NOW (live snapshot, this turn):\n${p.join("\n")}`:null};export const CROSSPOST={maxHop:1,perTurnCap:3};export const crossPostDecision=({hop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSPOST)=>{const o={...CROSSPOST,...n||{}};return r?{ok:!1,reason:"Cross-terminal posting is disabled in this room."}:e>=o.maxHop?{ok:!1,reason:"Cross-terminal hop limit reached — an agent reached via cross-post cannot itself post onward; a person must direct the next cross-terminal action."}:t>=o.perTurnCap?{ok:!1,reason:`Cross-terminal post limit reached for this turn (${o.perTurnCap}).`}:{ok:!0}};export const crossPostNeedsCard=({mode:e="default",spawnedByMe:t=!1}={})=>!("bypassPermissions"===e||!0===t);export const SPAWN={maxHop:1,perWindowCap:6,windowMs:6e5,freeMaxSpawned:3,plusMaxSpawned:6,machineMax:12};export const maxSpawnedFor=(e,t=SPAWN)=>{const r={...SPAWN,...t||{}};return"plus"===e?r.plusMaxSpawned:r.freeMaxSpawned};export const isActiveDispatchedWorker=e=>!(!e?.spawnedBy||"conductor"===e.flowRole||e.flowTaskKey&&e.flowDone||!0===e.workerSettled);export const countActiveDispatchedWorkers=(e=[])=>[...e].filter(isActiveDispatchedWorker).length;export const isSettledDispatchedWorker=e=>!!e?.spawnedBy&&!String(e.spawnedBy).startsWith("flow:")&&!0===e.workerSettled;export const countMachineRuntimeLanes=(e=[])=>[...e].filter(e=>!isSettledDispatchedWorker(e)).length;export const machineCapacityDecision=({totalLive:e=0,disabled:t=!1}={},r=SPAWN)=>{const n={...SPAWN,...r||{}};return t?{ok:!1,reason:"Spawning terminals is disabled in this room."}:e>=n.machineMax?{ok:!1,reason:`Room is at the machine cap (${n.machineMax} live lanes) — close one before opening another.`}:{ok:!0}};export const activeWorkerCapacityDecision=({spawnedLive:e=0,totalLive:t=0,plan:r="free",disabled:n=!1}={},o=SPAWN)=>{const i={...SPAWN,...o||{}},s=machineCapacityDecision({totalLive:t,disabled:n},i);if(!s.ok)return s;const a=maxSpawnedFor(r,i);return e>=a?{ok:!1,reason:`Dispatch ceiling reached (${a} lane${1===a?"":"s"} on the ${r} plan)${"plus"!==r?` — Plus raises it to ${i.plusMaxSpawned}.`:"."} Close a dispatched lane to open another.`}:{ok:!0}};export const recentSpawnCount=(e,t,r=SPAWN.windowMs)=>(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>t-r).length;export const spawnDecision=({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN)=>{const l={...SPAWN,...a||{}};if(s)return{ok:!1,reason:"Spawning terminals is disabled in this room."};if(e>=l.maxHop)return{ok:!1,reason:"Spawn limit reached — a terminal reached via cross-terminal cannot itself open further terminals; a person must open the next lane."};const c=activeWorkerCapacityDecision({spawnedLive:n,totalLive:o,plan:i},l);if(!c.ok)return c;if(recentSpawnCount(t,r,l.windowMs)>=l.perWindowCap){const e=Math.round(l.windowMs/6e4);return{ok:!1,reason:`Spawn burst limit reached (${l.perWindowCap} in ${e} min) — this replenishes as the window slides forward, or instantly when a person types into this lane.`}}return{ok:!0}};const d=Object.freeze(["cascade","mode","model","name","provider","runtime","sliceType","task"]),p=new Set(["default","acceptEdits","bypassPermissions","plan","review"]),u=new Set(["claude","codex","hermes"]),m=new Set(["scaffold","feature","fix","review"]),h=/((?<![a-z0-9])sk-[a-z0-9_-]{8,}|(?<![a-z0-9])gsk_[a-z0-9_-]{8,}|(?<![a-z0-9])AIza[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,})/gi,f=/(?:\/home\/|\/users\/|\/private\/|\/tmp\/|[a-z]:\\|\\\\)/gi,g=e=>Array.isArray(e)?e.map(g):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,g(e[t])])):e,y=(e,t)=>null==e?null:String(e).slice(0,t),w=(e,t)=>y(e,t)?.replace(h,"[credential redacted]").replace(f,"[host path redacted]")||"";export function normalizeDispatchArgs(e={},t={}){if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!d.includes(e)))throw new TypeError("Invalid dispatch arguments");const r=e.runtime||t.runtime||"claude",o=e.mode||("plan"===t.mode?"default":t.mode)||"default";if(!u.has(r)||!p.has(o)||e.sliceType&&!m.has(e.sliceType))throw new TypeError("Invalid dispatch arguments");const i=null==e.cascade?null:n(e.cascade);if(null!=e.cascade&&!i)throw new TypeError("Invalid Cascade metadata");return Object.freeze({cascade:i?Object.freeze(i):null,name:y(e.name,80),task:y(e.task,12e3),model:y(e.model,160),sliceType:y(e.sliceType,24),runtime:r,provider:y(e.provider,160),mode:o})}export function dispatchCapSnapshot({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN){const l={...SPAWN,...a||{}},c="plus"===i?"plus":"free",d=maxSpawnedFor(c,l);return Object.freeze({killSwitch:!0===s,hop:Object.freeze({used:Math.max(0,Number(e)||0),limit:l.maxHop}),width:Object.freeze({requested:1,live:Math.max(0,Number(n)||0),limit:d}),machine:Object.freeze({live:Math.max(0,Number(o)||0),limit:l.machineMax}),plan:Object.freeze({name:c,price:"plus"===c?"€5/mo":"€0",used:Math.max(0,Number(n)||0),limit:d}),burst:Object.freeze({used:recentSpawnCount(t,r,l.windowMs),limit:l.perWindowCap,windowMinutes:Math.round(l.windowMs/6e4)})})}export function buildDispatchPreview({args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){if(!(r&&n&&o&&i))throw new TypeError("Dispatch authority is incomplete");const l=normalizeDispatchArgs(t,{runtime:s.runtime,mode:s.mode}),c=dispatchCapSnapshot(s,a),d=(p={authority:Object.freeze({initiatorTerminalId:String(r),roomCode:String(n).toUpperCase(),bridgeAuthorityId:String(o),localAuthorityId:String(i)}),exactArgs:l,caps:c},e("sha256").update(JSON.stringify(g(p))).digest("hex"));var p;const u=l.provider||("codex"===l.runtime?"OpenAI / Codex login":"hermes"===l.runtime?"Hermes ACP profile":"Anthropic / host default"),m=l.model||s.resolvedModel||"runtime default",h=w(l.name||l.task?.split(/\r?\n/)[0]||"Open one worker lane",120);return Object.freeze({version:1,fingerprint:d,purpose:h,packages:Object.freeze([{title:w(l.name||"Worker package",80),task:w(l.task||"Open idle for a later hand-off.",600)}]),runtime:Object.freeze({name:l.runtime,provider:w(s.providerName||u,120),model:w(m,160),band:"Start: now–1 min · completion: task-dependent",execution:"Runs locally on the bridge machine; no cloud worker is created.",accountAndSpend:`Uses the host’s ${w(s.providerName||u,100)} account/BYOK. Provider usage is the spend source; thinkpool has no credit meter.`}),permissions:Object.freeze({mode:l.mode,consequence:"bypassPermissions"===l.mode?"Unattended local access within this worker’s sandbox and tool policy.":"review"===l.mode?"Read-only inspection of the pinned parent snapshot; writes and approval escapes are unavailable.":"plan"===l.mode?"Planning only; execution remains gated.":"Protected local actions keep their normal permission gates."}),worktree:"Automatically creates one visible lateral worker lane and an isolated git worktree on this machine.",caps:c})}export function authorizeDispatchApproval({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},decision:l="allow",consumed:c=!1,permissionId:d=null,consumedPermissionId:p=null,limits:u=SPAWN}={}){return"allow"!==l?{ok:!1,code:"dispatch_canceled"}:c?{ok:!1,code:d&&d===p?"idempotent_replay":"approval_replayed"}:b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:u})}export function authorizeDirectDispatch({preview:e,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){return b({preview:e,approvedFingerprint:e?.fingerprint,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s,limits:a})}function b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},limits:l=SPAWN}={}){let c;try{c=buildDispatchPreview({args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:l})}catch{return{ok:!1,code:"invalid_dispatch"}}if(!e||e.fingerprint!==t)return{ok:!1,code:"approval_mismatch"};if(c.fingerprint!==e.fingerprint)return{ok:!1,code:"stale_dispatch"};const d=spawnDecision(a,l);return d.ok?{ok:!0,code:"approved",preview:c}:{ok:!1,code:"cap_denied",reason:d.reason}}export function verifyDurableDispatchAuthority({item:e,events:t=[],expected:r={}}={}){const n=e=>({ok:!1,code:e}),o=String(r.fingerprint||"");if(!e||e.id!==r.controlItemId)return n("control_item_mismatch");if(e.session_code!==String(r.roomCode||"").toUpperCase())return n("wrong_room");if("dispatch"!==e.action_kind||"approval"!==e.item_kind)return n("wrong_action");if("resolved"!==e.status||"approved"!==e.resolution_code||!e.resolved_by)return n("not_approved");if("either_member"!==e.resolution_policy||null!=e.required_actor_id)return n("wrong_resolution_policy");if(e.bridge_authority_id!==r.bridgeAuthorityId)return n("wrong_bridge_authority");if(e.local_authority_id!==r.localAuthorityId)return n("wrong_local_authority");if(!/^[0-9a-f]{64}$/.test(o))return n("invalid_fingerprint");if(!String(e.request_context?.action||"").includes(`fp:${o}`))return n("request_fingerprint_mismatch");if(e.resolution_payload?.dispatch_fingerprint!==o)return n("resolution_fingerprint_mismatch");const i=(Array.isArray(t)?t:[]).filter(t=>t?.item_id===e.id);if(1!==i.filter(t=>"resolved"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.resolution_payload?.dispatch_fingerprint===o&&"approved"===t.payload?.resolution_code).length)return n("resolution_event_mismatch");const s=i.filter(t=>"delivery_attempted"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.data?.dispatch_fingerprint===o);return 1!==s.length?n("delivery_attempt_mismatch"):i.some(e=>"delivered"===e.event_type||"delivery_failed"===e.event_type)?n("delivery_already_final"):{ok:!0,code:"durable_dispatch_authorized",fingerprint:o,humanActorId:e.resolved_by,attemptKey:s[0].correlation_key}}export const CLOSABLE_HINT_MAX=6;export const closeLaneDecision=({callerId:e,spawnedBy:t,busy:r}={})=>e&&t===e?r?{ok:!1,reason:"busy"}:{ok:!0}:{ok:!1,reason:"not-owner"};export const closableLanes=(e,t=[])=>(Array.isArray(t)?t:[]).filter(t=>t&&closeLaneDecision({callerId:e,spawnedBy:t.spawnedBy,busy:t.busy}).ok);export const formatClosableHint=(e,t=[],r=6)=>{const n=closableLanes(e,t);if(!n.length)return"";const o=n.slice(0,r),i=o.map(e=>`${e.ref}${e.name?` ("${e.name}")`:""}`).join(", "),s=n.length>o.length?` (+${n.length-o.length} more)`:"";return`\n\nYou have ${n.length} finished lane${1===n.length?"":"s"} preserved for review. A person can free a slot by closing one with the X in its Ensemble row: ${i}${s}`};export const CROSSROOM={peekPerTurnCap:5};export const PEER_READ_MCP_TOOLS=Object.freeze(["read_terminal","list_sessions","read_session"]);export const PEER_POST_MCP_TOOLS=Object.freeze(["post_to_terminal","post_to_session"]);export const PEER_MCP_TOOLS=Object.freeze([...PEER_READ_MCP_TOOLS,...PEER_POST_MCP_TOOLS]);export const formatPairRoster=(e,{thisRoom:t,error:r}={})=>{if(r)return String(r);const n=new Set,o=(Array.isArray(e)?e:[]).filter(e=>!(!e||!e.code||e.code===t||n.has(e.code)||(n.add(e.code),0)));return o.length?`Your other thinkpool Code sessions (call read_session with a room code to look inside one):\n${o.map(e=>`- ${e.code}${e.name?` · "${e.name}"`:""}${e.host?` · on ${e.host}`:""}${!1===e.online?" · partner bridge offline":""}`).join("\n")}`:"No other sessions of yours are open right now. (Cross-session reach is limited to rooms this account is running here, plus your partner's rooms in the same pair.)"};export const CROSSROOM_BUS={busTimeoutMs:4e3};export const pairKeyFor=(e,t)=>{const r=String(null==e?"":e).toLowerCase().trim(),n=String(null==t?"":t).toLowerCase().trim();return r&&n&&r!==n?r<n?`${r}.${n}`:`${n}.${r}`:null};export const pairTopic=e=>e?`tppair:${e}`:null;export const CROSSROOM_POST={maxRoomHop:1,crossRoomPostCap:1};export const crossRoomPostDecision=({roomHop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSROOM_POST)=>{const o={...CROSSROOM_POST,...n||{}};return r?{ok:!1,reason:"Cross-room posting is turned off on this bridge."}:e>=o.maxRoomHop?{ok:!1,reason:"Cross-room hop limit reached — a room reached via a cross-room post cannot itself post into another room; a person must direct the next cross-room action."}:t>=o.crossRoomPostCap?{ok:!1,reason:`Cross-room post limit reached for this turn (${o.crossRoomPostCap}).`}:{ok:!0}};
|
|
1
|
+
import{createHash as e}from"node:crypto";import{BLOCKED_REASON as t,projectLaneLifecycle as r}from"./lane-lifecycle.mjs";import{normalizeCascadeMetadata as n}from"./worker-completion.mjs";export const PEEK={defaultLines:20,maxLines:200,perTurnCap:10,rosterPerTurnCap:1,lineCap:200,finalAssistantCap:24e3,rosterPreview:100};export const readTerminalBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0}={},n=PEEK)=>{const o=Math.max(0,Number(t)||0),i=Math.max(0,Number(r)||0);return e?o>=n.perTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`Cross-terminal transcript read limit reached for this turn (${n.perTurnCap}). Stop polling and continue with the worker results already collected; a fresh accepted room turn resets the allowance.`}:{ok:!0,targetedCount:o+1,rosterCount:i}:i>=n.rosterPerTurnCap?{ok:!1,targetedCount:o,rosterCount:i,reason:`The full terminal roster was already read this turn (${n.rosterPerTurnCap}/${n.rosterPerTurnCap}). Use the current ROOM NOW snapshot or read one relevant terminal by ref or name.`}:{ok:!0,targetedCount:o,rosterCount:i+1}};export const readTerminalTurnBudgetDecision=({targeted:e=!1,targetedCount:t=0,rosterCount:r=0,budgetTurnRev:n=null,currentTurnRev:o=0}={},i=PEEK)=>{const s=Math.max(0,Number(o)||0),a=(null==n?null:Number.isFinite(Number(n))?Number(n):null)===s;return{...readTerminalBudgetDecision({targeted:e,targetedCount:a?t:0,rosterCount:a?r:0},i),budgetTurnRev:s}};export const LANE_STATUS={stuckAfterMs:9e4,repeatedFailureCount:2,failureWindow:12};const o=e=>!!e&&("error"===e.kind&&!0!==e.recoverable||"tool_result"===e.kind&&e.isError||"result"===e.kind&&e.subtype&&"success"!==e.subtype);export const laneBusyOf=(e={})=>"boolean"==typeof e?.busy?e.busy:"boolean"==typeof e?._busyAnn?e._busyAnn:e?.session?.turnActive??!1;export const settleLaneBusy=e=>!(!e||!1===e._busyAnn||(e._busyAnn=!1,0));export const settleLaneControl=(e,t=Date.now())=>!(!e||!0!==e._busyAnn||!settleLaneBusy(e)||(e._settledTurnRev=Number(e._turnRev)||null,e._turnSettledAt=Number(t)||Date.now(),e._turnStart=null,0));export const laneStatusOf=(e={},n=Date.now(),i=LANE_STATUS)=>{const s=Array.isArray(e?.log)?e.log:[],a=s.slice(-i.failureWindow);!e?.lastEvent||e.lastEvent===s[s.length-1]||e.lastEvent.cid&&e.lastEvent.cid===s[s.length-1]?.cid||a.push(e.lastEvent);let l=null;for(let e=a.length-1;e>=0;e--)if(Number.isFinite(a[e]?.ts)){l=a[e].ts;break}const c=Number.isFinite(e?.lastActionAt)?e.lastActionAt:l??null,d=null==c?null:Math.max(0,n-c),p=laneBusyOf(e),u=e?.pending instanceof Map?e.pending.size:Math.max(0,Number(e?.pendingCount)||0),m=e?.pending instanceof Map&&[...e.pending.values()].some(e=>"ask"===e?.payload?.risk)?t.NEEDS_INPUT:t.NEEDS_DECISION,h=((e,t=LANE_STATUS)=>{let r=0,n=0;for(let i=(Array.isArray(e)?e.length:0)-1;i>=0&&n<t.failureWindow;i--){const t=e[i];if(t&&"object"==typeof t){if(n++,"result"===t.kind&&(!t.subtype||"success"===t.subtype)||"you"===t.kind)break;o(t)&&r++}}return r})(a,i);let f=null,g=null;return u>0?(f="BLOCKED",g="waiting for input"):e?.stalled||p&&!e?.compacting&&null!=d&&d>=i.stuckAfterMs?(f="STUCK",g="no events while busy"):h>=i.repeatedFailureCount&&(f="BLOCKED",g="repeated errors"),{busy:!!p,lastActionAt:c,lastActionAgeMs:d,statusFlag:f,statusReason:g,lifecycle:r({events:a,busy:!!p,pendingCount:u,pendingReason:m,stalled:"STUCK"===f,failed:h>=i.repeatedFailureCount&&!p,rawStatus:f||(p?"busy":"idle")})}};export const formatLaneAge=e=>{if(!Number.isFinite(e))return"no activity";const t=Math.max(0,Math.floor(e/1e3));if(t<60)return`${t}s ago`;const r=Math.floor(t/60);if(r<60)return`${r}m ago`;const n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`};const i=e=>`${e.lifecycle?.state||"Blocked"}${e.lifecycle?.reason?` (${e.lifecycle.reason})`:""} · ${formatLaneAge(e.lastActionAgeMs)}`,s=(e,t)=>{const r=String(null==e?"":e).replace(/\s+/g," ").trim();return r.length>t?r.slice(0,t-1)+"…":r};export const digestLogEvent=(e,t=PEEK.lineCap)=>{if(!e||"object"!=typeof e)return"";switch(e.kind){case"you":return s(`${e.by||"someone"}: ${e.text||""}`,t);case"assistant":{const r=[];for(const t of Array.isArray(e.blocks)?e.blocks:[])"text"===t?.type&&String(t.text||"").trim()?r.push(String(t.text).trim()):"tool_use"===t?.type&&t.name&&r.push(`→ ${t.name}`);return r.length?s(`assistant: ${r.join(" ")}`,t):""}case"tool_result":return e.isError?s("⎿ error",t):"";case"result":return e.subtype&&"success"!==e.subtype?s(`turn ended — ${e.subtype}`,t):"";case"error":return s(`error: ${e.message||""}`,t);default:return""}};const a=e=>"assistant"===e?.kind&&(Array.isArray(e.blocks)?e.blocks:[]).some(e=>"text"===e?.type&&String(e.text||"").trim());export const cleanPtyTail=(e,t)=>String(e||"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[@-_][0-?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").split(/\r?\n/).map(e=>e.replace(/\s+$/,"")).filter(e=>e.trim()).slice(-Math.max(1,t));export const siblingRef=e=>String(e).slice(0,8);export const siblingsOf=({selfId:e,sessions:t,terms:r,names:n={},now:o=Date.now()}={})=>{const i=e=>n&&n[e]||"",s=[];for(const[r,n]of t||new Map)r!==e&&s.push({id:r,ref:siblingRef(r),name:i(r),kind:"agent",cmd:n?.cmd||"claude",log:Array.isArray(n?.log)?n.log:[],...laneStatusOf(n,o)});for(const[t,n]of r||new Map)t!==e&&s.push({id:t,ref:siblingRef(t),name:i(t),kind:"shell",cmd:n?.cmd||"shell",buf:n?.buf||"",...laneStatusOf(n,o)});return s};export const isOrdinalRef=e=>/^(terminal\s*)?\d+$/i.test(e)||/^t\d+$/i.test(e);export const resolveSibling=(e,t)=>{const r=String(t||"").toLowerCase().trim();if(!r)return null;const n=Array.isArray(e)?e:[];return n.find(e=>String(e.id).toLowerCase()===r)||n.find(e=>String(e.name||"").toLowerCase()===r)||n.find(e=>String(e.ref).toLowerCase()===r)||(isOrdinalRef(r)?null:n.find(e=>r.length>=4&&String(e.id).toLowerCase().startsWith(r))||n.find(e=>e.name&&String(e.name).toLowerCase().includes(r))||n.find(e=>String(e.cmd).toLowerCase().includes(r))||null)};const l=e=>`${e.ref}${e.name?` · "${e.name}"`:""}`;export const formatPeek=({selfId:e,sessions:t,terms:r,names:n={},terminal:o,lines:c,now:d=Date.now()}={})=>{const p=Math.min(PEEK.maxLines,Math.max(1,Number(c)||PEEK.defaultLines)),u=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:d});if(!o&&!u.length)return"No other terminals are open in this room right now.";if(!o)return`Other terminals in this room (call read_terminal with a ref or name to read one):\n${u.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,PEEK.rosterPreview)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${e.cmd} · ${i(e)} — ${s(t,PEEK.rosterPreview)}`}).join("\n")}`;const m=resolveSibling(siblingsOf({sessions:t,terms:r,names:n,now:d}),o);if(!m)return`No terminal matches "${o}". Call read_terminal with no argument to list the open terminals (refer to one by its name or ref).${isOrdinalRef(String(o||"").toLowerCase().trim())?" On-screen numbers like \"Terminal N\" shift when a terminal closes, so they can't identify a lane — use a terminal's name or ref instead.":""}`;let h;if("agent"===m.kind){let e=-1;if("Done"===m.lifecycle?.state)for(let t=m.log.length-1;t>=0;t--)if(a(m.log[t])){e=t;break}h=m.log.map((t,r)=>digestLogEvent(t,r===e?PEEK.finalAssistantCap:PEEK.lineCap)).filter(Boolean).slice(-p).join("\n")||"(no activity yet)"}else h=cleanPtyTail(m.buf,p).join("\n")||"(no output yet)";return`Terminal ${l(m)}${m.id===e?" (current terminal)":""} (${m.kind} · ${m.cmd}) · ${i(m)} — last ${p} lines:\n${h}`};export const nativeClaudeProviderAccessFailed=e=>{let t="";try{t="string"==typeof e?e:JSON.stringify(e||"")}catch{t=String(e||"")}return/organization has disabled (?:claude(?: subscription access)?|subscription access)|(?:claude )?subscription access (?:has been |is )?disabled|invalid anthropic (?:api )?key|invalid x-api-key|not logged in to claude|(?:run|use) \/login[^\n]{0,80}claude|no valid anthropic (?:api )?key/i.test(t)};const c=Object.freeze(["nous:anthropic/claude-sonnet-4.6","nous:anthropic/claude-sonnet-4.5","nous:anthropic/claude-sonnet-4"]);export const preferredHermesClaudeReviewModel=(e=[])=>{const t=new Set((Array.isArray(e)?e:[]).map(e=>"string"==typeof e?e:e?.value||e?.slug).filter(Boolean));for(const e of c)if(t.has(e))return e;for(const e of t)if(/^nous:anthropic\/claude-sonnet-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-haiku-/i.test(e))return e;for(const e of t)if(/^nous:anthropic\/claude-opus-/i.test(e))return e;return null};export const nativeClaudeFallbackHint=({parentRuntime:e,parentModels:t,targetRuntime:r,targetLog:n}={})=>{if("claude"!==r||!nativeClaudeProviderAccessFailed(n))return"";const o="thinkpool provider status: native Claude access failed at the account/provider level. Do not retry Sonnet, Opus, Haiku, or another model alias on the same native Claude provider during this task.",i="hermes"===e?preferredHermesClaudeReviewModel(t):null;return i?`\n\n${o} For a visible manual review fallback, spawn exactly one worker with runtime="hermes", model=${JSON.stringify(i)}, mode="default", and sliceType="review". This Hermes lane is permission-gated, not structurally read-only: instruct it not to edit, deny/stop any write request, and do not treat its review as a Flow safety proof. Never use Hermes delegate_task.`:`\n\n${o} Use a different available runtime/provider once, or report the blocker.`};export const ROOMNOW={laneCap:8,previewLen:70,wtCap:10};export const formatRoomNow=({selfId:e,sessions:t,terms:r,names:n={},worktrees:o=[],now:a=Date.now()}={},c=ROOMNOW)=>{const d=siblingsOf({selfId:e,sessions:t,terms:r,names:n,now:a}),p=[];if(e&&(t?.has(e)||r?.has(e))&&p.push(`Current terminal: ${e}${n[e]?` · ${n[e]}`:""}. This is your own lane.`),d.length){const e=d.slice(0,c.laneCap),t=e.map(e=>{let t;if("agent"===e.kind){const r=e.log.map(e=>digestLogEvent(e,c.previewLen)).filter(Boolean);t=r[r.length-1]||"(no activity yet)"}else t=cleanPtyTail(e.buf,1)[0]||"(no output yet)";return`- ${l(e)} · ${e.kind} · ${i(e)} — ${s(t,c.previewLen)}`});d.length>e.length&&t.push(`- … +${d.length-e.length} more (read_terminal lists all)`),p.push(`Other lanes in this room right now (this is the roster; use targeted read_terminal only for needed detail):\n${t.join("\n")}`)}const u=Array.isArray(o)?o:[],m=u.slice(0,c.wtCap);if(m.length){const e=m.map(e=>`- ${s(e,c.previewLen+30)}`);u.length>m.length&&e.push(`- … +${u.length-m.length} more (\`git worktree list\` shows all)`),p.push(`Active git worktrees on this host (code edits go in your OWN worktree — never one another lane is on):\n${e.join("\n")}`)}return p.length?`ROOM NOW (live snapshot, this turn):\n${p.join("\n")}`:null};export const CROSSPOST={maxHop:1,perTurnCap:3};export const crossPostDecision=({hop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSPOST)=>{const o={...CROSSPOST,...n||{}};return r?{ok:!1,reason:"Cross-terminal posting is disabled in this room."}:e>=o.maxHop?{ok:!1,reason:"Cross-terminal hop limit reached — an agent reached via cross-post cannot itself post onward; a person must direct the next cross-terminal action."}:t>=o.perTurnCap?{ok:!1,reason:`Cross-terminal post limit reached for this turn (${o.perTurnCap}).`}:{ok:!0}};export const crossPostNeedsCard=({mode:e="default",spawnedByMe:t=!1}={})=>!("bypassPermissions"===e||!0===t);export const SPAWN={maxHop:1,perWindowCap:6,windowMs:6e5,freeMaxSpawned:3,plusMaxSpawned:6,machineMax:12};export const maxSpawnedFor=(e,t=SPAWN)=>{const r={...SPAWN,...t||{}};return"plus"===e?r.plusMaxSpawned:r.freeMaxSpawned};export const isActiveDispatchedWorker=e=>!(!e?.spawnedBy||"conductor"===e.flowRole||e.flowTaskKey&&e.flowDone||!0===e.workerSettled);export const countActiveDispatchedWorkers=(e=[])=>[...e].filter(isActiveDispatchedWorker).length;export const isSettledDispatchedWorker=e=>!!e?.spawnedBy&&!String(e.spawnedBy).startsWith("flow:")&&!0===e.workerSettled;export const countMachineRuntimeLanes=(e=[])=>[...e].filter(e=>!isSettledDispatchedWorker(e)).length;export const machineCapacityDecision=({totalLive:e=0,disabled:t=!1}={},r=SPAWN)=>{const n={...SPAWN,...r||{}};return t?{ok:!1,reason:"Spawning terminals is disabled in this room."}:e>=n.machineMax?{ok:!1,reason:`Room is at the machine cap (${n.machineMax} live lanes) — close one before opening another.`}:{ok:!0}};export const activeWorkerCapacityDecision=({spawnedLive:e=0,totalLive:t=0,plan:r="free",disabled:n=!1}={},o=SPAWN)=>{const i={...SPAWN,...o||{}},s=machineCapacityDecision({totalLive:t,disabled:n},i);if(!s.ok)return s;const a=maxSpawnedFor(r,i);return e>=a?{ok:!1,reason:`Dispatch ceiling reached (${a} lane${1===a?"":"s"} on the ${r} plan)${"plus"!==r?` — Plus raises it to ${i.plusMaxSpawned}.`:"."} Close a dispatched lane to open another.`}:{ok:!0}};export const recentSpawnCount=(e,t,r=SPAWN.windowMs)=>(Array.isArray(e)?e:[]).filter(e=>Number.isFinite(e)&&e>t-r).length;export const spawnDecision=({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN)=>{const l={...SPAWN,...a||{}};if(s)return{ok:!1,reason:"Spawning terminals is disabled in this room."};if(e>=l.maxHop)return{ok:!1,reason:"Spawn limit reached — a terminal reached via cross-terminal cannot itself open further terminals; a person must open the next lane."};const c=activeWorkerCapacityDecision({spawnedLive:n,totalLive:o,plan:i},l);if(!c.ok)return c;if(recentSpawnCount(t,r,l.windowMs)>=l.perWindowCap){const e=Math.round(l.windowMs/6e4);return{ok:!1,reason:`Spawn burst limit reached (${l.perWindowCap} in ${e} min) — this replenishes as the window slides forward, or instantly when a person types into this lane.`}}return{ok:!0}};const d=Object.freeze(["cascade","mode","model","name","provider","runtime","sliceType","task"]),p=new Set(["default","acceptEdits","bypassPermissions","plan","review"]),u=new Set(["claude","codex","hermes"]),m=new Set(["scaffold","feature","fix","review"]),h=/((?<![a-z0-9])sk-[a-z0-9_-]{8,}|(?<![a-z0-9])gsk_[a-z0-9_-]{8,}|(?<![a-z0-9])AIza[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,})/gi,f=/(?:\/home\/|\/users\/|\/private\/|\/tmp\/|[a-z]:\\|\\\\)/gi,g=e=>Array.isArray(e)?e.map(g):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,g(e[t])])):e,y=(e,t)=>null==e?null:String(e).slice(0,t),w=(e,t)=>y(e,t)?.replace(h,"[credential redacted]").replace(f,"[host path redacted]")||"";export function normalizeDispatchArgs(e={},t={}){if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!d.includes(e)))throw new TypeError("Invalid dispatch arguments");const r=e.runtime||t.runtime||"claude",o=e.mode||("plan"===t.mode?"default":t.mode)||"default";if(!u.has(r)||!p.has(o)||e.sliceType&&!m.has(e.sliceType))throw new TypeError("Invalid dispatch arguments");const i=null==e.cascade?null:n(e.cascade);if(null!=e.cascade&&!i)throw new TypeError("Invalid Cascade metadata");return Object.freeze({cascade:i?Object.freeze(i):null,name:y(e.name,80),task:y(e.task,12e3),model:y(e.model,160),sliceType:y(e.sliceType,24),runtime:r,provider:y(e.provider,160),mode:o})}export function dispatchCapSnapshot({hop:e=0,spawnTimes:t=[],now:r=0,spawnedLive:n=0,totalLive:o=0,plan:i="free",disabled:s=!1}={},a=SPAWN){const l={...SPAWN,...a||{}},c="plus"===i?"plus":"free",d=maxSpawnedFor(c,l);return Object.freeze({killSwitch:!0===s,hop:Object.freeze({used:Math.max(0,Number(e)||0),limit:l.maxHop}),width:Object.freeze({requested:1,live:Math.max(0,Number(n)||0),limit:d}),machine:Object.freeze({live:Math.max(0,Number(o)||0),limit:l.machineMax}),plan:Object.freeze({name:c,price:"plus"===c?"€5/mo":"€0",used:Math.max(0,Number(n)||0),limit:d}),burst:Object.freeze({used:recentSpawnCount(t,r,l.windowMs),limit:l.perWindowCap,windowMinutes:Math.round(l.windowMs/6e4)})})}export function buildDispatchPreview({args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){if(!(r&&n&&o&&i))throw new TypeError("Dispatch authority is incomplete");const l=normalizeDispatchArgs(t,{runtime:s.runtime,mode:s.mode}),c=dispatchCapSnapshot(s,a),d=(p={authority:Object.freeze({initiatorTerminalId:String(r),roomCode:String(n).toUpperCase(),bridgeAuthorityId:String(o),localAuthorityId:String(i)}),exactArgs:l,caps:c},e("sha256").update(JSON.stringify(g(p))).digest("hex"));var p;const u=l.provider||("codex"===l.runtime?"OpenAI / Codex login":"hermes"===l.runtime?"Hermes ACP profile":"Anthropic / host default"),m=l.model||s.resolvedModel||"runtime default",h=w(l.name||l.task?.split(/\r?\n/)[0]||"Open one worker lane",120);return Object.freeze({version:1,fingerprint:d,purpose:h,packages:Object.freeze([{title:w(l.name||"Worker package",80),task:w(l.task||"Open idle for a later hand-off.",600)}]),runtime:Object.freeze({name:l.runtime,provider:w(s.providerName||u,120),model:w(m,160),band:"Start: now–1 min · completion: task-dependent",execution:"Runs locally on the bridge machine; no cloud worker is created.",accountAndSpend:`Uses the host’s ${w(s.providerName||u,100)} account/BYOK. Provider usage is the spend source; thinkpool has no credit meter.`}),permissions:Object.freeze({mode:l.mode,consequence:"bypassPermissions"===l.mode?"Unattended local access within this worker’s sandbox and tool policy.":"review"===l.mode?"Read-only inspection of the pinned parent snapshot; writes and approval escapes are unavailable.":"plan"===l.mode?"Planning only; execution remains gated.":"Protected local actions keep their normal permission gates."}),worktree:"Automatically creates one visible lateral worker lane and an isolated git worktree on this machine.",caps:c})}export function authorizeDispatchApproval({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},decision:l="allow",consumed:c=!1,permissionId:d=null,consumedPermissionId:p=null,limits:u=SPAWN}={}){return"allow"!==l?{ok:!1,code:"dispatch_canceled"}:c?{ok:!1,code:d&&d===p?"idempotent_replay":"approval_replayed"}:b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:u})}export function authorizeDirectDispatch({preview:e,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s={},limits:a=SPAWN}={}){return b({preview:e,approvedFingerprint:e?.fingerprint,args:t,initiatorTerminalId:r,roomCode:n,bridgeAuthorityId:o,localAuthorityId:i,context:s,limits:a})}function b({preview:e,approvedFingerprint:t,args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a={},limits:l=SPAWN}={}){let c;try{c=buildDispatchPreview({args:r,initiatorTerminalId:n,roomCode:o,bridgeAuthorityId:i,localAuthorityId:s,context:a,limits:l})}catch{return{ok:!1,code:"invalid_dispatch"}}if(!e||e.fingerprint!==t)return{ok:!1,code:"approval_mismatch"};if(c.fingerprint!==e.fingerprint)return{ok:!1,code:"stale_dispatch"};const d=spawnDecision(a,l);return d.ok?{ok:!0,code:"approved",preview:c}:{ok:!1,code:"cap_denied",reason:d.reason}}export function verifyDurableDispatchAuthority({item:e,events:t=[],expected:r={}}={}){const n=e=>({ok:!1,code:e}),o=String(r.fingerprint||"");if(!e||e.id!==r.controlItemId)return n("control_item_mismatch");if(e.session_code!==String(r.roomCode||"").toUpperCase())return n("wrong_room");if("dispatch"!==e.action_kind||"approval"!==e.item_kind)return n("wrong_action");if("resolved"!==e.status||"approved"!==e.resolution_code||!e.resolved_by)return n("not_approved");if("either_member"!==e.resolution_policy||null!=e.required_actor_id)return n("wrong_resolution_policy");if(e.bridge_authority_id!==r.bridgeAuthorityId)return n("wrong_bridge_authority");if(e.local_authority_id!==r.localAuthorityId)return n("wrong_local_authority");if(!/^[0-9a-f]{64}$/.test(o))return n("invalid_fingerprint");if(!String(e.request_context?.action||"").includes(`fp:${o}`))return n("request_fingerprint_mismatch");if(e.resolution_payload?.dispatch_fingerprint!==o)return n("resolution_fingerprint_mismatch");const i=(Array.isArray(t)?t:[]).filter(t=>t?.item_id===e.id);if(1!==i.filter(t=>"resolved"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.resolution_payload?.dispatch_fingerprint===o&&"approved"===t.payload?.resolution_code).length)return n("resolution_event_mismatch");const s=i.filter(t=>"delivery_attempted"===t.event_type&&t.actor_id===e.resolved_by&&t.payload?.data?.dispatch_fingerprint===o);return 1!==s.length?n("delivery_attempt_mismatch"):i.some(e=>"delivered"===e.event_type||"delivery_failed"===e.event_type)?n("delivery_already_final"):{ok:!0,code:"durable_dispatch_authorized",fingerprint:o,humanActorId:e.resolved_by,attemptKey:s[0].correlation_key}}export const CLOSABLE_HINT_MAX=6;export const closeLaneDecision=({callerId:e,spawnedBy:t,busy:r}={})=>e&&t===e?r?{ok:!1,reason:"busy"}:{ok:!0}:{ok:!1,reason:"not-owner"};export const closableLanes=(e,t=[])=>(Array.isArray(t)?t:[]).filter(t=>t&&closeLaneDecision({callerId:e,spawnedBy:t.spawnedBy,busy:t.busy}).ok);export const formatClosableHint=(e,t=[],r=6)=>{const n=closableLanes(e,t);if(!n.length)return"";const o=n.slice(0,r),i=o.map(e=>`${e.ref}${e.name?` ("${e.name}")`:""}`).join(", "),s=n.length>o.length?` (+${n.length-o.length} more)`:"";return`\n\nYou have ${n.length} finished lane${1===n.length?"":"s"} preserved for review. A person can free a slot by closing one with the X in its Ensemble row: ${i}${s}`};export const CROSSROOM={peekPerTurnCap:5};export const PEER_READ_MCP_TOOLS=Object.freeze(["read_terminal","list_sessions","read_session"]);export const PEER_POST_MCP_TOOLS=Object.freeze(["post_to_terminal","post_to_session"]);export const PEER_MCP_TOOLS=Object.freeze([...PEER_READ_MCP_TOOLS,...PEER_POST_MCP_TOOLS]);export const formatPairRoster=(e,{thisRoom:t,error:r}={})=>{if(r)return String(r);const n=new Set,o=(Array.isArray(e)?e:[]).filter(e=>!(!e||!e.code||e.code===t||n.has(e.code)||(n.add(e.code),0)));return o.length?`Your other thinkpool Code sessions (call read_session with a room code to look inside one):\n${o.map(e=>`- ${e.code}${e.name?` · "${e.name}"`:""}${e.host?` · on ${e.host}`:""}${!1===e.online?" · partner bridge offline":""}`).join("\n")}`:"No other sessions of yours are open right now. (Cross-session reach is limited to rooms this account is running here, plus your partner's rooms in the same pair.)"};export const CROSSROOM_BUS={busTimeoutMs:4e3};export const pairKeyFor=(e,t)=>{const r=String(null==e?"":e).toLowerCase().trim(),n=String(null==t?"":t).toLowerCase().trim();return r&&n&&r!==n?r<n?`${r}.${n}`:`${n}.${r}`:null};export const pairTopic=e=>e?`tppair:${e}`:null;export const CROSSROOM_POST={maxRoomHop:1,crossRoomPostCap:1};export const crossRoomPostDecision=({roomHop:e=0,postCount:t=0,disabled:r=!1}={},n=CROSSROOM_POST)=>{const o={...CROSSROOM_POST,...n||{}};return r?{ok:!1,reason:"Cross-room posting is turned off on this bridge."}:e>=o.maxRoomHop?{ok:!1,reason:"Cross-room hop limit reached — a room reached via a cross-room post cannot itself post into another room; a person must direct the next cross-room action."}:t>=o.crossRoomPostCap?{ok:!1,reason:`Cross-room post limit reached for this turn (${o.crossRoomPostCap}).`}:{ok:!0}};
|
package/design-edit.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:crypto";import t from"node:fs";import r from"node:os";import n from"node:path";import{DESIGN_SOURCE_MAP_RELATIVE as o,DESIGN_SOURCE_MAP_VERSION as i,MAX_DESIGN_SOURCE_ENTRIES as s,MAX_DESIGN_SOURCE_MAP_BYTES as a}from"./design-source-contract.mjs";const c=2097152,u=4e3,l=new Set(["dist","build",".next",".git","coverage","node_modules"]),d=(e,t)=>String(e??"").replace(/\0/g,"").trim().slice(0,t),p=(e,t)=>{const r=n.relative(e,t);return""===r||!r.startsWith("..")&&!n.isAbsolute(r)};export async function syncRealtimeAuth(e,t,r=null){const n=t||r;if(!n||"function"!=typeof e?.realtime?.setAuth)return!1;try{return await e.realtime.setAuth(n),!0}catch{return!1}}export function sourceRevision(t){return e.createHash("sha256").update(String(t)).digest("hex").slice(0,24)}export function resolveDesignSourceMap(e,r,u){if(!e||!r||!u)return null;let h,m,f,g,v,y;try{h=t.realpathSync(e),f=t.realpathSync(r),m=t.realpathSync(n.join(f,o)),g=t.realpathSync(u);const i=t.statSync(h);if(!i.isFile()||i.size<=0||i.size>a)return null;if(h!==m||!p(g,f)||!p(f,h))return null;v=t.readFileSync(h,"utf8"),y=JSON.parse(v)}catch{return null}if(y?.version!==i||!y.entries||"object"!=typeof y.entries||Array.isArray(y.entries))return null;const b=Object.entries(y.entries);if(b.length>s)return null;const w=new Map,x=new Map;for(const[e,r]of b){if(!/^[A-Za-z0-9_-]{8,80}$/.test(e)||!r||"object"!=typeof r)return null;const o=d(r.file,700);if(!o||n.isAbsolute(o)||o.split(/[\\/]/).some(e=>".."===e||l.has(e)))return null;let i=w.get(o);if(!i){try{i=t.realpathSync(n.resolve(g,o));const e=t.statSync(i);if(!e.isFile()||e.size>c||!p(g,i))return null}catch{return null}w.set(o,i)}const s=Number(r.start),a=Number(r.end),u=Number(r.line),h=Number(r.column),m=d(r.tag,40).toLowerCase(),f=d(r.fileHash,64).toLowerCase();if(![s,a,u,h].every(Number.isSafeInteger)||s<0||a<=s||u<1||h<0)return null;if(!/^[a-z][a-z0-9:-]*$/.test(m)||!/^[a-f0-9]{24}$/.test(f))return null;x.set(e,{sourceId:e,file:o.split(n.sep).join("/"),sourcePath:i,start:s,end:a,line:u,column:h,tag:m,fileHash:f})}return{path:h,revision:sourceRevision(v),buildId:d(y.buildId,80),entries:x}}export function resolveDesignSource(r,o){if(!r||!o)return null;let i,s;try{i=t.realpathSync(r),s=t.realpathSync(o);const e=t.statSync(i);if(!e.isFile()||e.size>c)return null}catch{return null}const a=n.relative(s,i);if(!a||a.startsWith("..")||n.isAbsolute(a)||".html"!==n.extname(i).toLowerCase())return null;if(a.split(n.sep).some(e=>["dist","build",".next",".git","coverage","node_modules"].includes(e)))return null;const u=t.readFileSync(i,"utf8"),l=sourceRevision(u);return{previewId:e.createHash("sha256").update(`${i}\0${l}`).digest("base64url").slice(0,32),revision:l,sourcePath:i,workspaceRoot:s,source:u}}export function resolvePreviewDesignSource(r,o,i){if(!(r&&"preview"===r.sourceKind&&r.snapshot&&o&&i))return null;let s,a,u;try{s=t.realpathSync(r.snapshot),a=t.realpathSync(o),u=t.realpathSync(i);const e=t.statSync(s);if(!e.isFile()||e.size>c)return null}catch{return null}const l=n.relative(u,s);if(!l||l.startsWith("..")||n.isAbsolute(l)||".html"!==n.extname(s).toLowerCase())return null;const h=d(r.route||"/",500),m=d(r.previewRoot||"dist",500),f=d(r.captureKey,1e3);if(!h.startsWith("/")||!f||f!==JSON.stringify([m,h]))return null;const g=t.readFileSync(s,"utf8");let v=null;try{if(v=t.realpathSync(n.resolve(a,m)),!p(a,v))return null}catch{return null}const y=r.sourceMap?resolveDesignSourceMap(r.sourceMap,v,a):null,b=sourceRevision(y?`${g}\0${y.revision}`:g);return{sourceKind:"preview",previewId:e.createHash("sha256").update(`${a}\0${f}\0${b}`).digest("base64url").slice(0,32),revision:b,sourcePath:s,workspaceRoot:a,source:g,outbox:u,route:h,previewRoot:m,captureKey:f,sourceMap:y,sourceMapPath:y?.path||null,sourceMapDeclared:!!r.sourceMap}}export function resolveManifestDesignSource(e,t,{box:r}={}){return e&&"object"==typeof e?"preview"===e.sourceKind?resolvePreviewDesignSource(e,t,r):resolveDesignSource(e.source||e.html,t):null}export function resolveManifestDisplaySource(e,r,{box:o}={}){if(!r)return null;if("preview"===r.sourceKind||!e?.snapshot)return r.source;let i,s;try{i=t.realpathSync(e.snapshot),s=t.realpathSync(o);const r=t.statSync(i);if(!r.isFile()||r.size>c)return null}catch{return null}const a=n.relative(s,i);return!a||a.startsWith("..")||n.isAbsolute(a)||".html"!==n.extname(i).toLowerCase()?null:t.readFileSync(i,"utf8")}export function refreshDesignSource(e){return e?"preview"!==e.sourceKind?resolveDesignSource(e.sourcePath,e.workspaceRoot):resolvePreviewDesignSource({sourceKind:"preview",snapshot:e.sourcePath,route:e.route,previewRoot:e.previewRoot,captureKey:e.captureKey,sourceMap:e.sourceMapPath},e.workspaceRoot,e.outbox):null}export function restoreDesignSources(e,r,o,i=100){if(!e||!r||!o)return[];let s;try{s=t.readdirSync(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(".json")&&!e.name.endsWith(".receipt.json")).map(r=>{const o=n.join(e,r.name);return{file:o,mtimeMs:t.statSync(o).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs).slice(0,Math.max(0,i))}catch{return[]}const a=[];for(const{file:i}of s)try{const s=JSON.parse(t.readFileSync(i,"utf8")),c=resolveManifestDesignSource(s,r,{box:e});if(!c)continue;Object.assign(c,{term:o,slug:String(s.slug||""),title:String(s.title||s.slug||""),desktop:s.desktop||null,mobile:s.mobile||null});const u=n.join(e,".design-revisions",`${c.revision}.html`);t.existsSync(u)&&(c.backupPath=u),a.push(c)}catch{}return a}export function resolveRequestSourceIdentity(e,r){if(!r?.sourceId)return{ok:!0,identity:null};const n=e?.sourceMap?.entries?.get(r.sourceId);if(!n)return{ok:!1,error:e?.sourceMapDeclared?"stale-source-identity":"source-identity-unavailable"};if(r.tag&&n.tag!==r.tag)return{ok:!1,error:"source-identity-mismatch"};let o;try{o=t.readFileSync(n.sourcePath,"utf8")}catch{return{ok:!1,error:"stale-source-identity"}}return sourceRevision(o)!==n.fileHash?{ok:!1,error:"stale-source-identity"}:{ok:!0,identity:{sourceId:n.sourceId,file:n.file,start:n.start,end:n.end,line:n.line,column:n.column,tag:n.tag,fileHash:n.fileHash}}}export function validateDesignRequest(e,t,r=""){if(!e||"object"!=typeof e)return{ok:!1,error:"invalid-request"};const n=d(e.previewId,64),o=t.get(n);if(!o)return{ok:!1,error:"preview-unavailable"};if(d(e.revision,64)!==o.revision)return{ok:!1,error:"stale-preview"};const i=function(e){if(!e||"object"!=typeof e)return null;const t=e.rect&&"object"==typeof e.rect?{x:Number(e.rect.x)||0,y:Number(e.rect.y)||0,width:Math.max(0,Number(e.rect.width)||0),height:Math.max(0,Number(e.rect.height)||0)}:null;return{selector:d(e.selector,600),xpath:d(e.xpath,600),tag:d(e.tag,40).toLowerCase(),role:d(e.role,80),name:d(e.name,240),text:d(e.text,u),canEditText:!0===e.canEditText,canReplaceImage:!0===e.canReplaceImage,nodeKey:d(e.nodeKey,80),parentNodeKey:d(e.parentNodeKey,80),sourceId:d(e.sourceId,80),parentSourceId:d(e.parentSourceId,80),rect:t}}(e.target);if(!i?.selector&&!i?.xpath)return{ok:!1,error:"select-an-element"};const s=["text","prompt","move","image"].includes(e.mode)?e.mode:null;if(!s)return{ok:!1,error:"invalid-mode"};const a=d(e.intent,"text"===s?u:2e3);if(["text","prompt"].includes(s)&&!a)return{ok:!1,error:"text"===s?"enter-text":"describe-change"};if("text"===s&&!i.canEditText)return{ok:!1,error:"text-edit-not-safe"};const c="move"===s?function(e){if(!e||"object"!=typeof e)return null;const t=Number(e.fromIndex),r=Number(e.toIndex);return![t,r].every(Number.isSafeInteger)||t<0||r<0||t>499||r>499?null:{fromIndex:t,toIndex:r}}(e.movement):null;if("move"===s&&(!c||c.fromIndex===c.toIndex))return{ok:!1,error:"invalid-move"};const l="image"===s?function(e,t){if(!e||"object"!=typeof e)return null;const r={path:d(e.path,700),name:d(e.name,240),mediaType:d(e.mediaType,120).toLowerCase(),size:Number(e.size)},n=t?`code/${String(t).toUpperCase()}/design-drafts/`:"code/";return r.path.startsWith(n)&&!r.path.split("/").includes("..")&&r.mediaType.startsWith("image/")?!Number.isSafeInteger(r.size)||r.size<=0||r.size>10485760?null:r:null}(e.asset,r):null;if(!("image"!==s||i.canReplaceImage&&l))return{ok:!1,error:"invalid-image"};const p=resolveRequestSourceIdentity(o,i);return p.ok?{ok:!0,record:o,request:{previewId:n,revision:o.revision,mode:s,intent:a,target:i,movement:c,asset:l,sourceIdentity:p.identity,cid:d(e.cid,80)}}:p}export function validateDesignBatchRequest(e,t,r=""){if(!e||"object"!=typeof e||!Array.isArray(e.edits))return validateDesignRequest(e,t,r);if(e.edits.length<1||e.edits.length>8)return{ok:!1,error:"invalid-batch"};const n=d(e.previewId,64),o=d(e.revision,64),i=d(e.cid,80),s=[],a=new Set;let c=null;for(let u=0;u<e.edits.length;u++){const l=validateDesignRequest({...e.edits[u],previewId:n,revision:o,cid:i},t,r);if(!l.ok)return{...l,editIndex:u};const d=l.request.target.sourceId||l.request.target.nodeKey||l.request.target.selector||l.request.target.xpath;if(a.has(d))return{ok:!1,error:"duplicate-target",editIndex:u};a.add(d),c=l.record,s.push(l.request)}return{ok:!0,record:c,request:{previewId:n,revision:c.revision,mode:"batch",edits:s,cid:i}}}export async function deleteDesignAssetDraft({webBase:e,token:t,room:r,path:n,fetcher:o=fetch}){if(!(e&&t&&r&&n))return!1;const i=await o(`${String(e).replace(/\/$/,"")}/api/code-design-draft`,{method:"DELETE",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify({code:r,path:n})}).catch(()=>null);return!!i?.ok}export async function materializeDesignAsset({webBase:e,token:o,room:i,asset:s,requestId:a,cleanup:c=!0,fetcher:u=fetch}){if(!(e&&o&&i&&s?.path&&a))return null;const l={"Content-Type":"application/json",Authorization:`Bearer ${o}`},p=await u(`${String(e).replace(/\/$/,"")}/api/code-attachment-url`,{method:"POST",headers:l,body:JSON.stringify({code:i,path:s.path})});if(!p.ok)return null;const{url:h}=await p.json();if(!h)return null;const m=await u(h);if(!m.ok)return null;const f=Buffer.from(await m.arrayBuffer());if(!f.length||f.length>10485760)return null;const g=n.extname(s.name).replace(/[^.a-zA-Z0-9]/g,"").slice(0,9)||".img",v=n.join(r.tmpdir(),"thinkpool-design-assets"),y=n.join(v,`${d(a,80)}${g}`);return t.mkdirSync(v,{recursive:!0}),t.writeFileSync(y,f),c&&await deleteDesignAssetDraft({webBase:e,token:o,room:i,path:s.path,fetcher:u}),y}function h(e){return"text"===e.mode?`Replace only this element's visible text with:\n${e.intent}`:"move"===e.mode?`Move only this selected element within its current direct parent, from sibling position ${e.movement.fromIndex+1} to ${e.movement.toIndex+1}. Preserve the parent, responsive behavior, semantics, and every other sibling.`:"image"===e.mode?`Replace only this selected image's content with the uploaded image at this bridge-local temporary path:\n${e.asset.localPath}\nCopy it into an appropriate durable project asset location. Preserve the existing image frame, dimensions, border radius, object-fit behavior, responsive rules, and surrounding layout.`:`Apply this visual change to only the selected element unless the instruction explicitly requires its immediate context:\n${e.intent}`}function m(e){const t=e.sourceIdentity?`\n\nBridge-verified application source:\n${JSON.stringify(e.sourceIdentity,null,2)}`:"\n\nApplication source identity: unavailable — selector/XPath fallback only.";return`Selected element:\n${JSON.stringify(e.target,null,2)}${t}`}export function designTranscript(e,{restore:t=!1}={}){if(t)return"thinkpool Design · restore the previous verified revision";const r="batch"===e?.mode?e.edits:[e],n=Array.isArray(r)?r.filter(Boolean).slice(0,8):[],o="batch"===e?.mode?`thinkpool Design · ${n.length} pooled edit${1===n.length?"":"s"}`:"thinkpool Design · 1 edit",i=n.map((e,t)=>{const r=d(e.target?.name||e.target?.tag||"element",240);return`${t+1}. ${n=e.mode,{text:"Text",prompt:"Prompt",move:"Move",image:"Image"}[n]||"Edit"} · ${r}\n${function(e){return"text"===e.mode?`Replacement text:\n${d(e.intent,u)}`:"prompt"===e.mode?`Instruction:\n${d(e.intent,2e3)}`:"move"===e.mode?`Move from position ${e.movement.fromIndex+1} to position ${e.movement.toIndex+1}.`:"image"===e.mode?`Replacement image: ${d(e.asset?.name||"uploaded image",240)}`:""}(e)}`;var n});return[o,...i].join("\n\n")}export function designPrompt({record:e,request:t,by:r,restore:n=!1,priorRecord:o=null}){const i="batch"===t.mode?t.edits:null,s=n?`Restore only the immediately previous Design Mode change by comparing the current artifact with this bridge-created verified backup: ${o?.backupPath||"(backup unavailable)"}. Do not overwrite unrelated changes.`:i?i.map((e,t)=>`Edit ${t+1} of ${i.length} · ${e.mode}\n${m(e)}\n\n${h(e)}`).join("\n\n---\n\n"):`${m(t)}\n\n${h(t)}`;return"preview"===e.sourceKind?`A room member used thinkpool Design Mode on a rendered application preview. This is an authorized source edit in your current workspace.\n\nRendered snapshot (read-only evidence): ${e.sourcePath}\nApplication workspace: ${e.workspaceRoot}\nPreview root: ${e.previewRoot}\nPreview route: ${e.route}\nExpected snapshot revision: ${e.revision}\nRequested by: ${d(r||"A room member",120)}\nOperation: ${i?`${i.length} queued edits in one batch`:t.mode}\n\n${s}\n\nRules:\n- Re-read the rendered snapshot and confirm the selected element still matches before editing.\n- Do not edit the snapshot. For an edit with bridge-verified application source, start at that exact relative file and range. For a selector/XPath fallback, locate the represented element in application source and do not call the match exact.\n- Before editing a bridge-verified target, confirm the current source still hashes to the supplied fileHash. If it does not, stop and request a fresh capture/reselection.\n- If fallback selector, text, or surrounding structure does not identify one application source location unambiguously, stop without editing and explain that the person must reselect it.\n${i?"- Apply the queued edits in order in one source pass, then build and capture once after the full batch.":"- Apply only the requested change, then build and capture once."}\n- Preserve the product page faithfully: keep its real copy, structure, fonts, assets, spacing, responsive behavior, and unrelated source unchanged.\n- Run the project build, then use preview_start with root ${e.previewRoot} and preview_capture with path ${e.route}, both viewports, and title ${JSON.stringify(e.title||e.slug||"Design preview")}. The bridge correlates that capture to this request.\n- Do not claim the edit is verified until the room receives the new desktop and mobile capture.\n- If the build or capture fails, report the failure instead of substituting a screenshot-only result.`:`A room member used thinkpool Design Mode on the artifact below. This is an authorized source edit in your current workspace.\n\nArtifact source: ${e.sourcePath}\nWorkspace: ${e.workspaceRoot}\nExpected source revision: ${e.revision}\nRequested by: ${d(r||"A room member",120)}\nOperation: ${n?"restore the immediately previous verified Design Mode revision":i?`${i.length} queued edits in one batch`:t.mode}\n\n${s}\n\nRules:\n- Re-read the exact artifact source and confirm its revision/content still matches every selected element before editing.\n${i?"- Apply the queued edits in order in one source pass, then render once after the full batch.":"- Apply only the requested change, then render the result once."}\n- Edit the canonical source above; do not search for or edit a different plausible file.\n- Preserve the represented product page faithfully: keep its real copy, structure, fonts, assets, spacing, and responsive behavior except for the selected edits. Never replace it with generic mockup content.\n- Preserve unrelated content and styling.\n- Use the existing mockup render workflow to produce both desktop and mobile captures after the edit. Run it with TP_DESIGN_REQUEST_ID=${t.cid} and TP_DESIGN_PARENT_REVISION=${e.revision} in the command environment so the proof correlates to this request.\n- Do not claim this is live until both captures succeed and the room receives the manifest.\n- If any target is stale or ambiguous, stop without editing and explain that the person must reselect it.`}
|
|
1
|
+
import e from"node:crypto";import t from"node:fs";import r from"node:os";import n from"node:path";import{DESIGN_SOURCE_MAP_RELATIVE as i,DESIGN_SOURCE_MAP_VERSION as o,MAX_DESIGN_SOURCE_ENTRIES as s,MAX_DESIGN_SOURCE_MAP_BYTES as a}from"./design-source-contract.mjs";const c=2097152,u=4e3,l=new Set(["dist","build",".next",".git","coverage","node_modules"]),d=(e,t)=>String(e??"").replace(/\0/g,"").trim().slice(0,t),p=(e,t)=>{const r=n.relative(e,t);return""===r||!r.startsWith("..")&&!n.isAbsolute(r)};export function durableDesignOutbox(e,t=process.env.TP_PAIR_ROOT||n.join(r.homedir(),".thinkpool-pair")){if(!/^[a-zA-Z0-9_-]+$/.test(e||""))throw new TypeError("invalid Design room");return n.join(t,"design",e)}export function migrateDesignOutbox(e,r){if(!t.existsSync(e))return;const i=t.realpathSync(e);t.mkdirSync(r,{recursive:!0,mode:448});const o=t.realpathSync(r);if(i===o)return;const s=t=>{if("string"!=typeof t||!n.isAbsolute(t))return t;const r=p(i,t)?i:n.resolve(e);return p(r,t)?n.join(o,n.relative(r,t)):t};let a=2e3;const u=(e,r,i=0)=>{if(!(i>5))for(const o of t.readdirSync(e,{withFileTypes:!0})){if(--a<0)return;const l=n.join(e,o.name),d=n.join(r,o.name);if(o.isSymbolicLink())continue;const p=t.existsSync(d)?t.lstatSync(d):null;if(!p||o.isDirectory()&&p.isDirectory()&&!p.isSymbolicLink())if(o.isDirectory())t.mkdirSync(d,{recursive:!0,mode:448}),u(l,d,i+1);else if(o.isFile()&&t.statSync(l).size<=c){const e=`${d}.migrate-${process.pid}.tmp`;try{if(o.name.endsWith(".json")&&!o.name.endsWith(".receipt.json")){const r=JSON.parse(t.readFileSync(l,"utf8"));for(const e of["snapshot","desktop","mobile"])r[e]=s(r[e]);t.writeFileSync(e,JSON.stringify(r),{mode:384})}else t.copyFileSync(l,e);t.renameSync(e,d)}catch{}finally{t.rmSync(e,{force:!0})}}}};u(i,o)}export async function syncRealtimeAuth(e,t,r=null){const n=t||r;if(!n||"function"!=typeof e?.realtime?.setAuth)return!1;try{return await e.realtime.setAuth(n),!0}catch{return!1}}export function sourceRevision(t){return e.createHash("sha256").update(String(t)).digest("hex").slice(0,24)}export function resolveDesignSourceMap(e,r,u){if(!e||!r||!u)return null;let h,m,f,y,g,v;try{h=t.realpathSync(e),f=t.realpathSync(r),m=t.realpathSync(n.join(f,i)),y=t.realpathSync(u);const o=t.statSync(h);if(!o.isFile()||o.size<=0||o.size>a)return null;if(h!==m||!p(y,f)||!p(f,h))return null;g=t.readFileSync(h,"utf8"),v=JSON.parse(g)}catch{return null}if(v?.version!==o||!v.entries||"object"!=typeof v.entries||Array.isArray(v.entries))return null;const b=Object.entries(v.entries);if(b.length>s)return null;const S=new Map,x=new Map;for(const[e,r]of b){if(!/^[A-Za-z0-9_-]{8,80}$/.test(e)||!r||"object"!=typeof r)return null;const i=d(r.file,700);if(!i||n.isAbsolute(i)||i.split(/[\\/]/).some(e=>".."===e||l.has(e)))return null;let o=S.get(i);if(!o){try{o=t.realpathSync(n.resolve(y,i));const e=t.statSync(o);if(!e.isFile()||e.size>c||!p(y,o))return null}catch{return null}S.set(i,o)}const s=Number(r.start),a=Number(r.end),u=Number(r.line),h=Number(r.column),m=d(r.tag,40).toLowerCase(),f=d(r.fileHash,64).toLowerCase();if(![s,a,u,h].every(Number.isSafeInteger)||s<0||a<=s||u<1||h<0)return null;if(!/^[a-z][a-z0-9:-]*$/.test(m)||!/^[a-f0-9]{24}$/.test(f))return null;x.set(e,{sourceId:e,file:i.split(n.sep).join("/"),sourcePath:o,start:s,end:a,line:u,column:h,tag:m,fileHash:f})}return{path:h,revision:sourceRevision(g),buildId:d(v.buildId,80),entries:x}}export function resolveDesignSource(r,i){if(!r||!i)return null;let o,s;try{o=t.realpathSync(r),s=t.realpathSync(i);const e=t.statSync(o);if(!e.isFile()||e.size>c)return null}catch{return null}const a=n.relative(s,o);if(!a||a.startsWith("..")||n.isAbsolute(a)||".html"!==n.extname(o).toLowerCase())return null;if(a.split(n.sep).some(e=>["dist","build",".next",".git","coverage","node_modules"].includes(e)))return null;const u=t.readFileSync(o,"utf8"),l=sourceRevision(u);return{previewId:e.createHash("sha256").update(`${o}\0${l}`).digest("base64url").slice(0,32),revision:l,sourcePath:o,workspaceRoot:s,source:u}}export function resolvePreviewDesignSource(r,i,o){if(!(r&&"preview"===r.sourceKind&&r.snapshot&&i&&o))return null;let s,a,u;try{s=t.realpathSync(r.snapshot),a=t.realpathSync(i),u=t.realpathSync(o);const e=t.statSync(s);if(!e.isFile()||e.size>c)return null}catch{return null}const l=n.relative(u,s);if(!l||l.startsWith("..")||n.isAbsolute(l)||".html"!==n.extname(s).toLowerCase())return null;const h=d(r.route||"/",500),m=d(r.previewRoot||"dist",500),f=d(r.captureKey,1e3);if(!h.startsWith("/")||!f||f!==JSON.stringify([m,h]))return null;const y=t.readFileSync(s,"utf8");let g=null;try{if(g=t.realpathSync(n.resolve(a,m)),!p(a,g))return null}catch{return null}const v=r.sourceMap?resolveDesignSourceMap(r.sourceMap,g,a):null,b=sourceRevision(v?`${y}\0${v.revision}`:y);return{sourceKind:"preview",previewId:e.createHash("sha256").update(`${a}\0${f}\0${b}`).digest("base64url").slice(0,32),revision:b,sourcePath:s,workspaceRoot:a,source:y,outbox:u,route:h,previewRoot:m,captureKey:f,sourceMap:v,sourceMapPath:v?.path||null,sourceMapDeclared:!!r.sourceMap}}export function resolveManifestDesignSource(e,t,{box:r}={}){return e&&"object"==typeof e?"preview"===e.sourceKind?resolvePreviewDesignSource(e,t,r):resolveDesignSource(e.source||e.html,t):null}export function resolveManifestDisplaySource(e,r,{box:i}={}){if(!r)return null;if("preview"===r.sourceKind||!e?.snapshot)return r.source;let o,s;try{o=t.realpathSync(e.snapshot),s=t.realpathSync(i);const r=t.statSync(o);if(!r.isFile()||r.size>c)return null}catch{return null}const a=n.relative(s,o);return!a||a.startsWith("..")||n.isAbsolute(a)||".html"!==n.extname(o).toLowerCase()?null:t.readFileSync(o,"utf8")}export function refreshDesignSource(e){return e?"preview"!==e.sourceKind?resolveDesignSource(e.sourcePath,e.workspaceRoot):resolvePreviewDesignSource({sourceKind:"preview",snapshot:e.sourcePath,route:e.route,previewRoot:e.previewRoot,captureKey:e.captureKey,sourceMap:e.sourceMapPath},e.workspaceRoot,e.outbox):null}export function restoreDesignSources(e,r,i,o=100){if(!e||!r||!i)return[];let s;try{s=t.readdirSync(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(".json")&&!e.name.endsWith(".receipt.json")).map(r=>{const i=n.join(e,r.name);return{file:i,mtimeMs:t.statSync(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs).slice(0,Math.max(0,o))}catch{return[]}const a=[];for(const{file:o}of s)try{const s=JSON.parse(t.readFileSync(o,"utf8")),c=resolveManifestDesignSource(s,r,{box:e});if(!c)continue;Object.assign(c,{term:i,slug:String(s.slug||""),title:String(s.title||s.slug||""),desktop:s.desktop||null,mobile:s.mobile||null});const u=n.join(e,".design-revisions",`${c.revision}.html`);t.existsSync(u)&&(c.backupPath=u),a.push(c)}catch{}return a}export function resolveRequestSourceIdentity(e,r){if(!r?.sourceId)return{ok:!0,identity:null};const n=e?.sourceMap?.entries?.get(r.sourceId);if(!n)return{ok:!1,error:e?.sourceMapDeclared?"stale-source-identity":"source-identity-unavailable"};if(r.tag&&n.tag!==r.tag)return{ok:!1,error:"source-identity-mismatch"};let i;try{i=t.readFileSync(n.sourcePath,"utf8")}catch{return{ok:!1,error:"stale-source-identity"}}return sourceRevision(i)!==n.fileHash?{ok:!1,error:"stale-source-identity"}:{ok:!0,identity:{sourceId:n.sourceId,file:n.file,start:n.start,end:n.end,line:n.line,column:n.column,tag:n.tag,fileHash:n.fileHash}}}export function validateDesignRequest(e,t,r=""){if(!e||"object"!=typeof e)return{ok:!1,error:"invalid-request"};const n=d(e.previewId,64),i=t.get(n);if(!i)return{ok:!1,error:"preview-unavailable"};if(d(e.revision,64)!==i.revision)return{ok:!1,error:"stale-preview"};const o=function(e){if(!e||"object"!=typeof e)return null;const t=e.rect&&"object"==typeof e.rect?{x:Number(e.rect.x)||0,y:Number(e.rect.y)||0,width:Math.max(0,Number(e.rect.width)||0),height:Math.max(0,Number(e.rect.height)||0)}:null;return{selector:d(e.selector,600),xpath:d(e.xpath,600),tag:d(e.tag,40).toLowerCase(),role:d(e.role,80),name:d(e.name,240),text:d(e.text,u),canEditText:!0===e.canEditText,canReplaceImage:!0===e.canReplaceImage,nodeKey:d(e.nodeKey,80),parentNodeKey:d(e.parentNodeKey,80),sourceId:d(e.sourceId,80),parentSourceId:d(e.parentSourceId,80),rect:t}}(e.target);if(!o?.selector&&!o?.xpath)return{ok:!1,error:"select-an-element"};const s=["text","prompt","move","image"].includes(e.mode)?e.mode:null;if(!s)return{ok:!1,error:"invalid-mode"};const a=d(e.intent,"text"===s?u:2e3);if(["text","prompt"].includes(s)&&!a)return{ok:!1,error:"text"===s?"enter-text":"describe-change"};if("text"===s&&!o.canEditText)return{ok:!1,error:"text-edit-not-safe"};const c="move"===s?function(e){if(!e||"object"!=typeof e)return null;const t=Number(e.fromIndex),r=Number(e.toIndex);return![t,r].every(Number.isSafeInteger)||t<0||r<0||t>499||r>499?null:{fromIndex:t,toIndex:r}}(e.movement):null;if("move"===s&&(!c||c.fromIndex===c.toIndex))return{ok:!1,error:"invalid-move"};const l="image"===s?function(e,t){if(!e||"object"!=typeof e)return null;const r={path:d(e.path,700),name:d(e.name,240),mediaType:d(e.mediaType,120).toLowerCase(),size:Number(e.size)},n=t?`code/${String(t).toUpperCase()}/design-drafts/`:"code/";return r.path.startsWith(n)&&!r.path.split("/").includes("..")&&r.mediaType.startsWith("image/")?!Number.isSafeInteger(r.size)||r.size<=0||r.size>10485760?null:r:null}(e.asset,r):null;if(!("image"!==s||o.canReplaceImage&&l))return{ok:!1,error:"invalid-image"};const p=resolveRequestSourceIdentity(i,o);return p.ok?{ok:!0,record:i,request:{previewId:n,revision:i.revision,mode:s,intent:a,target:o,movement:c,asset:l,sourceIdentity:p.identity,cid:d(e.cid,80)}}:p}export function validateDesignBatchRequest(e,t,r=""){if(!e||"object"!=typeof e||!Array.isArray(e.edits))return validateDesignRequest(e,t,r);if(e.edits.length<1||e.edits.length>8)return{ok:!1,error:"invalid-batch"};const n=d(e.previewId,64),i=d(e.revision,64),o=d(e.cid,80),s=[],a=new Set;let c=null;for(let u=0;u<e.edits.length;u++){const l=validateDesignRequest({...e.edits[u],previewId:n,revision:i,cid:o},t,r);if(!l.ok)return{...l,editIndex:u};const d=l.request.target.sourceId||l.request.target.nodeKey||l.request.target.selector||l.request.target.xpath;if(a.has(d))return{ok:!1,error:"duplicate-target",editIndex:u};a.add(d),c=l.record,s.push(l.request)}return{ok:!0,record:c,request:{previewId:n,revision:c.revision,mode:"batch",edits:s,cid:o}}}export async function deleteDesignAssetDraft({webBase:e,token:t,room:r,path:n,fetcher:i=fetch}){if(!(e&&t&&r&&n))return!1;const o=await i(`${String(e).replace(/\/$/,"")}/api/code-design-draft`,{method:"DELETE",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify({code:r,path:n})}).catch(()=>null);return!!o?.ok}export async function materializeDesignAsset({webBase:e,token:i,room:o,asset:s,requestId:a,cleanup:c=!0,fetcher:u=fetch}){if(!(e&&i&&o&&s?.path&&a))return null;const l={"Content-Type":"application/json",Authorization:`Bearer ${i}`},p=await u(`${String(e).replace(/\/$/,"")}/api/code-attachment-url`,{method:"POST",headers:l,body:JSON.stringify({code:o,path:s.path})});if(!p.ok)return null;const{url:h}=await p.json();if(!h)return null;const m=await u(h);if(!m.ok)return null;const f=Buffer.from(await m.arrayBuffer());if(!f.length||f.length>10485760)return null;const y=n.extname(s.name).replace(/[^.a-zA-Z0-9]/g,"").slice(0,9)||".img",g=n.join(r.tmpdir(),"thinkpool-design-assets"),v=n.join(g,`${d(a,80)}${y}`);return t.mkdirSync(g,{recursive:!0}),t.writeFileSync(v,f),c&&await deleteDesignAssetDraft({webBase:e,token:i,room:o,path:s.path,fetcher:u}),v}function h(e){return"text"===e.mode?`Replace only this element's visible text with:\n${e.intent}`:"move"===e.mode?`Move only this selected element within its current direct parent, from sibling position ${e.movement.fromIndex+1} to ${e.movement.toIndex+1}. Preserve the parent, responsive behavior, semantics, and every other sibling.`:"image"===e.mode?`Replace only this selected image's content with the uploaded image at this bridge-local temporary path:\n${e.asset.localPath}\nCopy it into an appropriate durable project asset location. Preserve the existing image frame, dimensions, border radius, object-fit behavior, responsive rules, and surrounding layout.`:`Apply this visual change to only the selected element unless the instruction explicitly requires its immediate context:\n${e.intent}`}function m(e){const t=e.sourceIdentity?`\n\nBridge-verified application source:\n${JSON.stringify(e.sourceIdentity,null,2)}`:"\n\nApplication source identity: unavailable — selector/XPath fallback only.";return`Selected element:\n${JSON.stringify(e.target,null,2)}${t}`}export function designTranscript(e,{restore:t=!1}={}){if(t)return"thinkpool Design · restore the previous verified revision";const r="batch"===e?.mode?e.edits:[e],n=Array.isArray(r)?r.filter(Boolean).slice(0,8):[],i="batch"===e?.mode?`thinkpool Design · ${n.length} pooled edit${1===n.length?"":"s"}`:"thinkpool Design · 1 edit",o=n.map((e,t)=>{const r=d(e.target?.name||e.target?.tag||"element",240);return`${t+1}. ${n=e.mode,{text:"Text",prompt:"Prompt",move:"Move",image:"Image"}[n]||"Edit"} · ${r}\n${function(e){return"text"===e.mode?`Replacement text:\n${d(e.intent,u)}`:"prompt"===e.mode?`Instruction:\n${d(e.intent,2e3)}`:"move"===e.mode?`Move from position ${e.movement.fromIndex+1} to position ${e.movement.toIndex+1}.`:"image"===e.mode?`Replacement image: ${d(e.asset?.name||"uploaded image",240)}`:""}(e)}`;var n});return[i,...o].join("\n\n")}export function designPrompt({record:e,request:t,by:r,restore:n=!1,priorRecord:i=null}){const o="batch"===t.mode?t.edits:null,s=n?`Restore only the immediately previous Design Mode change by comparing the current artifact with this bridge-created verified backup: ${i?.backupPath||"(backup unavailable)"}. Do not overwrite unrelated changes.`:o?o.map((e,t)=>`Edit ${t+1} of ${o.length} · ${e.mode}\n${m(e)}\n\n${h(e)}`).join("\n\n---\n\n"):`${m(t)}\n\n${h(t)}`;return"preview"===e.sourceKind?`A room member used thinkpool Design Mode on a rendered application preview. This is an authorized source edit in your current workspace.\n\nRendered snapshot (read-only evidence): ${e.sourcePath}\nApplication workspace: ${e.workspaceRoot}\nPreview root: ${e.previewRoot}\nPreview route: ${e.route}\nExpected snapshot revision: ${e.revision}\nRequested by: ${d(r||"A room member",120)}\nOperation: ${o?`${o.length} queued edits in one batch`:t.mode}\n\n${s}\n\nRules:\n- Re-read the rendered snapshot and confirm the selected element still matches before editing.\n- Do not edit the snapshot. For an edit with bridge-verified application source, start at that exact relative file and range. For a selector/XPath fallback, locate the represented element in application source and do not call the match exact.\n- Before editing a bridge-verified target, confirm the current source still hashes to the supplied fileHash. If it does not, stop and request a fresh capture/reselection.\n- If fallback selector, text, or surrounding structure does not identify one application source location unambiguously, stop without editing and explain that the person must reselect it.\n${o?"- Apply the queued edits in order in one source pass, then build and capture once after the full batch.":"- Apply only the requested change, then build and capture once."}\n- Preserve the product page faithfully: keep its real copy, structure, fonts, assets, spacing, responsive behavior, and unrelated source unchanged.\n- Run the project build, then use preview_start with root ${e.previewRoot} and preview_capture with path ${e.route}, both viewports, and title ${JSON.stringify(e.title||e.slug||"Design preview")}. The bridge correlates that capture to this request.\n- Do not claim the edit is verified until the room receives the new desktop and mobile capture.\n- If the build or capture fails, report the failure instead of substituting a screenshot-only result.`:`A room member used thinkpool Design Mode on the artifact below. This is an authorized source edit in your current workspace.\n\nArtifact source: ${e.sourcePath}\nWorkspace: ${e.workspaceRoot}\nExpected source revision: ${e.revision}\nRequested by: ${d(r||"A room member",120)}\nOperation: ${n?"restore the immediately previous verified Design Mode revision":o?`${o.length} queued edits in one batch`:t.mode}\n\n${s}\n\nRules:\n- Re-read the exact artifact source and confirm its revision/content still matches every selected element before editing.\n${o?"- Apply the queued edits in order in one source pass, then render once after the full batch.":"- Apply only the requested change, then render the result once."}\n- Edit the canonical source above; do not search for or edit a different plausible file.\n- Preserve the represented product page faithfully: keep its real copy, structure, fonts, assets, spacing, and responsive behavior except for the selected edits. Never replace it with generic mockup content.\n- Preserve unrelated content and styling.\n- Use the existing mockup render workflow to produce both desktop and mobile captures after the edit. Run it with TP_DESIGN_REQUEST_ID=${t.cid} and TP_DESIGN_PARENT_REVISION=${e.revision} in the command environment so the proof correlates to this request.\n- Do not claim this is live until both captures succeed and the room receives the manifest.\n- If any target is stale or ambiguous, stop without editing and explain that the person must reselect it.`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{claimTurnReceipt as e}from"./turn-receipt.mjs";const t=4194304,n=new Set(["received","prepared","dispatching","dispatched","completed","recovery_required"]),r=e=>Buffer.byteLength(JSON.stringify(e)),o=()=>new Error("Command journal capacity is full; pending commands must be resolved first.");export function restoreCommandJournal(e){const
|
|
1
|
+
import{claimTurnReceipt as e}from"./turn-receipt.mjs";const t=4194304,n=new Set(["received","prepared","dispatching","dispatched","completed","recovery_required"]),r=e=>Buffer.byteLength(JSON.stringify(e)),o=()=>new Error("Command journal capacity is full; pending commands must be resolved first.");export function restoreCommandJournal(e){const i=(Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.cid&&n.has(e.state));if(i.length>128||i.reduce((e,t)=>e+r(t.payload),0)>t)throw o();return i}export function recordCommand(e,n){const i="string"==typeof n?.cid?n.cid:"";if(!i)return null;const d=e.commandJournal||(e.commandJournal=[]),a=d.find(e=>e.cid===i);if(a)return a;const c=JSON.stringify(n),s=Buffer.byteLength(c);if(s>1048576)throw new Error("Command is too large to persist safely.");let m=d.reduce((e,t)=>e+r(t.payload),0);for(;d.length>=128||m+s>t;){const e=d.findIndex(e=>"completed"===e.state);if(e<0)throw o();m-=r(d[e].payload),d.splice(e,1)}const u={cid:i,state:"received",payload:JSON.parse(c),createdAt:Date.now(),updatedAt:Date.now()};return d.push(u),u}export function transitionCommand(e,t,r){if(!n.has(r))throw new TypeError(`invalid command journal state: ${r}`);const o=e?.commandJournal?.find(e=>e.cid===t);return o?("completed"===o.state&&"dispatched"===r||(o.state=r,o.updatedAt=Date.now()),o):null}export function acceptDurableCommand(t,n){if(t.receivedTurnCids?.has(n.cid))return{duplicate:!0,claimed:!1};const r=[...t.commandJournal||[]],o=new Set(t.receivedTurnCids||[]);try{recordCommand(t,n);const r=e(t,n.cid);if(!0!==t.flush?.())throw new Error("Command persistence failed; no receipt was issued.");return r}catch(e){throw t.commandJournal=r,t.receivedTurnCids=o,e}}export function beginCommandDispatch(e,t){if(!t)return;const n=e.commandJournal?.find(e=>e.cid===t);if(!n)throw new Error("Command has no durable receipt.");const r=n.state;if(transitionCommand(e,t,"dispatching"),!0!==e.flush?.())throw n.state=r,new Error("Command dispatch persistence failed; agent was not called.");const o=e._dispatchingCommandCids||(e._dispatchingCommandCids=[]);o.includes(t)||o.push(t)}export function takeCommandResult(e){return e._dispatchingCommandCids?.shift()||null}export function completeCommand(e,t){if(!t)return!1;const n=e.commandJournal?.find(e=>e.cid===t);if(!n)return!1;const r=n.state;return transitionCommand(e,t,"completed"),!0!==e.flush?.()?(n.state=r,!1):(e._dispatchingCommandCids&&(e._dispatchingCommandCids=e._dispatchingCommandCids.filter(e=>e!==t)),!0)}export function recoverableCommands(e){const t=[];for(const n of e?.commandJournal||[])"received"===n.state||"prepared"===n.state?t.push({...n,recovery:"safe_redelivery"}):"completed"!==n.state&&(n.state="recovery_required",n.updatedAt=Date.now(),t.push({...n,recovery:"dispatch_uncertain"}));return t}export function unnotifiedRecoveryCommands(e,t){return t.filter(t=>{const n=e.commandJournal.find(e=>e.cid===t.cid),r=!0===n.recoveryNotified||e.log?.some(e=>"error"===e.kind&&!0===e.recoverable&&e.commandCid===t.cid);return n.recoveryNotified=!0,!r})}
|
package/flow-preview.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:http";import e from"node:fs";import r from"node:fs/promises";import n from"node:path";export const previews=new Map;const
|
|
1
|
+
import t from"node:http";import e from"node:fs";import r from"node:fs/promises";import n from"node:path";export const previews=new Map;const o={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".svg":"image/svg+xml",".wav":"audio/wav",".mp3":"audio/mpeg",".ico":"image/x-icon"};function s(t){return o[n.extname(t).toLowerCase()]||"application/octet-stream"}async function i(t){try{return(await r.stat(t)).isFile()}catch{return!1}}async function a(t,e,r,n){t.writeHead(e,{"Content-Type":r,"Access-Control-Allow-Origin":"*"}),t.end(n)}export function startPreview({dir:e,port:o=0,host:c="127.0.0.1",id:p}={}){if(!e)throw new Error("startPreview: dir is required");const l=t.createServer(function(t){const e=n.join(t,"index.html");return async(o,c)=>{const p=function(t,e){let r;try{r=decodeURIComponent(new URL(e,"http://x").pathname)}catch{return null}const o=n.resolve(t,"."+r),s=n.relative(t,o);return s.startsWith("..")||n.isAbsolute(s)?null:o}(t,o.url||"/");if(null===p)return a(c,403,"text/plain; charset=utf-8","Forbidden");if(".thinkpool-design"===n.relative(t,p).split(n.sep)[0])return a(c,404,"text/plain; charset=utf-8","Not found");if(await i(p))try{return a(c,200,s(p),await r.readFile(p))}catch{return a(c,500,"text/plain; charset=utf-8","Read error")}if(await i(e))try{return a(c,200,s(e),await r.readFile(e))}catch{return a(c,500,"text/plain; charset=utf-8","Read error")}return a(c,404,"text/plain; charset=utf-8","Not found")}}(e)),u=new Set;let d=null;l.on("connection",t=>{d?t.destroy():(u.add(t),t.once("close",()=>u.delete(t)))});const f=()=>d||(void 0!==p&&previews.delete(p),d=new Promise(t=>{l.close(()=>t());for(const t of u)t.destroy()}),d);return new Promise((t,r)=>{l.once("error",r),l.listen(o,c,()=>{l.removeListener("error",r);const n=l.address().port,o=`http://${c}:${n}/`,s={server:l,url:o,stop:f,dir:e};void 0!==p&&previews.set(p,s),t({url:o,port:n,stop:f})})})}export async function stopAllPreviews(){const t=[...previews.values()];previews.clear(),await Promise.all(t.map(t=>t.stop()))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PARTNER_ACCESS as e,normalizePartnerAccess as s}from"./pair-access.mjs";export function createSharedControlSubscription({open:r,close:n}){let o=!1,t=!1,i=null,c=0,a=Promise.resolve();const l=()=>(a=a.catch(()=>{}).then(async()=>{if(i&&(!o||i.revision!==c)){const e=i;await n(e.value),i===e&&(i=null)}t||!o||i||(i={value:r(),revision:c})}),a);return{setAccess(r){const n=!t&&s(r)===e.CONTROL;return o&&!n&&c++,o=n,l()},isCurrent:e=>!t&&o&&i?.revision===c&&i?.value===e,dispose:()=>(t=!0,o=!1,c++,l())}}
|
package/lane-lifecycle.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{classifyCodeEvent as e}from"./code-event-contract.mjs";import{isSafeContinuationProjection as t}from"./lane-continuation.mjs";export const LIFECYCLE_STATE=Object.freeze({WORKING:"Working",BLOCKED:"Blocked",FAILED:"Failed",DONE:"Done",CANCELED:"Canceled"});export const BLOCKED_REASON=Object.freeze({NEEDS_DECISION:"needs_decision",NEEDS_INPUT:"needs_input",DEPENDENCY:"dependency",OFFLINE:"offline",STALLED:"stalled",UNKNOWN_GAP:"unknown_gap",VERIFICATION_REQUIRED:"verification_required"});const n=new Set([LIFECYCLE_STATE.FAILED,LIFECYCLE_STATE.DONE,LIFECYCLE_STATE.CANCELED]),s=new Set(["aborted","cancelled","canceled","interrupted"]);export function canTransitionLifecycle(e,t,{beginsNewWork:s=!1}={}){return!e||e===t||(n.has(e)?s&&t===LIFECYCLE_STATE.WORKING:e===LIFECYCLE_STATE.WORKING||e===LIFECYCLE_STATE.BLOCKED)}export function transitionLifecycle(e,t,n={}){if(!canTransitionLifecycle(e?.state,t?.state,n)){if(!1!==n.strict)throw new TypeError(`Illegal lifecycle transition: ${e?.state} -> ${t?.state}`);return e}return{...t,rawStatus:t.rawStatus??e?.rawStatus??null}}const r=e=>JSON.stringify(e,(e,t)=>!t||"object"!=typeof t||Array.isArray(t)?t:Object.fromEntries(Object.keys(t).sort().map(e=>[e,t[e]]))),a=e=>{const t=Array.isArray(e)?e:[],n=t.every(e=>Number.isFinite(e?.seq)),s=t.map((e,t)=>({event:e,shape:r(e),sourceIndex:t})).sort((e,t)=>{if(!n)return e.sourceIndex-t.sourceIndex;if(e.event.seq!==t.event.seq)return e.event.seq-t.event.seq;const s=Number(e.event?.ts)||0,r=Number(t.event?.ts)||0;return s!==r?s-r:String(e.event?.cid||"").localeCompare(String(t.event?.cid||""))||e.shape.localeCompare(t.shape)}),a=new Map,E=new Map,l=[],i=[];for(const e of s){const{event:t,shape:n}=e,s="string"==typeof t?.cid&&t.cid?t.cid:null,r=Number.isFinite(t?.seq)?t.seq:null,o=s?a.get(s):null,c=null!=r?E.get(r):null;if(o||c){const e=o||c;e.shape!==n&&i.push({cid:s,seq:r,priorCid:e.cid,priorSeq:e.seq});continue}const u={cid:s,seq:r,shape:n};s&&a.set(s,u),null!=r&&E.set(r,u),l.push(t)}return{events:l,conflicts:i}},E=(e,t=null,n={})=>({state:e,reason:t,phase:null,freshness:"current",transport:"online",rawStatus:null,verified:!1,...n});export function projectLaneLifecycle({events:r=[],busy:l=!1,pendingCount:i=0,pendingReason:o=null,stalled:c=!1,offline:u=!1,stale:C=!1,failed:L=!1,canceled:S=!1,rawStatus:p=null,continuation:N=null}={}){let T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:p});const _=a(r);let f=!1;for(const t of _.events){const n=e(t,{strict:!1});n.known?"volatile-presence"!==n.eventClass&&("pool"===t.kind&&(t.transient||t.pending)||("you"===t.kind||"assistant"===t.kind||"tool_result"===t.kind||"pool"===t.kind?T=E(LIFECYCLE_STATE.WORKING,null,{rawStatus:p,phase:t.kind}):"propose"===t.kind||"answer"===t.kind?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p}):"needs-input"===t.kind?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:p}):"error"===t.kind?T=E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):"result"===t.kind&&(T=s.has(t.subtype)?E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:p}):t.subtype&&"success"!==t.subtype?E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):E(LIFECYCLE_STATE.DONE,null,{rawStatus:p})))):f=!0}return _.conflicts.length||f?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.UNKNOWN_GAP,{rawStatus:p}):L?T=E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):S?T=E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:p}):i>0?T=E(LIFECYCLE_STATE.BLOCKED,o||BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p}):t(N)&&!n.has(T.state)&&"terminal"!==N.state?T=E(LIFECYCLE_STATE.BLOCKED,"waiting_for_human"===N.state?BLOCKED_REASON.NEEDS_INPUT:BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p,phase:"continuation"}):c?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.STALLED,{rawStatus:p,phase:"stalled"}):l&&!n.has(T.state)?T=E(LIFECYCLE_STATE.WORKING,null,{rawStatus:p,phase:T.phase||"active"}):u&&!n.has(T.state)&&(T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.OFFLINE,{rawStatus:p})),u&&(T.transport="offline"),C&&(T.freshness="stale"),Object.freeze(T)}export function projectFlowLifecycle(e){switch(e){case"building":case"dispatched":case"reviewing":case"assembling":case"planning":return E(LIFECYCLE_STATE.WORKING,null,{rawStatus:e,phase:e});case"awaiting_approval":case"approved":return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_DECISION,{rawStatus:e});case"pending":case"ready":case"reverted":return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.DEPENDENCY,{rawStatus:e});case"failed":return E(LIFECYCLE_STATE.FAILED,null,{rawStatus:e});case"cancelled":case"canceled":return E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:e});case"done":return E(LIFECYCLE_STATE.DONE,null,{rawStatus:e});default:return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:e})}}export function projectRecoveryWatermark({events:e=[],epoch:t=0,floor:n=null,gap:s=null,offline:r=!1,stale:E=!1}={}){const l=a(e),i=l.events.some(e=>Number.isFinite(e?.epoch)&&e.epoch!==t),o=l.events.filter(e=>Number.isFinite(e?.seq)&&(!Number.isFinite(e?.epoch)||e.epoch===t)),c=o.reduce((e,t)=>!e||t.seq>e.seq?t:e,null);let u=s&&Number.isFinite(s.from)&&Number.isFinite(s.to)?{from:s.from,to:s.to,reason:s.reason||"unknown",recoverable:!1!==s.recoverable}:null;if(!u&&(l.conflicts.length||i)){const e=l.conflicts.map(e=>e.seq).filter(Number.isFinite);u={from:e.length?Math.min(...e):null,to:e.length?Math.max(...e):null,reason:l.conflicts.length?"conflicting_event_identity":"foreign_epoch",recoverable:!0}}if(!u&&o.length>1)for(let e=1;e<o.length;e++)if(o[e].seq>o[e-1].seq+1){u={from:o[e-1].seq+1,to:o[e].seq-1,reason:"out_of_order_or_trimmed",recoverable:!0};break}return Object.freeze({epoch:t,floor:Number.isFinite(n)?n:null,lastProven:c?{cid:c.cid||null,seq:c.seq,epoch:t}:null,unknownGap:u,freshness:E?"stale":"current",transport:r?"offline":"online",globalRollbackAvailable:!1})}
|
|
1
|
+
import{classifyCodeEvent as e}from"./code-event-contract.mjs";import{isSafeContinuationProjection as t}from"./lane-continuation.mjs";export const LIFECYCLE_STATE=Object.freeze({WORKING:"Working",BLOCKED:"Blocked",FAILED:"Failed",DONE:"Done",CANCELED:"Canceled"});export const BLOCKED_REASON=Object.freeze({NEEDS_DECISION:"needs_decision",NEEDS_INPUT:"needs_input",DEPENDENCY:"dependency",OFFLINE:"offline",STALLED:"stalled",UNKNOWN_GAP:"unknown_gap",VERIFICATION_REQUIRED:"verification_required"});const n=new Set([LIFECYCLE_STATE.FAILED,LIFECYCLE_STATE.DONE,LIFECYCLE_STATE.CANCELED]),s=new Set(["aborted","cancelled","canceled","interrupted"]);export function canTransitionLifecycle(e,t,{beginsNewWork:s=!1}={}){return!e||e===t||(n.has(e)?s&&t===LIFECYCLE_STATE.WORKING:e===LIFECYCLE_STATE.WORKING||e===LIFECYCLE_STATE.BLOCKED)}export function transitionLifecycle(e,t,n={}){if(!canTransitionLifecycle(e?.state,t?.state,n)){if(!1!==n.strict)throw new TypeError(`Illegal lifecycle transition: ${e?.state} -> ${t?.state}`);return e}return{...t,rawStatus:t.rawStatus??e?.rawStatus??null}}const r=e=>JSON.stringify(e,(e,t)=>!t||"object"!=typeof t||Array.isArray(t)?t:Object.fromEntries(Object.keys(t).sort().map(e=>[e,t[e]]))),a=e=>{const t=Array.isArray(e)?e:[],n=t.every(e=>Number.isFinite(e?.seq)),s=t.map((e,t)=>({event:e,shape:r(e),sourceIndex:t})).sort((e,t)=>{if(!n)return e.sourceIndex-t.sourceIndex;if(e.event.seq!==t.event.seq)return e.event.seq-t.event.seq;const s=Number(e.event?.ts)||0,r=Number(t.event?.ts)||0;return s!==r?s-r:String(e.event?.cid||"").localeCompare(String(t.event?.cid||""))||e.shape.localeCompare(t.shape)}),a=new Map,E=new Map,l=[],i=[];for(const e of s){const{event:t,shape:n}=e,s="string"==typeof t?.cid&&t.cid?t.cid:null,r=Number.isFinite(t?.seq)?t.seq:null,o=s?a.get(s):null,c=null!=r?E.get(r):null;if(o||c){const e=o||c;e.shape!==n&&i.push({cid:s,seq:r,priorCid:e.cid,priorSeq:e.seq});continue}const u={cid:s,seq:r,shape:n};s&&a.set(s,u),null!=r&&E.set(r,u),l.push(t)}return{events:l,conflicts:i}},E=(e,t=null,n={})=>({state:e,reason:t,phase:null,freshness:"current",transport:"online",rawStatus:null,verified:!1,...n});export function projectLaneLifecycle({events:r=[],busy:l=!1,pendingCount:i=0,pendingReason:o=null,stalled:c=!1,offline:u=!1,stale:C=!1,failed:L=!1,canceled:S=!1,rawStatus:p=null,continuation:N=null}={}){let T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:p});const _=a(r);let f=!1;for(const t of _.events){const n=e(t,{strict:!1});n.known?"volatile-presence"!==n.eventClass&&("pool"===t.kind&&(t.transient||t.pending)||("you"===t.kind||"assistant"===t.kind||"tool_result"===t.kind||"pool"===t.kind?T=E(LIFECYCLE_STATE.WORKING,null,{rawStatus:p,phase:t.kind}):"propose"===t.kind||"answer"===t.kind?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p}):"needs-input"===t.kind?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:p}):"error"===t.kind&&!0!==t.recoverable?T=E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):"result"===t.kind&&(T=s.has(t.subtype)?E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:p}):t.subtype&&"success"!==t.subtype?E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):E(LIFECYCLE_STATE.DONE,null,{rawStatus:p})))):f=!0}return _.conflicts.length||f?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.UNKNOWN_GAP,{rawStatus:p}):L?T=E(LIFECYCLE_STATE.FAILED,null,{rawStatus:p}):S?T=E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:p}):i>0?T=E(LIFECYCLE_STATE.BLOCKED,o||BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p}):t(N)&&!n.has(T.state)&&"terminal"!==N.state?T=E(LIFECYCLE_STATE.BLOCKED,"waiting_for_human"===N.state?BLOCKED_REASON.NEEDS_INPUT:BLOCKED_REASON.NEEDS_DECISION,{rawStatus:p,phase:"continuation"}):c?T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.STALLED,{rawStatus:p,phase:"stalled"}):l&&!n.has(T.state)?T=E(LIFECYCLE_STATE.WORKING,null,{rawStatus:p,phase:T.phase||"active"}):u&&!n.has(T.state)&&(T=E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.OFFLINE,{rawStatus:p})),u&&(T.transport="offline"),C&&(T.freshness="stale"),Object.freeze(T)}export function projectFlowLifecycle(e){switch(e){case"building":case"dispatched":case"reviewing":case"assembling":case"planning":return E(LIFECYCLE_STATE.WORKING,null,{rawStatus:e,phase:e});case"awaiting_approval":case"approved":return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_DECISION,{rawStatus:e});case"pending":case"ready":case"reverted":return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.DEPENDENCY,{rawStatus:e});case"failed":return E(LIFECYCLE_STATE.FAILED,null,{rawStatus:e});case"cancelled":case"canceled":return E(LIFECYCLE_STATE.CANCELED,null,{rawStatus:e});case"done":return E(LIFECYCLE_STATE.DONE,null,{rawStatus:e});default:return E(LIFECYCLE_STATE.BLOCKED,BLOCKED_REASON.NEEDS_INPUT,{rawStatus:e})}}export function projectRecoveryWatermark({events:e=[],epoch:t=0,floor:n=null,gap:s=null,offline:r=!1,stale:E=!1}={}){const l=a(e),i=l.events.some(e=>Number.isFinite(e?.epoch)&&e.epoch!==t),o=l.events.filter(e=>Number.isFinite(e?.seq)&&(!Number.isFinite(e?.epoch)||e.epoch===t)),c=o.reduce((e,t)=>!e||t.seq>e.seq?t:e,null);let u=s&&Number.isFinite(s.from)&&Number.isFinite(s.to)?{from:s.from,to:s.to,reason:s.reason||"unknown",recoverable:!1!==s.recoverable}:null;if(!u&&(l.conflicts.length||i)){const e=l.conflicts.map(e=>e.seq).filter(Number.isFinite);u={from:e.length?Math.min(...e):null,to:e.length?Math.max(...e):null,reason:l.conflicts.length?"conflicting_event_identity":"foreign_epoch",recoverable:!0}}if(!u&&o.length>1)for(let e=1;e<o.length;e++)if(o[e].seq>o[e-1].seq+1){u={from:o[e-1].seq+1,to:o[e].seq-1,reason:"out_of_order_or_trimmed",recoverable:!0};break}return Object.freeze({epoch:t,floor:Number.isFinite(n)?n:null,lastProven:c?{cid:c.cid||null,seq:c.seq,epoch:t}:null,unknownGap:u,freshness:E?"stale":"current",transport:r?"offline":"online",globalRollbackAvailable:!1})}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.448",
|
|
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",
|
|
@@ -99,6 +99,8 @@
|
|
|
99
99
|
"transport-frame.mjs",
|
|
100
100
|
"turn-admission.mjs",
|
|
101
101
|
"durable-command-journal.mjs",
|
|
102
|
+
"host-control-channels.mjs",
|
|
103
|
+
"recovery-diagnostics.mjs",
|
|
102
104
|
"mcp-flight-recorder.mjs",
|
|
103
105
|
"plan-meters.mjs",
|
|
104
106
|
"recap.mjs",
|
|
@@ -125,6 +127,7 @@
|
|
|
125
127
|
"flow-preview.mjs",
|
|
126
128
|
"mockup-delivery.mjs",
|
|
127
129
|
"viewport.mjs",
|
|
130
|
+
"browser-job.mjs",
|
|
128
131
|
"design-edit.mjs",
|
|
129
132
|
"realtime-auth.mjs",
|
|
130
133
|
"design-source-contract.mjs",
|