bostrat-node 0.2.3 → 0.2.4
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/bin/bostrat-git-credential +6 -45
- package/bin/bostrat-mock +2 -117
- package/bin/bostrat-node.js +1 -11
- package/bin/bostrat-runbook +1 -125
- package/bin/bostrat-ticket +2 -136
- package/bin/bostrat-view +2 -95
- package/dist/index.js +139 -112
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,88 +1,107 @@
|
|
|
1
|
-
var
|
|
1
|
+
var yg=Object.defineProperty;var Ul=(t,e)=>{for(var n in e)yg(t,n,{get:e[n],enumerable:!0})};import ct from"node:path";import Ot from"node:fs";import uE from"node:readline";import{fileURLToPath as dE}from"node:url";import ae from"express";import fE from"node:http";import pE from"node:https";import Ll from"node:os";import{WebSocketServer as mE}from"ws";import{execFile as Ig}from"node:child_process";import{promisify as Rg}from"node:util";import ka from"node:fs";import{randomUUID as $g}from"node:crypto";import In from"node:fs/promises";import Hl from"node:os";import Qe from"node:path";function Ro(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function bg(){return process.env.CLAUDE_CONFIG_DIR?Qe.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Qe.join(Hl.homedir(),".claude.json")}function kg(){return process.env.CLAUDE_CONFIG_DIR?Qe.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Qe.join(Hl.homedir(),".claude","settings.json")}async function ke(t){if(!t||typeof t!="string")return;let e=Qe.resolve(t),n=bg(),r=kg(),o={};try{o=JSON.parse(await In.readFile(n,"utf8"))}catch{}o.hasCompletedOnboarding=!0,o.theme||(o.theme="dark"),o.projects=o.projects||{},o.projects[e]=o.projects[e]||{};let s=o.projects[e];Array.isArray(s.allowedTools)||(s.allowedTools=[]),s.hasTrustDialogAccepted=!0,s.hasCompletedProjectOnboarding=!0;let a=process.env.ANTHROPIC_API_KEY;if(a){let c=a.slice(-20);o.customApiKeyResponses=o.customApiKeyResponses||{},o.customApiKeyResponses.approved=o.customApiKeyResponses.approved||[],o.customApiKeyResponses.rejected=o.customApiKeyResponses.rejected||[],o.customApiKeyResponses.approved.includes(c)||o.customApiKeyResponses.approved.push(c)}await In.mkdir(Qe.dirname(n),{recursive:!0}),await In.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await In.readFile(r,"utf8"))}catch{}if(i.skipDangerousModePermissionPrompt=!0,process.env.BOSTRAT_HOOKS!=="off"){let l=`curl -sS -m 3 -X POST ${process.env.BOSTRAT_HOOK_URL||`http://127.0.0.1:${process.env.PORT||3362}/api/hook`} -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1 || true`,u=[{hooks:[{type:"command",command:l}]}];i.hooks=i.hooks||{};for(let d of["SessionStart","UserPromptSubmit","Stop"])i.hooks[d]=u;i.hooks.PostToolUse=[{matcher:"*",hooks:[{type:"command",command:l}]}]}await In.mkdir(Qe.dirname(r),{recursive:!0}),await In.writeFile(r,JSON.stringify(i,null,2))}import ya from"node:fs/promises";import xg from"node:os";import $o from"node:path";function Sg(){let t=process.env.CODEX_HOME||$o.join(xg.homedir(),".codex");return $o.join(t,"config.toml")}function Wl(t){return`[projects."${t}"]`}function vg(t,e){let n=Wl(e),r=t.indexOf(n);if(r===-1)return!1;let o=t.slice(r+n.length),s=o.search(/^\s*\[/m),a=s===-1?o:o.slice(0,s);return/trust_level\s*=\s*"trusted"/.test(a)}async function Rn(t){if(!t||typeof t!="string")return;let e=$o.resolve(t),n=Sg(),r="";try{r=await ya.readFile(n,"utf8")}catch{}if(vg(r,e))return;let o=Wl(e);if(r.includes(o)){let a=r.indexOf(o)+o.length,c=r.slice(a).search(/^\s*\[/m),l=c===-1?r.length:a+c,u=r.slice(a,l),d=/trust_level\s*=/.test(u)?u.replace(/trust_level\s*=\s*"[^"]*"/,'trust_level = "trusted"'):`${u.replace(/\s*$/,"")}
|
|
2
2
|
trust_level = "trusted"
|
|
3
3
|
`;r=r.slice(0,a)+d+r.slice(l)}else{let s=`
|
|
4
4
|
${o}
|
|
5
5
|
trust_level = "trusted"
|
|
6
6
|
`;r=r.trimEnd()+s+(r.length&&!r.endsWith(`
|
|
7
7
|
`)?`
|
|
8
|
-
`:"")}try{await
|
|
9
|
-
`,"utf8")}import
|
|
10
|
-
`,"utf8");let n=(await
|
|
11
|
-
`).filter(Boolean);n.length>
|
|
8
|
+
`:"")}try{await ya.mkdir($o.dirname(n),{recursive:!0}),await ya.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import ql from"node:fs/promises";import Ag from"node:os";import Tr from"node:path";function Tg(t){let n=Tr.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Tr.join(Ag.homedir(),".cursor");return Tr.join(r,"projects",n)}async function $n(t){if(!t||typeof t!="string")return;let e=Tr.resolve(t),n=Tg(e),r=Tr.join(n,".workspace-trusted");await ql.mkdir(n,{recursive:!0}),await ql.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
9
|
+
`,"utf8")}import _r from"node:fs/promises";import _g from"node:os";import Kl from"node:path";var ba=process.env.BOSTRAT_DEBUG_DIR||Kl.join(_g.homedir(),".bostrat","debug"),On=Kl.join(ba,"handoff.jsonl"),Gl=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Oo(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function Eg(t){try{await _r.mkdir(ba,{recursive:!0}),await _r.appendFile(On,`${JSON.stringify(t)}
|
|
10
|
+
`,"utf8");let n=(await _r.readFile(On,"utf8")).split(`
|
|
11
|
+
`).filter(Boolean);n.length>Gl&&await _r.writeFile(On,`${n.slice(-Gl).join(`
|
|
12
12
|
`)}
|
|
13
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
14
|
-
`).filter(Boolean).slice(-t).map(r=>{try{return JSON.parse(r)}catch{return{raw:r}}})}catch{return[]}}var
|
|
15
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,d,f]=r.split("|"),
|
|
16
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function
|
|
17
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
18
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function
|
|
19
|
-
`).trim():""}function
|
|
20
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let
|
|
21
|
-
|
|
22
|
-
`):""}import
|
|
23
|
-
`))Fw.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Ys(t,e){let n=Ot[t];if(!n?.busy)return"idle";if(Zs(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e))return"busy";let r=Hw(e);return t==="claude"&&r>0&&r<Uw?"unknown":"idle"}var Ww=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function Tl(t,e){return t==="claude"&&Ww.test(String(e||""))}function dr(t,e){return Ys(t,e)==="busy"}function io(t,e){return Ys(t,e)!=="idle"}function co(t,e){let n=Ot[t];return n?.up?n.up.test(e):!0}function qw(t,e){let n=Ot[t];return n?Zs(t,e)?!1:n.up.test(e)&&Ys(t,e)==="idle":!0}function Zs(t,e){let n=Ot[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="cursor"?!n.up.test(e):!0:!1:!1}async function vl(t,e,n){let r=Date.now()+n,o=0;for(await Fe(t).catch(()=>{});;){let s=await pt(t);if(qw(e,s)){await he(ur);return}if(e==="cursor"&&Bw.test(s)&&!Ot.cursor.up.test(s)&&o<3){if(!Sl(t,"cursor-trust")){await he(Xs);continue}o+=1,await L({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await yt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await he(ur);continue}if(e==="codex"&&o<3){let a=jw(s);if(a){if(!Sl(t,`codex-update:${a.from}->${a.to}`)){await he(Xs);continue}o+=1,await L({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await yt(t,"3",{enter:!1,kind:"codex-update-skip"}),xl({sessionName:t,from:a.from,to:a.to}),await he(ur);continue}}if(Date.now()>r){if(Zs(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=Ot.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="claude"&&Ot.claude.auth.test(s)&&(a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again"),L({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),L({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!Ot[e]?.block?.test(s),paneUp:!!Ot[e]?.up?.test(s),paneBusy:!!Ot[e]?.busy?.test(s)}).catch(()=>{}),await he(ur);return}await he(Xs)}}function Al({agent:t,agentCmd:e}={}){let n=e?Sn(e):Sn(t||"");return{label:n,realClaude:e?xn(e):n==="claude",realCodex:e?/codex/.test(e)&&!/fake-codex|fake-lead/.test(e):n==="codex",realCursor:e?/cursor/.test(e)&&!/fake-cursor|fake-box/.test(e):n==="cursor"}}function lo({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=Al({agent:n,agentCmd:r});return!e||!o&&!s?null:we({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function we({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Nw()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=Al({agent:r,agentCmd:o});if(i&&t&&await fe(t),c&&t&&await yn(t),l&&t&&await bn(t),(i||c||l)&&n){await vl(n,a,s);return}let u=Ot[r]?r:Ot[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await vl(n,u,s);return}await he(ur)}var Gw=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function ne(t){return t==="codex"?{enter:!0,submitDelayMs:Gw}:{enter:!0}}var _l=".bostrat/handoff",El="latest.md",Il="Checkpoint: write a work-in-progress handoff",Rl="HANDOFF WRITTEN",$l="Resume from .bostrat/handoff/latest.md",Vw="/clear",Jw=t=>`${Il} to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file) so a FRESH Claude context with none of this conversation can resume seamlessly. Use these sections: Objective; Done (with file paths); In progress (exact state); Next steps (ordered); Key files & commands; Open decisions/blockers; Resume instruction (one line). Be concrete and self-contained \u2014 assume the reader has none of this chat. When the file is written, reply only with ${Rl} and take no further action.`,zw=`${$l}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`;function Xw(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Rl:n.startsWith(Il)||n===Vw||n.startsWith($l):!1}var Yw=6e5;function Ol(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-Yw;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function Cl(t,e){let n=[];for(let r of t?.boxes||[]){let o=e?.(r.name);o?.startedAt&&n.push({box:r.name,from:o.startedAt,to:o.finishedAt||Date.now()+1e12})}return n}function Ml(t,{windows:e=[]}={}){if(Xw(t.role,t.text))return!0;if(t.role!=="assistant"||!t.ts)return!1;for(let n of e)if(t.box===n.box&&t.ts>=n.from&&t.ts<=n.to)return!0;return!1}var Zw=3500,Qw=3e5,Qs=new Map;function fo(t){return Qs.get(t)||null}async function Pl(t){let e=Qs.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await G(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:uo.join(n.cwd,_l,El)};return Qs.set(t,r),tg(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function tg(t,e){t.step="summarize",await Nl(t.name,e),t.step="clear";let n=await G(t.name);await ea(t.name,n,"/clear"),await Dl(Zw),t.step="resume",await ea(t.name,n,zw),t.step="done",t.finishedAt=Date.now()}async function Nl(t,e){let n=uo.join(e,_l),r=uo.join(n,El);await na.mkdir(n,{recursive:!0});let o=await ta(r),s=await G(t);return await L({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await ea(t,s,Jw(r)),await eg(r,o,Qw),await L({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await ta(r)}),await ng(n,r),r}async function Bl(t){let e=await G(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Nl(t,e.cwd)}async function ta(t){try{return(await na.stat(t)).mtimeMs}catch{return 0}}async function eg(t,e,n){let r=Date.now()+n;for(;;){if(await ta(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Dl(2e3)}}async function ng(t,e){try{if(!Kw.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await na.copyFile(e,uo.join(t,`${n}.md`))}catch{}}function Dl(t){return new Promise(e=>setTimeout(e,t))}async function ea(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await we({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await G(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await yt(t,n,{kind:"orchestration-drive",...ne(r.agent)})}import P from"node:fs/promises";import U from"node:path";import{randomUUID as ts}from"node:crypto";import{execFile as jx}from"node:child_process";import{promisify as Lx}from"node:util";import ra from"node:fs/promises";import rg from"node:path";var og=0;async function qt(t,e,{mode:n}={}){await ra.mkdir(rg.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++og}.tmp`;await ra.writeFile(r,JSON.stringify(e,null,2)+`
|
|
24
|
-
|
|
25
|
-
`)
|
|
26
|
-
`,n)
|
|
27
|
-
|
|
28
|
-
`)
|
|
29
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let d=l.timestamp?Date.parse(l.timestamp):null;for(let f of u)if(!(!f||typeof f!="object"))if(f.type==="tool_use"&&f.id){if(f.name==="Agent"){let w=f.input||{};o.set(f.id,{id:f.id,kind:"agent",name:String(w.description||"").trim()||null,detail:String(w.subagent_type||"").trim()||null,ts:d,proven:!1})}else if(f.name==="Bash"&&f.input?.run_in_background===!0){let w=String(f.input.command||"").trim();o.set(f.id,{id:f.id,kind:"command",name:String(f.input.description||"").trim()||(w?In(w):null),detail:w?In(w):null,ts:d,proven:!0})}}else f.type==="tool_result"&&f.tool_use_id&&qg.test(Gg(f.content))&&s.add(f.tool_use_id)}let a=[];for(let i of o.values()){if(!i.proven&&!s.has(i.id))continue;let{proven:c,...l}=i;a.push(l)}return a.slice(-n)}import wr from"node:fs";import Vg from"node:fs/promises";import qe from"node:path";import Jg from"node:os";import zg from"node:crypto";import Xg from"node:zlib";var wo=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),Lt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Rn=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function be(t,e){if(!t)return null;let n=String(t);if(e){let o=e.endsWith("/")?e:`${e}/`;if(n===e)return".";if(n.startsWith(o))return n.slice(o.length)||"."}let r=n.split("/").filter(Boolean);return n.startsWith("/")&&r.length>2?r.slice(-2).join("/"):n}var ba=t=>`"${String(t).replace(/"/g,'\\"')}"`;function Kg(t){let e=typeof t=="string"?t:String(t?.patch??t?.input??""),n=[];for(let r of e.matchAll(/^\*{3} (?:Update|Add|Delete) File: (.+)$/gm))n.push(r[1].trim());return n}function nu(t,e,n){switch(t){case"Shell":{let r=String(e?.command||"").trim();return r?{command:r,description:String(e?.description||"").trim()}:null}case"Read":{let r=e?.path;if(!r)return null;let o=Number(e?.offset)||null,s=o&&Number(e?.limit)?o+Number(e.limit)-1:null;return{command:Lt(`read ${be(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${Rn(r)}`:`Read ${Rn(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?be(e.path,n):null,s=e?.glob?` --glob ${ba(e.glob)}`:"",a=Lt(String(r),40);return{command:Lt(`grep ${ba(r)}${o&&o!=="."?` ${o}`:""}${s}`,140),description:o&&o!=="."?`Search for \u201C${a}\u201D in ${o}`:`Search for \u201C${a}\u201D`}}case"Glob":{let r=e?.glob_pattern;if(!r)return null;let o=e?.target_directory?be(e.target_directory,n):null;return{command:Lt(`glob ${ba(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Lt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Lt(`edit ${be(e.path,n)}`,140),description:`Edit ${Rn(e.path)}`}:null;case"Write":return e?.path?{command:Lt(`write ${be(e.path,n)}`,140),description:`Write ${Rn(e.path)}`}:null;case"Delete":return e?.path?{command:Lt(`delete ${be(e.path,n)}`,140),description:`Delete ${Rn(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>be(s,n)).filter(Boolean);return{command:Lt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=Kg(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Lt(`apply_patch ${be(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${Rn(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:Lt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var Yg=process.env.BOSTRAT_CURSOR_CHATS||qe.join(Jg.homedir(),".cursor","chats");function su(t){let e=zg.createHash("md5").update(String(t)).digest("hex");return qe.join(Yg,e)}function au(t){try{let e=JSON.parse(wr.readFileSync(qe.join(t,"meta.json"),"utf8"));return{createdAtMs:Number(e.createdAtMs)||null,updatedAtMs:Number(e.updatedAtMs)||Number(e.createdAtMs)||null,hasConversation:e.hasConversation===!0}}catch{return null}}function Zg(t){let e=su(t),n;try{n=wr.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=qe.join(e,o.name),a=qe.join(s,"store.db");if(!wr.existsSync(a))continue;let i=au(s);if(!i)continue;let c={id:o.name,dir:s,store:a,...i};(!r||c.hasConversation&&!r.hasConversation||c.hasConversation===r.hasConversation&&(c.updatedAtMs||0)>(r.updatedAtMs||0))&&(r=c)}return r}function go(t,e){if(!t)return null;if(e){let n=qe.join(su(t),e),r=qe.join(n,"store.db");if(wr.existsSync(r))return{id:e,dir:n,store:r,...au(n)||{createdAtMs:null,updatedAtMs:null}}}return Zg(t)}function ru(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function Qg(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=Xg.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function ty(t){let e=[],n=0,r=t.length,o=()=>{let s=0,a=0;for(;n<r;){let i=t[n++];if(s+=(i&127)*2**a,!(i&128))break;a+=7}return s};for(;n<r;){let s=o(),a=s>>>3,i=s&7;if(i===2){let c=o(),l=t.subarray(n,n+c);n+=c,a===1&&c===32&&e.push(l.toString("hex"))}else if(i===0)o();else if(i===5)n+=4;else if(i===1)n+=8;else break}return e}function ey(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!wo.has(r.toolName))continue;let o=nu(r.toolName,r.args,e);!o||!o.command||n.push({id:r.toolCallId||null,tool:r.toolName,command:o.command,description:o.description||""})}return n}var ny=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
30
|
-
`).
|
|
31
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function cy(t){let e=await iy(t);if(!e)return null;let n;try{n=JSON.parse(e)}catch{return null}return n?.type==="session_meta"&&n?.payload?.cwd||null}async function Ge(t,e){if(!t&&!e)return null;let n=ay();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await pu(t);for(let o of n){let s=await cy(o.file);if(s&&(s===t||await pu(s)===r))return o}return null}var ly=new Set(["output_text","text"]),uy=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&ly.has(e.type)).map(e=>e.text).join(`
|
|
32
|
-
`).trim():"";async function mu(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Ge(t,e);if(!o)return[];let s;try{s=await vt(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,d=null;for(let f of s.split(`
|
|
33
|
-
`)){let w=f.trim();if(!w||w[0]!=="{")continue;let m;try{m=JSON.parse(w)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,d=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:Ta,sourceId:m.payload?.turn_id||d||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i}));continue}if(m.type!=="response_item")continue;let g=m.payload;if(!g)continue;if((g.type==="function_call"||g.type==="custom_tool_call")&&g.name){u=!0,va.has(g.name)||c.push(g.name);continue}if(g.type!=="message"||g.role!=="assistant")continue;u=!0;let x=uy(g.content),A=c.length?c.slice():null;c=[],!(!x&&!A)&&a.push({role:"assistant",text:x,tools:A||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i})}if(i)for(let f of a)f.model||(f.model=i);return a.slice(-n)}var gr=new Set(["exec_command","shell","exec"]),va=new Set(["wait","write_stdin"]),Ta="Codex ended the turn without a response \u2014 it hit an error the CLI shows only in the terminal (e.g. quota or billing). Check the Terminal tab.";function dy(t){if(typeof t?.cmd=="string"&&t.cmd.trim())return t.cmd.trim();let e=t?.command;return typeof e=="string"&&e.trim()?e.trim():Array.isArray(e)&&e.length?e.length===3&&/^(ba)?sh$/.test(e[0])&&e[1]==="-lc"?String(e[2]).trim():e.map(String).join(" ").trim():null}function fy(t){let e=t[0];if(e!=='"'&&e!=="'"&&e!=="`")return null;let n="";for(let r=1;r<t.length;r++){let o=t[r];if(o==="\\"){let s=t[++r];if(s===void 0)return null;if(s==="n")n+=`
|
|
34
|
-
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function py(t){let e=String(t||""),n=[],r=/exec_command\s*\(/g;for(let s;s=r.exec(e);)n.push(s.index+s[0].length);let o=[];return n.forEach((s,a)=>{let i=e.slice(s,a+1<n.length?n[a+1]:e.length),c=/(?:^|[{,\s])["'`]?cmd["'`]?\s*:\s*/.exec(i);if(!c)return;let l=fy(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function hu(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Ge(t,e);if(!o)return[];let s;try{s=await vt(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
35
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let d=[];if(u.type==="function_call"&&gr.has(u.name)){let w;try{w=JSON.parse(u.arguments||"{}")}catch{continue}let m=dy(w);m&&(d=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(d=py(u.input));if(!d.length)continue;let f=u.call_id||u.id||null;d.forEach((w,m)=>{a.push({id:f?m?`${f}:${m}`:f:null,tool:u.name,command:w,description:"",summary:ye(w),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function wu(t,e){let n=await Ge(t,e);if(!n)return null;try{return Math.round((await Sa.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var On=class{constructor(e,n=new Set){this.agent=e,this.cliTools=n}async readMessages(){return[]}async readCommands(){return[]}async readJobs(){return[]}async updatedAt(){return null}},Aa=class extends On{transcript(e){return!e?.cwd||!e?.sessionId?null:ee(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Vs(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Ql(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?eu(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await my.stat(n)).mtimeMs)}catch{return null}}},_a=class extends On{async readMessages(e,n){return cu(e.cwd,e.sessionId,n)}async readCommands(e,n){return uu(e.cwd,e.sessionId,n)}async updatedAt(e){return du(e.cwd,e.sessionId)}},Ea=class extends On{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return mu(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return hu(e.cwd,this.sessionId(e),n)}async updatedAt(e){return wu(e.cwd,this.sessionId(e))}},hy=new On("unknown"),gu=new Map;function Ia(t){return gu.set(t.agent,t),t}function yr(t){return gu.get(t)||hy}Ia(new Aa("claude",ya));Ia(new _a("cursor",wo));Ia(new Ea("codex",gr));var Ra=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function yu(t,e={}){return(await yr(t.agent).readMessages(t,e)).map(Ra(t))}async function bu(t,e={}){return(await yr(t.agent).readCommands(t,e)).map(Ra(t))}async function ku(t,e={}){return(await yr(t.agent).readJobs(t,e)).map(Ra(t))}var wy=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,xu=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function gy(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
36
|
-
`);if(!xu.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(wy);if(u)o.unshift({n:Number(u[1]),label:u[2]}),s=!0;else{if(s&&r[l].trim()==="")continue;if(s){var a=r[l].trim();break}}}if(o.length<2)return null;let i=o.sort((l,u)=>l.n-u.n);if(i.some((l,u)=>l.n!==u+1))return null;let c=typeof a=="string"&&xu.test(a)?a:"The agent is asking for approval";return{provider:e,box:n,promptId:`${e}:${c}:${i.map(l=>l.label).join("|")}`.slice(0,200),questions:[{question:c,header:"Approval",multiSelect:!1,options:i.map(l=>({label:l.label,description:""}))}]}}var yo=/(^|\n)\s*›/,Su=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function yy(t,e=""){let n=String(t);if(!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n)||yo.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
37
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(Su);if(!u)continue;s<0&&(s=l);let d="";for(let f=l+1;f<Math.min(l+3,r.length);f++){let w=r[f].trim();if(w){if(Su.test(r[f])||/^Press enter/.test(w))break;d=w;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:d})}let a=o.sort((l,u)=>l.n-u.n);if(a.length<2||a.some((l,u)=>l.n!==u+1)||!a.some(l=>/Sign in with ChatGPT/.test(l.label)))return null;let i=[];for(let l=0;l<s;l++){let u=r[l].trim();u&&!/Welcome to Codex/.test(u)&&i.push(u)}let c=i.join(" ")||"Codex isn't signed in on this box \u2014 choose a sign-in method";return{provider:"codex",box:e,multiSelect:!1,cursorIndex:Math.max(0,a.findIndex(l=>l.cursor)),answerMode:"keys",promptId:`codex:signin:${a.map(l=>l.label).join("|")}`.slice(0,200),questions:[{question:c,header:"Sign in",multiSelect:!1,options:a.map(l=>({label:l.label,description:l.desc}))}]}}var vu=/Enter this one-time code/,by=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function ky(t,e=""){let n=String(t);if(!vu.test(n)||yo.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
38
|
-
`),o=r.findIndex(i=>vu.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(by);if(c){s=c[1];break}}if(!s)return null;let a=(n.match(/https:\/\/\S+/)||[])[0]||"https://auth.openai.com/codex/device";return{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:`codex:device-code:${s}`,questions:[{question:`Codex is waiting for you to finish signing in: open ${a} and enter code ${s} (expires in 15 minutes). The code goes straight to OpenAI \u2014 bostrat never sees your login. This card clears once OpenAI confirms.`,header:"Sign in",multiSelect:!1,options:[{label:"Cancel sign-in",description:"Go back to the sign-in options"}]}]}}function xy(t,e=""){let n=String(t);return!/open the following link to authenticate/.test(n)||yo.test(n)?null:{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:"codex:signin-browser",questions:[{question:"Codex started its browser sign-in, which opens on the box itself and can't complete from another device. Go back and choose \u201CSign in with Device Code\u201D instead.",header:"Sign in",multiSelect:!1,options:[{label:"Go back to sign-in options",description:""}]}]}}function Sy(t,e=""){let n=String(t);return!/Paste or type your API key/.test(n)||yo.test(n)?null:{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:"codex:signin-apikey",questions:[{question:"Codex is asking for an OpenAI API key. For safety a key can only be typed in the Terminal tab, never through chat. To use your ChatGPT subscription instead, go back and pick a sign-in option.",header:"Sign in",multiSelect:!1,options:[{label:"Go back to sign-in options",description:""}]}]}}function Tu(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=ky(e,n)||xy(e,n)||Sy(e,n)||yy(e,n);if(r)return r}return gy(e,{provider:t,box:n})}function Au(t,e){let n=Array.isArray(e?.[0])?e[0]:[],o=(Number.isInteger(n[0])&&n[0]>=0?n[0]:0)+1;return String(o)}var Iu=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Ca=/Chat about this/i,Ke=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,_u=/^(type something|chat about this|submit)\b/i,Ve=/^[─—-]{6,}\s*$/,Ma=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Je(t){let e=t.search(Ma);return(e>=0?t.slice(0,e):t).trim()}function vy(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function Ty(t){let e=String(t).trim();return!!(!e||Ve.test(t)||Ma.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function Ay(t,e=""){let n=[],r=/([☐☒])\s+([^☐☒←→✔]+)/g,o;for(;(o=r.exec(t))!==null;)n.push({label:o[2].trim(),answered:o[1]==="\u2612",current:!1});if(!n.length)return null;let s=_y(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function _y(t,e){let n=String(e).toLowerCase();if(n){let o=t.map((s,a)=>({i:a,label:s.label.toLowerCase()})).sort((s,a)=>a.label.length-s.label.length);for(let{i:s,label:a}of o){if(a.split(/\s+/).filter(l=>l.length>3).some(l=>n.includes(l)))return s;let c=a.slice(0,8).trim();if(c.length>=4&&n.includes(c))return s}}let r=t.findIndex(o=>!o.answered);return r>=0?r:t.length-1}function Eu(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Ru=/^\s*Switch model\?\s*$/m;function Ey(t,e=""){let n=String(t);if(!Ru.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
39
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let d=r[u].match(Ke);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!Ve.test(r[u])&&a.push(f)}}if(s.length<2)return null;let i=s.map(u=>({label:Je(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=["Switch model?",...a].join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:model-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Model",multiSelect:!1,options:i}]}}var $u=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,Iy=/^\s*[─—▔-]{6,}\s*$/;function Ry(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
40
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!$u.test(n[r]))return null;let o=r-1;for(;o>=0&&n[o].trim()==="";)o--;let s=[];for(;o>=0;o--){let c=n[o].match(Ke);if(!c)break;s.unshift({cursor:!!c[1],n:Number(c[2]),label:c[4].trim()})}if(s.length<2||s.some((c,l)=>c.n!==l+1))return null;let a=[];for(let c=o;c>=0&&a.length<6&&!(Iy.test(n[c])||Ve.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Je(c.label),description:""}));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,s.findIndex(c=>c.cursor)),promptId:`claude:confirm-gate:${i.map(c=>c.label).join("|")}`.slice(0,240),questions:[{question:a.join(" ")||"Claude Code is asking you to confirm",header:"Confirm",multiSelect:!1,options:i}]}}function $y(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
41
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(Ke);l&&o.push({cursor:!!l[1],label:l[4].trim()})}let s=o.filter(c=>/^(submit answers|cancel)$/i.test(c.label));if(s.length<2)return null;let a=s.map(c=>({label:c.label,description:""})),i=Math.max(0,s.findIndex(c=>c.cursor));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:i,review:!0,promptId:"claude:review:submit-answers",questions:[{question:"Ready to submit your answers?",header:"Review",multiSelect:!1,options:a}]}}var $a=/Resume from summary/i,Oy=/^\s*This session is\b/;function Cy(t,e=""){let n=String(t);if(!$a.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
42
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(Oy.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[r[o].trim()];for(let u=o+1;u<r.length;u++){let d=r[u].match(Ke);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!Ve.test(r[u])&&a.push(f)}}if(s.length<2||!s.some(u=>$a.test(u.label)))return null;let i=s.map(u=>({label:Je(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=a.join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:resume-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Resume",multiSelect:!1,options:i}]}}var ze=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function Pa(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(Ke);o&&e.push({line:r,cursor:!!o[1],n:Number(o[2]),label:o[4].trim()})}let n=e.sort((r,o)=>r.n-o.n);return n.length<2||n.some((r,o)=>r.n!==o+1)?null:n}var Ou=/Choose the text style that looks best with your terminal/,Cu=/Let's get started\./;function My(t,e=""){let n=String(t);if(!Ou.test(n)||!Cu.test(n)||ze.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
43
|
-
`),o=Pa(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"theme",promptId:`claude:setup:theme:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks: choose the text style that looks best with your terminal. You can change it later with /theme.",header:"Theme",multiSelect:!1,options:o.map(s=>({label:Je(s.label),description:""}))}]}:null}var Mu=/Select login method:/;function Py(t,e=""){let n=String(t);if(!Mu.test(n)||ze.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
44
|
-
`),o=Pa(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Je(i),description:c.join("\xB7").trim()}});return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(a=>a.cursor)),setup:"login-method",promptId:`claude:setup:login-method:${s.map(a=>a.label).join("|")}`.slice(0,200),questions:[{question:"How do you want to sign in to Claude? A Claude subscription (Pro/Max/Team) uses your existing plan; a Console account bills API usage. Your login stays on this box \u2014 bostrat never sees it.",header:"Log in",multiSelect:!1,options:s}]}}var Na=/Paste code here if prompted/,Ny=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function By(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!Ny.test(o)||Na.test(t[r]))break;n+=o}return n}function Dy(t,e=""){let n=String(t);if(!Na.test(n)||ze.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
45
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=By(r,s);break}return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,answerMode:"text",setup:"oauth",url:o,inputLabel:"Paste your sign-in code",promptId:"claude:setup:oauth",questions:[{question:"Open the sign-in link, approve access, then paste the code you're shown back here. The code goes straight to the Claude CLI on your box \u2014 bostrat relays the keystrokes and never stores it.",header:"Log in",multiSelect:!1,options:[]}]}:null}var Oa=/OAuth error:/,jy=/Press Enter to retry\./;function Ly(t,e=""){let n=String(t);if(!Oa.test(n)||!jy.test(n)||ze.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
46
|
-
`)].reverse().find(a=>Oa.test(a))||"OAuth error").trim();return{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,setup:"oauth-error",promptId:`claude:setup:oauth-error:${s}`.slice(0,200),questions:[{question:`${s} \u2014 retry to get a fresh sign-in link.`,header:"Log in",multiSelect:!1,options:[{label:"Retry sign-in",description:"Mints a new sign-in link and code"}]}]}}var Pu=/Quick safety check: Is this a project you created or one you trust/;function Uy(t,e=""){let n=String(t);if(!Pu.test(n)||ze.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
47
|
-
`),o=Pa(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"trust",promptId:`claude:setup:trust:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks whether it can read, edit, and run files in this workspace. This box's workspace is bostrat-managed scratch space, so trusting it is safe.",header:"Folder trust",multiSelect:!1,options:o.map(s=>({label:Je(s.label),description:""}))}]}:null}var Nu=/^\s*Press Enter to continue\s*$/;function Fy(t,e=""){let n=String(t);if(ze.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
48
|
-
`),o=-1;for(let i=r.length-1;i>=0;i--)if(r[i].trim()!==""){o=i;break}if(o<0||!Nu.test(r[o]))return null;let s=[];for(let i=o-1;i>=0&&s.length<3;i--){let c=r[i].trim();if(!c){if(s.length)break;continue}if(Ve.test(r[i])||Ma.test(c))break;s.unshift(c)}let a=s.join(" ")||"Claude Code is waiting for you to continue.";return{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,setup:"continue",promptId:`claude:setup:continue:${a}`.slice(0,200),questions:[{question:a,header:"Continue",multiSelect:!1,options:[{label:"Continue",description:""}]}]}}function Hy(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Ca.test(t[r])){e=r;break}if(e<0)return null;let n=-1;for(let r=e-1;r>=0;r--)if(/[☐☒]/.test(t[r])){n=r;break}return n<0?null:{headerLine:n,chatLine:e,optStart:n+1,optEnd:e}}function Wy(t,e=""){let n=String(t);if(!Iu.test(n)||!Ca.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
49
|
-
`),o=Hy(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let h=i;h<c;h++){let p=r[h].match(Ke);p&&(u<0&&(u=h),l.push({line:h,cursor:!!p[1],checked:p[3]!=null&&p[3]!==" ",hasCheckbox:p[3]!=null,label:p[4].trim(),desc:""}))}if(!l.length)return null;for(let h of l)for(let p=h.line+1;p<Math.min(r.length,h.line+4)&&!(Ke.test(r[p])||Ve.test(r[p]));p++){let b=r[p].trim();if(!(!b||Ty(r[p])||_u.test(b))){h.desc=vy(b);break}}let d=l.filter(h=>!_u.test(h.label));if(!d.length)return null;let f=d.some(h=>h.hasCheckbox),w=Math.max(0,d.findIndex(h=>h.cursor)),m=s>=0?s+1:0,g=[];for(let h=m;h<u;h++){let p=r[h].trim();p&&!Ve.test(r[h])&&!/[☐☒]/.test(r[h])&&g.push(p)}let x=g.join(" ")||(s>=0?Eu(r[s]):"")||"The agent is asking a question",A=s>=0?Ay(r[s],x):null,S=A?.currentTabLabel||(s>=0?Eu(r[s]):""),T=d.map(h=>({label:Je(h.label),description:h.desc})),E=T.map(h=>h.label).join("|"),M=`claude:${A?`q${A.questionIndex+1}/${A.questionTotal}`:"q1/1"}:${S}:${x}:${E}`.slice(0,240),I=A?`claude:ask:${A.tabs.map(h=>h.label).join("|")}`.slice(0,240):M;return{provider:"claude",box:e,multiSelect:f,cursorIndex:w,promptId:M,groupId:I,...A?{questionIndex:A.questionIndex,questionTotal:A.questionTotal,currentTabLabel:A.currentTabLabel,tabs:A.tabs}:{},questions:[{question:x,header:S,multiSelect:f,options:T}]}}function Cn(t,e){let n=String(e||"");return n?t==="claude"?Iu.test(n)&&Ca.test(n)||Ru.test(n)||$a.test(n)||$u.test(n.trimEnd().split(`
|
|
50
|
-
`).pop()||"")?!0:ze.test(n)?!1:Ou.test(n)&&Cu.test(n)||Mu.test(n)||Na.test(n)||Oa.test(n)||Pu.test(n)||Nu.test(n.trimEnd().split(`
|
|
51
|
-
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function Xe(t,e){return!t||!e?null:t.agent==="claude"?$y(e,t.name)||Wy(e,t.name)||Ey(e,t.name)||Cy(e,t.name)||Ly(e,t.name)||Dy(e,t.name)||Py(e,t.name)||My(e,t.name)||Uy(e,t.name)||Fy(e,t.name)||Ry(e,t.name):t.agent==="cursor"||t.agent==="codex"?Tu(t.agent,e,t.name):null}async function Ba(t){if(!t)return null;let e=await me(t.name).catch(()=>"");return Xe(t,e)}function Bu(t,e){let n=Array.isArray(e?.[0])?e[0].filter(a=>Number.isInteger(a)&&a>=0):[],r=Number.isInteger(t?.cursorIndex)?t.cursorIndex:0,o=(a,i)=>{let c=i-a;return Array.from({length:Math.abs(c)},()=>c>0?"Down":"Up")},s=[];if(t?.multiSelect){let a=r;for(let i of[...n].sort((c,l)=>c-l))s.push(...o(a,i),"Enter"),a=i;s.push("Tab","Enter")}else{let a=n.length?n[0]:0;s.push(...o(r,a),"Enter")}return s}function Du(t,e){let n=Number.isInteger(t?.questionIndex)?t.questionIndex:0,o=(Number.isInteger(e)?e:n)-n;if(!o)return[];let s=o>0?"Tab":"BTab";return Array.from({length:Math.abs(o)},()=>s)}function Mn(t){return typeof t=="string"?t.trim():""}var ju=/^\s*─{8,}\s*$/,qy=/^❯[ ]?(.*)$/;function Da(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
52
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(ju.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(ju.test(n[c])){o=c;break}if(o<0)return null;let s=n.slice(o+1,r);if(!s.length)return null;let a=s[0].match(qy);if(!a)return null;let i=[a[1].trim(),...s.slice(1).map(c=>c.trim())].filter(Boolean);return i.length?i.join(" "):null}function Ye(t){return String(t||"").replace(/\s+/g," ").trim()}function Lu(t,e){let n=Ye(t),r=Ye(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import{execFile as Gy}from"node:child_process";import{promisify as Ky}from"node:util";var La=Ky(Gy),Uu=64*1024*1024;async function mt(t,e,{input:n}={}){let r=La("git",["-C",t,...e],{maxBuffer:Uu});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Ua(t){if(!t)return!1;try{let{stdout:e}=await mt(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function Fu(t){let{stdout:e}=await mt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function Vy(t){try{return await mt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Hu(t,e="bostrat: subtask snapshot"){let{stdout:n}=await mt(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>La("git",["-C",t,...a],{maxBuffer:Uu,env:o,...i});try{let a=await Vy(t);a&&await s(["read-tree","HEAD"]),await s(["add","-A"]);let{stdout:i}=await s(["write-tree"]),c=i.trim(),l=a?["commit-tree",c,"-p","HEAD","-m",e]:["commit-tree",c,"-m",e],{stdout:u}=await s(l);return u.trim()}finally{try{await La("rm",["-f",r])}catch{}}}async function Wu(t,e){let{stdout:n}=await mt(t,["status","--porcelain"]);return n.trim()?(await mt(t,["add","-A"]),await mt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function bo(t,e,n,r){return await mt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function Fa(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await mt(t,["symbolic-ref","--short","refs/remotes/origin/HEAD"]);if(e.trim())return e.trim()}catch{}for(let[e,n]of[["refs/remotes/origin/main","origin/main"],["refs/remotes/origin/master","origin/master"],["refs/heads/main","main"],["refs/heads/master","master"]])try{return await mt(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function qu(t){await mt(t,["init","-q"]),await mt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Ze(t,e){try{await mt(t,["worktree","remove","--force",e])}catch{try{await mt(t,["worktree","prune"])}catch{}}}function Jy(t){let e=/(\d+) files? changed/.exec(t),n=/(\d+) insertions?\(\+\)/.exec(t),r=/(\d+) deletions?\(-\)/.exec(t);return{files:e?Number(e[1]):0,insertions:n?Number(n[1]):0,deletions:r?Number(r[1]):0}}function Ha(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Gu(t,e,n,r){let{stdout:o}=await mt(t,Ha(["diff","--shortstat",e,n],r));return Jy(o)}async function Ku(t,e,n,r){let{stdout:o}=await mt(t,Ha(["diff","--name-only",e,n],r));return o.split(`
|
|
53
|
-
`).map(s=>s.trim()).filter(Boolean)}async function Vu(t,e,n,r){let{stdout:o}=await mt(t,Ha(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await ja(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await ja(t,o,["--3way","--whitespace=nowarn"]),a=await zy(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await ja(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function ja(t,e,n){try{return await mt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function zy(t){try{let{stdout:e}=await mt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
54
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import Xy from"node:fs/promises";import Yy from"node:os";import Ju from"node:path";var ko=t=>new Promise(e=>setTimeout(e,t)),Zy=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,Qy=300,Zu=[{alias:"default",label:"Default"},{alias:"opus",label:"Opus 4.8"},{alias:"sonnet",label:"Sonnet 4.6"},{alias:"haiku",label:"Haiku 4.5"},{alias:"fable",label:"Fable 5"},{alias:"opusplan",label:"Opus Plan"}],tb=new Set([...Zu.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),eb=[{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.4",label:"GPT-5.4"},{alias:"gpt-5.4-mini",label:"GPT-5.4 Mini"},{alias:"gpt-5.3-codex",label:"Codex 5.3"},{alias:"gpt-5.2",label:"GPT-5.2"}],Qu=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),td=[{alias:"auto",label:"Auto"},{alias:"composer-2.5",label:"Composer 2.5"},{alias:"claude-opus-4-8",label:"Opus 4.8"},{alias:"claude-fable-5",label:"Fable 5"},{alias:"claude-sonnet-5",label:"Sonnet 5"},{alias:"claude-sonnet-4-6",label:"Sonnet 4.6"},{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.3-codex",label:"Codex 5.3"}];function ed(t){return t==="codex"?eb:t==="cursor"?td:Zu}function zu(t){let e=String(t||"").split(`
|
|
55
|
-
`),n=e.findIndex(o=>/Select Model and Effort/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+(\S+?)(?:\s+\((current|default)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),id:s[3],current:s[4]==="current",active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function Xu(t){let e=String(t||"").split(`
|
|
56
|
-
`),n=e.findIndex(o=>/Select Reasoning Level/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+([A-Za-z ]+?)(?:\s+\((default|current)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),label:s[3].trim(),isDefault:!!s[4],active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function nb(t){let e=String(t||"").split(`
|
|
57
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].match(/Model changed to\s+(\S+)(?:\s+(\S+))?\s*$/i);if(r)return{model:r[1],effort:r[2]||null}}return null}function Yu(t){let e=String(t||"").split(`
|
|
58
|
-
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function nd(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!Qu.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=td.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!tb.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function br(t,e,{timeoutMs:n=Zy}={}){let r=Date.now()+n;for(;;){let o=await pt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await ko(Qy)}}async function rb(t,e){return await yt(t,`/model ${e.model}`,ne("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function ob(t,e){await no(t),await yt(t,"/model",{enter:!1}),await ko(250),await gt(t,["Enter"],{kind:"model-switch"});let n=await br(t,zu);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await gt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=nb(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await yt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await br(t,i=>Xu(i)||!zu(i)&&o(i)&&{done:o(i)});if(!s)throw await gt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Qu.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await gt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await yt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await br(t,i=>!Xu(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function sb(){let t=Ju.join(process.env.CURSOR_CONFIG_DIR||Ju.join(Yy.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await Xy.readFile(t,"utf8"))?.model||null}catch{return null}}async function ab(t,e){await no(t),await oo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await br(t,Yu),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await br(t,Yu,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await gt(t,["Down"],{kind:"model-switch"}),await ko(250)}if(!n||!s||!o(s)){await gt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await gt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await gt(t,["Enter"],{kind:"model-switch"}),await ko(800);let a=await sb();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function rd(t,e,n){return t==="codex"?ob(e,n):t==="cursor"?ab(e,n):rb(e,n)}import Wa from"node:fs/promises";import Pn from"node:path";import ib from"node:os";import{EventEmitter as cb}from"node:events";import{randomUUID as lb}from"node:crypto";var ub=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","provider.connected","stall.detected","error"]),db=new Set(["user","agent","orchestrator","system"]),fb=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Nt=new cb;Nt.setMaxListeners(0);var qa=process.env.BOSTRAT_STATE_DIR||Pn.join(process.env.HOME||ib.tmpdir(),".bostrat");function So(t){return t?.cwd?Pn.join(t.cwd,".bostrat","events"):Pn.join(qa,"events")}var od=(t,e)=>Pn.join(t,`${e}.ndjson`),xo=new Map;async function pb(t,e){if(!xo.has(e)){let o=(await B(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);xo.set(e,o)}let n=xo.get(e)+1;return xo.set(e,n),n}function mb(t,e){let n=Array.isArray(t?.epochs)?t.epochs:null;if(!(!n||n.length===0)){if(!Number.isFinite(e))return n.length-1;for(let r=n.length-1;r>=0;r--){let o=n[r].startedAt;if(o==null||o<=e)return r}return 0}}async function R(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!ub.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!db.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!fb.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Pn.join(qa,"events"):So(t),o=e.epoch;if(o==null&&typeof t=="object"&&e.boxId&&Array.isArray(t?.boxes)){let c=t.boxes.find(l=>l.name===e.boxId);o=mb(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await pb(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||lb(),trackId:n,boxId:e.boxId??null,...o!=null?{epoch:o}:{},...a?{userId:a}:{},seq:s,ts:e.ts??Date.now(),type:e.type,actor:e.actor??"system",provider:e.provider??null,source:e.source??"orchestrator",payload:e.payload??{},...e.causationId?{causationId:e.causationId}:{},...e.correlationId?{correlationId:e.correlationId}:{}};return await Wa.mkdir(r,{recursive:!0}),await Wa.appendFile(od(r,n),JSON.stringify(i)+`
|
|
59
|
-
`),Nt.emit("event",i),Nt.emit(`track:${n}`,i),i}async function B(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?Pn.join(qa,"events"):So(t));let o;try{o=await Wa.readFile(od(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
60
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function sd(t,e){let n=`track:${t}`;return Nt.on(n,e),()=>Nt.off(n,e)}var Ct=t=>t||null;function To(t){let e=t||{},n=hb(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function hb(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ct(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ct(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ct(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Ct(t.agent),payload:{...kr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Ct(t.agent||t.toAgent),payload:{kind:"subtask-failed",...kr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{change:"subtask-resumed",...kr(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...kr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...kr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{change:"rehydrated"}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Ct(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var vo=Promise.resolve(),Ka=t=>(vo=vo.then(t).catch(()=>{}),vo),id=()=>vo;function cd(t,e){let n=To(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Ka(()=>R(r,n))}var ad=new Set;async function ld(t){if(!(!t?.id||ad.has(t.id))){ad.add(t.id);try{let e=await B(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=To(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function kr(t){let{type:e,at:n,...r}=t;return r}var Ga=new Map;function ud(t,e,n){!n?.promptId||Ga.get(t.id)===n.promptId||(Ga.set(t.id,n.promptId),Ka(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Ct(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function dd(t,e,n,r={}){Ga.delete(t.id),Ka(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Ct(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as qb}from"node:crypto";var fd=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,pd=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,md=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,wb=new RegExp(`\\b(?:${fd}|${pd}|${md})\\b`,"i"),gb=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,yb=/^(?:commit|push|open|create|raise|ready|ok(?:ay)?\s+to|good\s+to)\b/i,bb=90;function kb(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function hd(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of kb(n).reverse()){if(!wb.test(r))continue;let o=r.endsWith("?")&&r.length<=bb&&yb.test(r);if(gb.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var wd=["commit","push","pr"],xb=new RegExp(`\\b(?:${fd})\\b`,"i"),Sb=new RegExp(`\\b(?:${pd})\\b`,"i"),vb=new RegExp(`\\b(?:${md})\\b`,"i"),Tb=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function gd(t){let e=String(t||""),n=[];xb.test(e)&&n.push("commit"),Sb.test(e)&&n.push("push");let r=Tb.test(e);!r&&vb.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function Ao(t,{branch:e=null,base:n=null,toPr:r=null}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let o=new Set(t),s=e?` on this branch (${e})`:"",a=[];if(o.has("commit")&&a.push(`commit the work${s} with a clear commit message`),o.has("push")||o.has("pr")){let l=r&&!o.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"";a.push(o.has("commit")?`push it to the remote${l}`:`push this branch${s} to the remote${l}`)}o.has("pr")&&a.push(`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let i=[];o.has("commit")||i.push("don't create any new commits \u2014 ship what's already committed"),!o.has("push")&&!o.has("pr")&&i.push("don't push"),o.has("pr")||i.push("don't open a pull request");let c=`Yes \u2014 go ahead: ${a.join(", then ")}.`;return i.length&&(c+=` ${i.join(", and ").replace(/^./,l=>l.toUpperCase())}.`),o.has("pr")&&(c+=" If a PR for this branch is already open, just push the update instead."),c}var Ab=3e4;function yd(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=Ab&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function bd(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}import{execFile as Ib}from"node:child_process";import{promisify as Rb}from"node:util";import _o from"node:fs/promises";import kd from"node:path";import _b from"node:os";var xd=process.env.BOSTRAT_STATE_DIR||kd.join(process.env.HOME||_b.tmpdir(),".bostrat"),Ja=kd.join(xd,"pr-links.json"),bt=(t,e)=>`${t}#${Number(e)}`,Eo=null,Va=null,D=Promise.resolve();async function xt(){return Eo||(Va??=_o.readFile(Ja,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>Eo=t).finally(()=>Va=null),Va)}async function Z(){let t=JSON.stringify({links:Eo},null,2),e=`${Ja}.${process.pid}.tmp`;await _o.mkdir(xd,{recursive:!0}),await _o.writeFile(e,t),await _o.rename(e,Ja)}var Eb=new Set(["api","observed","reconciled"]);async function Bt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await xt(),c=bt(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:Eb.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function xr(t,e){if(!t||e==null)return null;let n=(await xt())[bt(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Tt(t,e){return!t||e==null?null:(await xt())[bt(t,e)]??null}async function Qe(t,e,n){if(!t||e==null)return null;let r=(await xt())[bt(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Sd(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await xt())[bt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function vd(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await xt())[bt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function Io(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await xt())[bt(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function Ro(t,e){if(!t||e==null)return null;let n=(await xt())[bt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function $o(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await xt())[bt(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function Oo(t,e){if(!t||e==null)return null;let n=(await xt())[bt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Td(t,e,n){if(!t||e==null||!n)return null;let r=(await xt())[bt(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Co(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await xt())[bt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function za(t,e,n=!0){if(!t||e==null)return null;let r=(await xt())[bt(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Mo(t,e){if(!t||e==null)return null;let n=(await xt())[bt(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Ad(){return Object.values(await xt()).filter(t=>t.state!=="merged"&&t.state!=="closed")}async function Xa(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await pt(t,{lines:$t}).catch(()=>"");if(!dr(e,r))return Tl(e,r)?{working:!0,prompted:!1}:n;if(Cn(e,r)){let o=await me(t).catch(()=>r);if(Xe({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Po(t,e){return(await Xa(t,e)).working}var _d=Rb(Ib),Ed=[4e3,15e3,45e3],$b=2500,Ob=5*6e4;async function Ya(t,e,{midTurn:n=Po,pollMs:r=$b,capMs:o=Ob}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var Cb=6e4,Mb=9e4,Pb=2e3,Nb=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,Bb=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,Db=/\s--web\b/,Id=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Za(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function Rd(t){let e=String(t||""),n=e.match(Nb);if(!n)return null;let r=Za(e,n.index);return Db.test(r)?null:{repo:r.match(Id)?.[1]??null}}var jb=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function $d(t){let e=String(t||""),n=e.match(Bb);if(!n)return null;let r=Za(e,n.index),o=Za(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){jb.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Id)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var Lb="number,url,title,headRefName,createdAt";async function Ub(t,e){let n=async s=>{let{stdout:a}=await _d("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",Lb],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...s}});return JSON.parse(a||"[]")},r=await Gt({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-Cb).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function Fb(t,e){if(e)return[e];let n=[],r=await Y(t.workspace?.base||t.cwd).catch(()=>null);r&&n.push(r);for(let o of await re().catch(()=>[])){let s=await Y(o.path).catch(()=>null);s&&!n.includes(s)&&n.push(s)}return n}async function Hb(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await Fb(t,e)){let a;try{a=await Ub(s,n)}catch{continue}for(let i of a){let c=await Tt(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var ke=new Set;function Od(t,e,{delays:n=Ed,turnEnd:r}={}){let o=Rd(e.command);if(!o)return null;let s=!!$d(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(ke.has(a))return null;ke.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(g=>setTimeout(g,l));let u=await Hb(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:w,created:m}=await Bt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!m&&!(w?.trackId===t.id&&await xr(d,f.number))?null:(await Ya(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:f.number,url:f.url,title:f.title,branch:f.headRefName,repo:d,...s?{selfMerge:!0}:{}}}),f)}return null}finally{ke.delete(a)}})();return c.catch(()=>ke.delete(a)),c}var Wb="number,url,title,headRefName,state";function Cd(t,e,{delays:n=Ed,turnEnd:r,openedWait:o}={}){let s=$d(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(ke.has(a))return null;ke.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);if(u&&([,c,l]=u),c||=await Y(t.workspace?.base||t.cwd).catch(()=>null),l||=t.workspace?.branch,!c||!l)return null;let d=async(w,m)=>{let g=await Tt(c,w.number);return g&&g.trackId!==t.id?null:(g||await Bt({repo:c,number:w.number,trackId:t.id,url:w.url,headRef:w.headRefName,title:w.title,source:"merge-observed"}),await Co(c,w.number),m&&((await B(t).catch(()=>[])).some(S=>S.type==="pr.state.changed"&&(S.payload?.change==="auto-armed"||S.payload?.change==="merged")&&S.payload?.number===w.number)||(await Ya(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:w.number,url:w.url,title:w.title,branch:w.headRefName,repo:c}}))),w)},f=null;for(let w of n){await new Promise(T=>setTimeout(T,w));let m;try{let{stdout:T}=await _d("gh",["pr","view",String(l),"--repo",c,"--json",Wb],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await Gt({slug:c})}});m=JSON.parse(T||"null")}catch{continue}if(!m?.number)continue;if(m.state==="CLOSED")return null;if(m.state!=="MERGED"){if(f=m,s.auto)return d(m,!0);continue}let g=await Tt(c,m.number);if(g&&g.trackId!==t.id)return null;g||await Bt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Ya(e.box,e.agent,r);let x=await B(t).catch(()=>[]);if(x.some(T=>T.type==="pr.state.changed"&&T.payload?.change==="merged"&&T.payload?.number===m.number))return null;let A=T=>T.some(E=>E.type==="pr.state.changed"&&E.payload?.change==="opened"&&E.payload?.number===m.number),S=0;if(!A(x)){if(Rd(e.command)&&await xr(c,m.number)){let T=Date.now();S=T+1,await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:T,change:"opened",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c,selfMerge:!0}})}else if((await Tt(c,m.number))?.announced){let T=o?.capMs??Mb,E=o?.pollMs??Pb,J=Date.now()+T;for(;Date.now()<J&&(await new Promise(M=>setTimeout(M,E)),!A(await B(t).catch(()=>[]))););}}return await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),S),change:"merged",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}),await tn(c,m.number).catch(()=>{}),await Qe(c,m.number,"merged"),m}return f?d(f,!1):null}finally{ke.delete(a)}})();return i.catch(()=>ke.delete(a)),i}function ti(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function Md(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Pd(t){return`job|${t.box}|id:${t.id}`}var Qa=new Map,No=new Map;async function ei(t){return Qa.has(t.id)?Qa.get(t.id):(No.has(t.id)||No.set(t.id,B(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(ti({role:o,sourceId:r.payload.sourceId})),n.keys.add(ti({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(Md({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(Pd({id:r.payload.id,box:r.boxId}));return Qa.set(t.id,n),n}).finally(()=>No.delete(t.id))),No.get(t.id))}async function Nd(t,e){try{let n=(await ei(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=ti(r);if(!n.has(o)&&(n.add(o),await R(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=hd(r.text);if(s){let{offered:a,toPr:i}=gd(s);await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:qb(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0}})}}}}catch{}}async function Bd(t,e){try{let n=await ei(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Md(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await R(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),Od(t,o),Cd(t,o))}}catch{}}async function Dd(t,e){try{let n=await ei(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Pd(o);n.keys.has(s)||(n.keys.add(s),await R(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var Gb=/^Base directory for this skill:\s*(\S+)/,Kb=/^Run the "([^"]+)" workflow\./,Vb=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,Jb=/^<local-command-(?:stdout|caveat)>/i,zb=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,Xb=/^<task-notification>/i,Yb=/<summary>([\s\S]*?)<\/summary>/i,Zb=/<task-id>([\s\S]*?)<\/task-id>/i,Qb=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,tk=/<status>([\s\S]*?)<\/status>/i,ek=/<result>([\s\S]*?)(?:<\/result>|$)/i;var ni=new Set(["model","clear","compact","login"]);function jd(t){return ni.has(String(t||"").replace(/^\//,"").toLowerCase())}var nk=/^\/([a-z0-9:_-]+)$/i;function Bo(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(Gb);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(Kb);if(r)return{kind:"skill",name:r[1]};let o=e.match(Vb);if(o){let a=o[1].toLowerCase();return ni.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(nk);if(s&&ni.has(s[1].toLowerCase()))return{kind:"suppress"};if(Jb.test(e))return{kind:"suppress"};if(zb.test(e))return{kind:"suppress"};if(Xb.test(e)){let a=c=>rk(e.match(c)?.[1].trim()||"")||null,i=a(ek);return{kind:"task",summary:a(Yb)||"",taskId:a(Zb),toolUseId:a(Qb),status:a(tk)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function rk(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Ld(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function Ud(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var at=t=>t.payload?.ts??t.ts;function Do(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,d=new Map;for(let h of t||[]){if(h.type!=="tool.called"||h.source!=="transcript")continue;let p=String(h.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let b of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let v=b[0].split("/").pop().toLowerCase();d.has(v)||d.set(v,[]),d.get(v).push(h.payload?.ts??h.ts)}}let f=3*6e4,w=20*6e4,m=(h,p)=>!!(h?.name&&d.get(String(h.name).toLowerCase()))?.some(v=>v>=p-f&&v<=p+w);for(let h of t||[])if(h.type==="turn.retracted"){r.add(`${h.boxId}|${h.payload?.saidTs}`);let p=Ye(h.payload?.text);p&&(o.has(h.boxId)||o.set(h.boxId,[]),o.get(h.boxId).push(p))}else if(h.type==="turn.user"&&h.source==="orchestrator"){let p=Ye(h.payload?.text);p&&(s.has(h.boxId)||s.set(h.boxId,new Set),s.get(h.boxId).add(p))}else if(h.type==="job.started"&&h.payload?.id)a.add(h.payload.id),(h.payload.kind||"agent")==="agent"&&h.payload.name&&c.add(h.payload.name);else if(h.type==="turn.user"&&h.payload?.note?.kind==="task"){let p=h.payload.note,b={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:at(h)};p.toolUseId&&i.set(p.toolUseId,b);let v=String(p.summary||"").match(u);v&&l.set(v[1],b)}let g=new Map,x=new Map,A=new Map,S=new Map,T=new Map,E=new Map,J=new Map,M=new Map,I=(h,p)=>{let b=o.get(h);if(!b?.length)return!1;let v=Ye(p),ot=s.get(h);return b.some($=>{if(!v.startsWith($))return!1;let z=Ye(v.slice($.length));return!!z&&!!ot?.has(z)})};for(let h of t||[]){let p=h.payload||{};switch(h.type){case"turn.user":{if(h.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let b=p.note||Bo(p.text);if(b){if(b.kind==="task"){if(b.toolUseId&&a.has(b.toolUseId))break;let v=String(b.summary||"").match(u);if(!b.toolUseId&&v&&c.has(v[1]))break}if(b.kind==="command"&&jd(b.command))break;b.kind!=="suppress"&&e.push({id:h.id,kind:"event",type:Ud(b),name:b.name??null,command:b.command??null,summary:b.summary??null,ts:at(h),box:h.boxId,agent:h.provider});break}}if(r.has(`${h.boxId}|${at(h)}`)||h.source==="transcript"&&I(h.boxId,p.text)||p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view")break;for(let b of g.values())b.status="stale";g.clear(),e.push({id:h.id,kind:"message",role:"user",text:p.text,ts:at(h),box:h.boxId,agent:h.provider,boxRole:p.boxRole,...h.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{J.clear();let b=(p.images||[]).filter(ot=>!m(ot,at(h)));if(!String(p.text||"").trim()&&!b.length)break;if(h.provider==="cursor"){let ot=`${h.boxId}|${p.text}`;if(n.has(ot))break;n.add(ot)}let v=h.provider==="codex"?(p.tools||[]).filter(ot=>!va.has(ot)&&!gr.has(ot)):p.tools||[];e.push({id:h.id,kind:"message",role:"assistant",text:p.text,ts:at(h),box:h.boxId,agent:h.provider,boxRole:p.boxRole,...v.length?{tools:v}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...b.length?{images:b}:{}});break}case"job.started":{if(!p.id)break;let b=at(h),v=i.get(p.id);if(!v&&(p.kind||"agent")==="agent"&&p.name){let N=l.get(p.name);N&&N.ts>=b&&(v=N)}let ot={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:v?v.status&&v.status!=="completed"?"failed":"done":"running",startedTs:b,finishedTs:v?.ts??null,summary:v?.summary??null,resultHead:v?.resultHead??null},$=M.get(h.boxId);if($&&$.jobs.some(N=>(N.finishedTs??1/0)>=b))$.jobs.push(ot);else{let N={id:h.id,kind:"job-dock",dockId:p.id,ts:b,box:h.boxId,agent:h.provider,jobs:[ot]};M.set(h.boxId,N),e.push(N)}break}case"handoff.done":e.push(xe(h,"lead-handoff"));break;case"subtask.delegated":e.push({...xe(h,"subtask-delegated"),to:h.boxId,toAgent:h.provider});break;case"subtask.merged":e.push(xe(h,"subtask-merged"));break;case"pr.state.changed":if(!p.change)break;if(p.change==="opened"){for(let b of g.values())b.status="stale";g.clear()}e.push(xe(h,`pr-${p.change}`));break;case"pr.offered":{if(!p.offerId)break;for(let v of g.values())v.status="stale";g.clear();let b={id:h.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,status:"open",ts:at(h),box:h.boxId,agent:h.provider};g.set(p.offerId,b),x.set(p.offerId,b),e.push(b);break}case"pr.offer.answered":{let b=x.get(p.offerId);b&&(b.status=p.action||"dismiss",b.actions=Array.isArray(p.actions)?p.actions:null,g.delete(p.offerId));break}case"box.state.changed":{let b=p.change==="model"?"model-change":p.change;if(!b)break;let v=xe(h,b);b==="model-change"&&(v.to=p.model),e.push(v);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:h.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:at(h),box:h.boxId,agent:h.provider});break}case"view.spinup.failed":{e.push({id:h.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:at(h),box:h.boxId,agent:h.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:h.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:at(h),box:h.boxId,agent:h.provider});break}if(p.change!=="created")break;e.push({id:h.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:at(h),box:h.boxId,agent:h.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:h.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:at(h),box:h.boxId,agent:h.provider});break}case"ticket.linked":{e.push({id:h.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:at(h),box:h.boxId,agent:h.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let v of A.values())v.key===p.key&&(v.status="stale",A.delete(v.offerId));let b={id:h.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:at(h),box:h.boxId,agent:h.provider};A.set(p.offerId,b),S.set(p.offerId,b),e.push(b);break}case"ticket.move.answered":{let b=S.get(p.offerId);b&&(b.status=p.action==="moved"?"moved":"dismiss",A.delete(p.offerId));break}case"ticket.state.changed":{for(let b of A.values())b.key===p.key&&b.to===p.to&&(b.status="stale",A.delete(b.offerId));if(p.mode==="offer")break;e.push(xe(h,"ticket-state"));break}case"ticket.commented":{e.push({id:h.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:at(h),box:h.boxId,agent:h.provider});break}case"ticket.flow.changed":e.push(xe(h,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let v of T.values())v.status="stale";T.clear();let b={id:h.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:at(h),box:h.boxId,agent:h.provider};T.set(p.proposalId,b),E.set(p.proposalId,b),e.push(b);break}case"ticket.create.answered":{let b=E.get(p.proposalId);b&&(b.status=p.action==="created"?"created":"dismiss",b.createdCount=p.createdCount??null,b.failedCount=p.failedCount??null,T.delete(p.proposalId));break}case"ticket.created":{e.push({id:h.id,kind:"ticket-created",items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null,ts:at(h),box:h.boxId,agent:h.provider});break}case"error":{if(!p.kind)break;e.push(xe(h,p.kind));break}case"provider.connected":{e.push({id:h.id,kind:"event",type:"provider-connected",provider:p.provider||h.provider,ts:at(h),at:at(h),box:h.boxId,agent:h.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let b=p.groupId||p.promptId;if(!b)break;let v=J.get(b);v||(v={id:h.id,kind:"prompt",groupId:b,provider:h.provider,box:h.boxId,agent:h.provider,questionTotal:p.questionTotal??1,questions:[],ts:at(h)},J.set(b,v));let ot=p.questionIndex??0,$={index:ot,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},z=v.questions.findIndex(W=>W.index===ot);z>=0?v.questions[z]=$:v.questions.push($),v.questions.sort((W,_)=>W.index-_.index),v.questionTotal=Math.max(v.questionTotal,p.questionTotal??1),new Set(v.questions.map(W=>W.index)).size>=v.questionTotal&&(e.push(v),J.delete(b));break}default:break}}return sk(e.sort((h,p)=>(h.ts??0)-(p.ts??0)))}var ok=2*6e4;function sk(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=ok){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function ri(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:ak(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??Do(t)).filter(o=>o.kind==="message");return tu(n,r)}function ak(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==In(t.command)?e:ye(t.command,t.description||"")}function xe(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:at(t),ts:at(t)}}var oi={"claude-opus-4-8":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"claude-sonnet-5":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-sonnet-4-6":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-haiku-4-5":{input:1,output:5,cacheWrite:1.25,cacheRead:.1},"claude-fable-5":{input:10,output:50,cacheWrite:12.5,cacheRead:1},"gpt-5.5":{input:5,output:30,cacheWrite:5,cacheRead:.5},"gpt-5.6-sol":{input:5,output:30,cacheWrite:6.25,cacheRead:.5},"gpt-5.6-terra":{input:2.5,output:15,cacheWrite:3.125,cacheRead:.25},"gpt-5.6-luna":{input:1,output:6,cacheWrite:1.25,cacheRead:.1}},Fd={opus:"claude-opus-4-8",opusplan:"claude-opus-4-8",default:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"};function Hd(t){if(!t)return null;let e=String(t).trim();return oi[e]?e:Fd[e]?Fd[e]:Object.keys(oi).find(r=>e.startsWith(r))||null}var jo=t=>Hd(t)!==null;function Kt(t,e={}){let n=oi[Hd(t)];return n?((e.input||0)*n.input+(e.output||0)*n.output+(e.cacheWrite||0)*n.cacheWrite+(e.cacheRead||0)*n.cacheRead)/1e6:0}function ik(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
61
|
-
`)){let o=r.trim();if(!o||o[0]!=="{")continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s.payload||{};if(s.type==="turn_context"&&a.model&&(n=a.model),s.type==="event_msg"&&a.type==="token_count"&&a.info?.last_token_usage){let i=a.info.last_token_usage;e.push({ts:s.timestamp?Date.parse(s.timestamp):null,model:n,last:{input:i.input_tokens||0,cachedInput:i.cached_input_tokens||0,output:i.output_tokens||0,reasoningOutput:i.reasoning_output_tokens||0},occupancy:i.total_tokens??null,contextWindow:a.info.model_context_window??null})}}return e}var si=new Map;async function ck(t,e){let n=`${t.id}:${e.name}`;if(!si.has(n)){let r=await B(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.codexTs!=null&&o.add(s.payload.codexTs);si.set(n,o)}return si.get(n)}async function Wd(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Ge(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await ck(t,e),o=ik(await vt(n.file,262144)),s=o[o.length-1];for(let a of o){if(a.ts==null||r.has(a.ts))continue;r.add(a.ts);let i={input:Math.max(0,a.last.input-a.last.cachedInput),output:a.last.output,cacheRead:a.last.cachedInput,cacheWrite:0},c=a.model||e.model||"gpt-5.5";await R(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:Kt(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await R(t,{type:"context.usage",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{occupancy:a.occupancy,window:a.contextWindow,pct:a.occupancy!=null?a.occupancy/a.contextWindow:null,model:a.model||null,ts:a.ts}})}}catch{}}import Lo from"node:fs";import ii from"node:path";import lk from"node:os";var uk=process.env.BOSTRAT_STATE_DIR||ii.join(process.env.HOME||lk.tmpdir(),".bostrat"),Gd=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,dk="https://cursor.com/api/dashboard/get-filtered-usage-events",qd=100;async function fk({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(dk,{method:"POST",headers:{"content-type":"application/json",origin:"https://cursor.com",cookie:`WorkosCursorSessionToken=${e}`},body:JSON.stringify({teamId:0,startDate:String(t),endDate:String(Date.now()),page:o,pageSize:qd})});if(!s.ok)throw new Error(`cursor usage feed: HTTP ${s.status}`);let a=await s.json(),i=a?.usageEventsDisplay||a?.usageEvents||[];if(r.push(...i),i.length<qd)break}return r}function pk(t){if(!t)return null;let e=Number(t.timestamp)||null,n=t.tokenUsage||null,r=n?{input:n.inputTokens||0,output:n.outputTokens||0,cacheRead:n.cacheReadTokens||0,cacheWrite:n.cacheWriteTokens||0}:null,o=t.conversationId||null;return{id:[o||"?",e??"?",r?`${r.input}.${r.output}.${r.cacheRead}.${r.cacheWrite}`:"?"].join(":"),conversationId:o,ts:e,model:t.model||t.modelIntent||null,kind:t.kindLabel||t.kind||null,isHeadless:t.isHeadless===!0,tokens:r,vendorCents:n?.totalCents??null,chargedCents:t.chargedCents??null}}var mk=5*6e4,Se={at:0,rows:null,error:null},Uo=()=>ii.join(uk,"cursor-usage.json");function Kd(){try{return JSON.parse(Lo.readFileSync(Uo(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function hk(t){try{Lo.mkdirSync(ii.dirname(Uo()),{recursive:!0});let e=Uo()+".tmp";Lo.writeFileSync(e,JSON.stringify(t)),Lo.renameSync(e,Uo())}catch{}}var wk=5e3;async function gk({fetchImpl:t,now:e=Date.now()}={}){if(Se.rows&&e-Se.at<mk)return Se.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Kd(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await fk({sinceMs:o,cookie:n,fetchImpl:t});Se={at:e,rows:s.map(pk).filter(Boolean),error:null}}catch(s){Se={at:e,rows:null,error:String(s?.message||s)}}return Se.rows}var Vd=()=>({enabled:Gd(),lastPollAt:Se.at||null,error:Se.error}),ai=new Map;async function yk(t,e){let n=`${t.id}:${e.name}`;if(!ai.has(n)){let r=await B(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.cursorEventId&&o.add(s.payload.cursorEventId);ai.set(n,o)}return ai.get(n)}async function Jd(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Gd())return;let r=await gk({fetchImpl:n});if(!r||!r.length)return;let o=await lu(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await yk(t,e),a=Kd(),i=new Set(a.seen||[]),c=a.sinceMs||0;for(let l of r){if(!l.conversationId||l.conversationId!==o.chatId||s.has(l.id)||i.has(`${t.id}:${e.name}:${l.id}`))continue;s.add(l.id),i.add(`${t.id}:${e.name}:${l.id}`),l.ts&&l.ts>c&&(c=l.ts);let u=l.model||o.models[o.models.length-1]||"cursor-unknown",d=l.tokens||null,f=l.vendorCents!=null?l.vendorCents/100:0;!f&&d&&(f=Kt(u,d)),!f&&l.chargedCents!=null&&(f=l.chargedCents/100),await R(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:f,model:u,...d?{tokens:d}:{},...l.vendorCents!=null?{vendorCents:l.vendorCents}:{},...l.chargedCents!=null?{chargedCents:l.chargedCents}:{},...l.kind?{kind:l.kind}:{},conversationId:l.conversationId,cursorEventId:l.id,ts:l.ts??void 0}})}hk({sinceMs:c,seen:[...i].slice(-wk)})}catch{}}import vr from"node:fs/promises";import At from"node:path";import bk from"node:os";import kk from"node:net";import{randomUUID as xk}from"node:crypto";import{execFile as Sk}from"node:child_process";import{promisify as vk}from"node:util";var Jt=vk(Sk),Tk=process.env.BOSTRAT_STATE_DIR||At.join(process.env.HOME||bk.tmpdir(),".bostrat"),Qd=At.join(Tk,"views.json"),[zd,Xd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),ve=Number(process.env.BOSTRAT_VIEW_MAX)||3,Ak="bostrat-view-",Nn=t=>`${Ak}${t}`,Yd=new Set(["bostrat","www","api","views","app"]),Fo=t=>Object.assign(new Error(t),{status:400}),_k=new Set(["npm","pnpm","yarn","bun"]),ui="package.json",Ek=/[|;&`]|\$\(/,Ik=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Rk=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,tf=t=>String(t).trim().split(/\s+/).filter(Boolean),$k=/^[A-Za-z_][A-Za-z0-9_]*=/;function Ok(t){for(let e of tf(t))if(!$k.test(e))return e;return null}function ef(t){let e=tf(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function Ck(t,e){let n=ef(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return At.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return At.resolve(t,n[r+1])}return t}var Mk=t=>vr.stat(t).then(e=>e.isDirectory(),()=>!1),nf=t=>vr.stat(t).then(e=>e.isFile(),()=>!1),Ho=t=>vr.realpath(t).catch(()=>At.resolve(t));async function Pk(t,e){if(At.resolve(t)===At.resolve(e))return!0;let[n,r]=await Promise.all([Ho(t),Ho(e)]);return n===r}async function Nk(t){let n=(await vr.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await nf(At.join(t,o.name,ui))?`./${o.name}`:null))).filter(Boolean)}async function Bk(t,e){let[n,r]=await Promise.all([Ho(t),Ho(e)]),o=At.relative(n,r);if(o&&(o.startsWith("..")||At.isAbsolute(o)))throw Fo(`serve directory escapes the Track workspace: ${e}`)}async function Dk(t,e){if(!await Mk(t))throw Fo(`serve directory does not exist: ${t}`);let n=Ok(e);if(!n)return;if(!n.includes("/")&&!await Ls(n,{loginShell:!0,failOpen:!0}))throw Fo(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Ek.test(e)||Ik.has(n)||!_k.has(n)||!ef(e).includes("run")||Rk.test(e))return;let r=Ck(t,e);if(await nf(At.join(r,ui)))return;let o=await Nk(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Fo(`no ${ui} in ${r}${s}`)}var Vt=null,ci=null;async function ae(){return Vt||(ci||=(async()=>{try{Vt=JSON.parse(await vr.readFile(Qd,"utf8"))}catch{Vt={views:[],adoptWatermarks:{}}}finally{ci=null}return Vt.views||=[],Vt.adoptWatermarks||={},Vt})(),ci)}async function zt(){await qt(Qd,Vt)}function se(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Sr=new Map,pi=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,rf=12e3;function mi(t){return Sr.get(t)||null}function of(t,{name:e,box:n}={}){let r=Sr.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return Sr.set(t,o),o}function di(t,e){let n=Sr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Wo(t,{error:e=null}={}){let n=Sr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function sf(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function fi(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function jk(t,e){let n=fi(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!Yd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!Yd.has(s))return s}}function Tr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=kk.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function hi(t){if(process.platform!=="darwin"){await Jt("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await Jt("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}async function Lk(t,e){let n=new Set(t.views.map(r=>r.port));if(e){let r=Number(e);if(!Number.isInteger(r)||r<1024||r>65535)throw new Error("invalid port");if(n.has(r))throw new Error(`port ${r} is already owned by a view`);return r}for(let r=zd;r<=Xd;r++)if(!n.has(r)&&!await Tr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${zd}-${Xd}`)}async function Uk(t){try{let{stdout:e}=await Jt(O,C("list-sessions","-F","#{session_name}"));return e.split(`
|
|
62
|
-
`).some(n=>n.trim()===Nn(t))}catch{return!1}}async function Ar(t){try{await Jt(O,C("kill-session","-t",Nn(t)))}catch{}}async function wi(t){if(!await ir())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Nn(t.slug),n=t.serveCwd??t.cwd,r=At.dirname(process.execPath);await Jt(O,C("new-session","-d","-s",e,"-c",n)),await Jt(O,C("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await Jt(O,C("send-keys","-t",e,"Enter")),await Jt(O,C("send-keys","-t",e,"-l",t.cmd)),await Jt(O,C("send-keys","-t",e,"Enter"))}async function Zd(t,e=12){try{let{stdout:n}=await Jt(O,C("capture-pane","-t",Nn(t),"-p"));return String(n||"").trimEnd().split(`
|
|
13
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${On}: ${e?.message||e}`)}}async function G(t={}){let e={at:new Date().toISOString(),ts:Date.now(),...t},n=[e.flow||"handoff",e.phase||e.step||"?",e.trackId?`track=${e.trackId}`:"",e.sessionName?`box=${e.sessionName}`:"",e.error?`ERR=${e.error}`:""].filter(Boolean).join(" \xB7 ");if(console.log(`[handoff-debug] ${n}`),e.sessionName&&Oo())try{e.paneTail=(await ht(e.sessionName,{lines:35})).slice(-4e3)}catch{e.paneTail=null}(Oo()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await Eg(e)}async function Vl({limit:t=80}={}){try{return(await _r.readFile(On,"utf8")).split(`
|
|
14
|
+
`).filter(Boolean).slice(-t).map(r=>{try{return JSON.parse(r)}catch{return{raw:r}}})}catch{return[]}}var Jl={DEBUG_DIR:ba,LOG_FILE:On};var rt=Rg(Ig);function Er(t){return new Promise(e=>setTimeout(e,t))}function xa(t,e){if(!e)return t;let n=o=>typeof o=="string"?o.split(e).join("<redacted>"):o,r=new Error(n(t?.message)||"command failed");return t?.code!==void 0&&(r.code=t.code),t?.stderr!==void 0&&(r.stderr=n(t.stderr)),r}var P=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",Ct=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",Og=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",Sa=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",va="bostrat-usage-probe",Cg=new Set([va]);function Aa(t,e){let n=Cn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Cn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function Mn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function Ta(t,{loginShell:e=!1,failOpen:n=!1}={}){if(!t)return!1;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await rt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function _a(t){return Ta(String(t||"").trim().split(/\s+/)[0])}function N(...t){return["-S",Ct,...t]}async function xe(){if(!ka.existsSync(Ct))return!1;try{return await rt(P,N("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function Ir(){if(await xe())return!0;try{ka.mkdirSync(Ct.replace(/\/[^/]+$/,""),{recursive:!0}),await rt(P,N("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await xe()}var Mg=/^[A-Za-z0-9_.-]{1,64}$/;function pt(t){return typeof t=="string"&&Mg.test(t)}async function Pn(){if(!ka.existsSync(Ct))return[];try{let t="#{session_name}|#{session_windows}|#{session_attached}|#{session_activity}|#{pane_current_path}|#{@bostrat-session}|#{@bostrat-title}|#{@bostrat-sid}|#{@bostrat-agent}",{stdout:e}=await rt(P,N("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
15
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,d,f]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:d||null,agent:f||(l==="1"?"claude":null)}}).filter(r=>!Cg.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function z(t){return(await Pn()).find(n=>n.name===t)||null}var Pg=/^[A-Za-z0-9 ._↻-]+$/,Ng=64;function Bg(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=Ng&&Pg.test(e)}function Ea(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Dg(t){try{let{stdout:e}=await rt(P,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
16
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function tn(t,e=Og,n={}){if(!Bg(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Ea(r);if(!pt(o))throw new Error("resulting tmux session name is invalid");if(!await Ir())throw new Error(`host tmux server not running on ${Ct} and auto-start failed \u2014 start one: ${P} -S ${Ct} new-session -d -s keeper "sleep 2147483647"`);if(await Dg(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||Sa,a=n.agent||Mn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||$g(),l=i?s:Aa(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),d=a==="claude"&&Cn(s),f=n.firstRun===!0;f||(d&&await ke(e),a==="codex"&&await Rn(e),a==="cursor"&&await $n(e));let p=d&&!f?Ro():null,h=p?["-e",`ANTHROPIC_API_KEY=${p}`]:[];try{await rt(P,N("new-session","-d","-s",o,"-c",e,...h))}catch(m){throw p?xa(m,p):m}return await en(o),await rt(P,N("set-option","-t",o,"@bostrat-session","1")),await rt(P,N("set-option","-t",o,"@bostrat-title",r)),await rt(P,N("set-option","-t",o,"@bostrat-sid",c)),await rt(P,N("set-option","-t",o,"@bostrat-agent",a)),await rt(P,N("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u}}async function Vt(t){if(!pt(t))throw new Error("invalid session name");await rt(P,N("kill-session","-t",t))}async function zl(t,e){if(pt(t))try{let n=e?N("set-option","-t",t,"@bostrat-sid",e):N("set-option","-u","-t",t,"@bostrat-sid");await rt(P,n)}catch{}}async function ht(t,{lines:e=40}={}){if(!pt(t))return"";try{let n=N("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await rt(P,n);return String(r||"")}catch{return""}}var jg=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,Lg=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,At=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,Fg=Number(process.env.BOSTRAT_BOX_COLS)||120,Ug=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Xl(t){if(!pt(t))return 0;try{let{stdout:e}=await rt(P,N("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
17
|
+
`).filter(Boolean).length}catch{return 0}}async function en(t,{cols:e=Fg,rows:n=Ug}={}){if(pt(t)&&!await Xl(t))try{let r=await Zl(t);if(r.cols===e&&r.rows===n)return;await rt(P,N("resize-window","-t",t,"-x",String(e),"-y",String(n))),await rt(P,N("set-window-option","-t",t,"window-size","latest")),await Er(80)}catch{}}async function Yl(t){if(!pt(t))return 0;try{let{stdout:e}=await rt(P,N("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function Zl(t){if(!pt(t))return{cols:0,rows:0};try{let{stdout:e}=await rt(P,N("display-message","-t",t,"-p","#{window_width} #{window_height}")),[n,r]=String(e||"").trim().split(/\s+/).map(o=>Number(o)||0);return{cols:n,rows:r}}catch{return{cols:0,rows:0}}}async function Co(t,{cols:e=jg,rows:n=Lg}={}){if(pt(t)&&!await Xl(t))try{let r=await Zl(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await rt(P,N("resize-window","-t",t,"-x",String(o),"-y",String(s))),await rt(P,N("set-window-option","-t",t,"window-size","latest")),await Er(80))}catch{}}async function Se(t){return await Co(t),ht(t,{lines:At})}var Hg=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function bt(t,e,{settleMs:n=40,kind:r}={}){if(!pt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>Hg.has(s));for(let s=0;s<o.length;s++)await rt(P,N("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await Er(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Mo(t,e,{kind:n}={}){if(!pt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await rt(P,N("send-keys","-t",t,"-N",String(r),"BSpace")),G({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function Po(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!pt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await rt(P,N("send-keys","-t",t,"-l",s)),n>0&&await Er(n);G({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function kt(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!pt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await rt(P,N("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await Er(s),await rt(P,N("send-keys","-t",t,"Enter"))}G({flow:"tmux",phase:"send",sessionName:t,kind:o||"text",textLen:typeof e=="string"?e.length:0,textHead:typeof e=="string"?e.slice(0,120):"",enter:n,submitDelayMs:r}).catch(()=>{})}import Wg from"node-pty";async function tu(t,e,n={}){if(!pt(e)){No(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await xe()){No(t,`host tmux server not running on ${Ct}`),t.close(1011,"tmux server unavailable");return}if(!await z(e)){No(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Ql(n.cols,120,eu),o=Ql(n.rows,32,nu),s;try{s=Wg.spawn(P,["-S",Ct,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){No(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Ia(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Ia(t,{type:"exit",code:a??null,signal:i??null}),t.close(1e3,"tmux exited"))}),t.on("message",a=>{let i;try{i=JSON.parse(a.toString())}catch{return}if(!(!i||typeof i!="object")){if(i.type==="data"&&typeof i.data=="string")s.write(i.data);else if(i.type==="resize"&&qg(i.cols,i.rows))try{s.resize(Math.min(500,i.cols|0),Math.min(500,i.rows|0))}catch{}}}),t.on("close",()=>{try{s.kill()}catch{}en(e).catch(()=>{})})}var eu=20,nu=6;function qg(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=eu&&Number(e)>=nu}function Ql(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Ia(t,e){try{t.send(JSON.stringify(e))}catch{}}function No(t,e){Ia(t,{type:"error",message:e})}function ru(t){let e=typeof t=="string"&&t?t:"/",n=e.indexOf("?"),r=n===-1?e:e.slice(0,n),o=n===-1?"":e.slice(n+1),s=r.match(/^\/api\/attach\/([^/]+)$/);if(!s)return{ok:!1,status:404};let a;try{a=decodeURIComponent(s[1])}catch{return{ok:!1,status:400}}let i=new URLSearchParams(o);return{ok:!0,sessionName:a,cols:Number(i.get("cols"))||void 0,rows:Number(i.get("rows"))||void 0}}import Rr from"node:fs";import Gg from"node:fs/promises";import Nn from"node:path";import Kg from"node:os";var ou={"claude-opus-4-8":1e6,"claude-opus-4-7":1e6,"claude-opus-4-6":1e6,"claude-sonnet-4-6":1e6,"claude-haiku-4-5":2e5,"claude-fable-5":1e6},Vg=2e5,Ra=process.env.BOSTRAT_CLAUDE_PROJECTS||Nn.join(Kg.homedir(),".claude","projects");function $a(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Oa(t){let e=Nn.join(Ra,$a(t)),n;try{n=Rr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Nn.join(e,o);try{let{mtimeMs:a}=Rr.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function ie(t,e){if(!e)return null;let n=Nn.join(Ra,$a(t),`${e}.jsonl`);return Rr.existsSync(n)?n:null}function su(t){if(!t)return[];let e=Nn.join(Ra,$a(t)),n;try{n=Rr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Rr.statSync(Nn.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Tt(t,e=131072){let n=await Gg.open(t,"r");try{let{size:r}=await n.stat(),o=Math.max(0,r-e),s=r-o;if(s===0)return"";let a=Buffer.alloc(s);return await n.read(a,0,s,o),a.toString("utf8")}finally{await n.close()}}function Jg(t){let e=t.split(`
|
|
18
|
+
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function $r(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=ie(t,e),o=!0,!r)return null}else if(n){if(r=Oa(t),o=!1,!r)return null}else return null;let s;try{s=Jg(await Tt(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(ou,s.model),i=ou[s.model]||Vg;return{source:"transcript",exact:o,model:s.model,windowKnown:a,window:i,occupancy:s.occupancy,pct:Math.min(1,s.occupancy/i),transcript:r}}function zg(t,e){return!t||!e?null:ie(t,e)}function Xg(t){return typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
19
|
+
`).trim():""}function Yg(t){if(!Array.isArray(t))return null;let e=t.filter(n=>n&&n.type==="tool_use").map(n=>n.name).filter(Boolean);return e.length?e:null}function Zg(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Ca(t){let e=[],n=t?.message?.content;if(!Array.isArray(n))return e;for(let r of n)if(r){if(r.type==="image"&&r.source?.type==="base64"&&r.source.data)e.push({mediaType:r.source.media_type||"image/png",data:r.source.data,toolUseId:null});else if(r.type==="tool_result"&&Array.isArray(r.content))for(let o of r.content)o?.type==="image"&&o.source?.type==="base64"&&o.source.data&&e.push({mediaType:o.source.media_type||"image/png",data:o.source.data,toolUseId:r.tool_use_id||null})}return e}function au(t,e){if(!t.uuid)return null;let n=Ca(t).map((r,o)=>({uuid:t.uuid,index:o,mediaType:r.mediaType,name:r.toolUseId&&e.get(r.toolUseId)||null}));return n.length?n:null}var Qg=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Ma(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Tt(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
20
|
+
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let h=c.attachment;h?.type==="queued_command"&&h.commandMode==="task-notification"&&typeof h.prompt=="string"&&h.prompt.trim()&&o.push({role:"user",text:h.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant")continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let m=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof m=="string"&&Qg.test(m)&&s.set(h.id,m.split("/").pop())}if(Zg(l.content)){let h=au(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let d=Xg(l.content),f=u==="assistant"?Yg(l.content):null,p=u==="user"?au(c,s):null;!d&&!f&&!p||o.push({role:u,text:d,tools:f||void 0,images:p||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var ty=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function iu(t,e,{turns:n=16}={}){let r=zg(t,e);if(!r)return"";let o=await Ma(r,{limit:n});return o.length?o.map(s=>{let a=s.role==="user"?"User":"Lead agent",i=s.tools?`_(also ran: ${s.tools.join(", ")})_`:"",c=s.images?.length?"(viewed an image)":"(took an action)";return`**${a}:** ${ty(s.text||c,700)} ${i}`.trim()}).join(`
|
|
21
|
+
|
|
22
|
+
`):""}import yy from"node:fs";import Cr from"node:fs/promises";import Dn from"node:path";import ey from"node:fs/promises";import{execFile as ny}from"node:child_process";import{promisify as ry}from"node:util";var Pa=ry(ny),oy=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,cu=new Set,lu=Promise.resolve(),Na=null;function du(t){Na=typeof t=="function"?t:null}function sy(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function fu({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||cu.has(r)||(cu.add(r),lu=lu.then(()=>ay({sessionName:t,from:e,to:r})).catch(()=>{}))}async function ay({sessionName:t,from:e,to:n}){await G({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await Pa("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await ey.realpath(r),s=sy(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await Pa("npm",["install","-g","@openai/codex"],{env:a,timeout:oy});let i=(await Pa(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await G({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await uu({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await G({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await uu({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function uu(t){if(Na)try{await Na(t)}catch{}}var iy=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Ba=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Or=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,_t={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,busyAlt:/^[·✢✳✶✻✽✺✹✸✷]\s+[A-Z][\w '’-]{1,40}…/m,auth:/Select login method:|Paste code here if prompted|OAuth error:|Choose the text style that looks best|Quick safety check: Is this a project you created/},codex:{up:/(^|\n)\s*›/,busy:/esc to interrupt|\bWorking \(/,block:/Update available![\s\S]*\b(?:Update now|Skip until next version)\b[\s\S]*Press enter to continue/,auth:/Sign in with ChatGPT[\s\S]*Press enter to continue|Finish signing in via your browser|Paste or type your API key/},cursor:{up:/Run Everything|Add a follow-up|Plan, search, build anything/,busy:/ctrl\+c to stop|^\s*[⠁-⣿]+\s+\S/m,block:/Workspace Trust Required|Trust this workspace|\[a\]\s*Trust|Trusting workspace/,auth:/Press any key to log in|click this link to log in|Scan this QR code to log in|Signing in with the browser/}},cy=/Trust this workspace|\[a\]\s*Trust/,ly=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function uy(t){let e=String(t||"");if(!_t.codex.block.test(e))return null;let n=e.match(ly);return{from:n?.[1]||null,to:n?.[2]||null}}function ve(t){return new Promise(e=>setTimeout(e,t))}var dy=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,Bo=new Map;function pu(t,e){let n=Date.now(),r=dy();for(let[s,a]of Bo)n-a>=r&&Bo.delete(s);let o=`${t}::${e}`;return Bo.has(o)?!1:(Bo.set(o,n),!0)}var fy=68,py=/^\s*[─━]{10,}\s*$/;function my(t){let e=0;for(let n of String(t).split(`
|
|
23
|
+
`))py.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Da(t,e){let n=_t[t];if(!n?.busy)return"idle";if(ja(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e)||n.busyAlt&&n.busyAlt.test(e))return"busy";let r=my(e);return t==="claude"&&r>0&&r<fy?"unknown":"idle"}var hy=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function hu(t,e){return t==="claude"&&hy.test(String(e||""))}function Bn(t,e){return Da(t,e)==="busy"}function Do(t,e){return Da(t,e)!=="idle"}function jo(t,e){let n=_t[t];return n?.up?n.up.test(e):!0}function wy(t,e){let n=_t[t];return n?ja(t,e)?!1:n.up.test(e)&&Da(t,e)==="idle":!0}function ja(t,e){let n=_t[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="cursor"?!n.up.test(e):!0:!1:!1}async function mu(t,e,n){let r=Date.now()+n,o=0;for(await en(t).catch(()=>{});;){let s=await ht(t);if(wy(e,s)){await ve(Or);return}if(e==="cursor"&&cy.test(s)&&!_t.cursor.up.test(s)&&o<3){if(!pu(t,"cursor-trust")){await ve(Ba);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await kt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await ve(Or);continue}if(e==="codex"&&o<3){let a=uy(s);if(a){if(!pu(t,`codex-update:${a.from}->${a.to}`)){await ve(Ba);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await kt(t,"3",{enter:!1,kind:"codex-update-skip"}),fu({sessionName:t,from:a.from,to:a.to}),await ve(Or);continue}}if(Date.now()>r){if(ja(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=_t.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&_t.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&_t.claude.auth.test(s)&&(a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again"),G({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),G({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!_t[e]?.block?.test(s),paneUp:!!_t[e]?.up?.test(s),paneBusy:!!_t[e]?.busy?.test(s)}).catch(()=>{}),await ve(Or);return}await ve(Ba)}}function wu({agent:t,agentCmd:e}={}){let n=e?Mn(e):Mn(t||"");return{label:n,realClaude:e?Cn(e):n==="claude",realCodex:e?/codex/.test(e)&&!/fake-codex|fake-lead/.test(e):n==="codex",realCursor:e?/cursor/.test(e)&&!/fake-cursor|fake-box/.test(e):n==="cursor"}}function Lo({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=wu({agent:n,agentCmd:r});return!e||!o&&!s?null:Ae({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Ae({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=iy()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=wu({agent:r,agentCmd:o});if(i&&t&&await ke(t),c&&t&&await Rn(t),l&&t&&await $n(t),(i||c||l)&&n){await mu(n,a,s);return}let u=_t[r]?r:_t[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await mu(n,u,s);return}await ve(Or)}var gy=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function ce(t){return t==="codex"?{enter:!0,submitDelayMs:gy}:{enter:!0}}var yu=".bostrat/handoff",bu="latest.md",ku="Checkpoint: write a work-in-progress handoff",xu="HANDOFF WRITTEN",Su="Resume from .bostrat/handoff/latest.md",by="/clear",ky=t=>`${ku} to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file) so a FRESH Claude context with none of this conversation can resume seamlessly. Use these sections: Objective; Done (with file paths); In progress (exact state); Next steps (ordered); Key files & commands; Open decisions/blockers; Resume instruction (one line). Be concrete and self-contained \u2014 assume the reader has none of this chat. When the file is written, reply only with ${xu} and take no further action.`,xy=`${Su}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,vu="Beat: write a 1-2 sentence summary",Au="BEAT WRITTEN",Sy=".bostrat/arc",vy="beat.txt",gu=320,Ay=t=>`${vu} of what this workstream accomplished and its outcome (plain prose, past tense, no headings or bullets \u2014 the way a changelog line reads) to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file). When the file is written, reply only with ${Au} and take no further action.`;async function Tu(t,e,{timeoutMs:n=9e4}={}){let r=Dn.join(e,Sy),o=Dn.join(r,vy);await Cr.mkdir(r,{recursive:!0});let s=await Fo(o),a=await z(t);if(!a)throw new Error(`session "${t}" not found`);await Uo(t,a,Ay(o)),await Cu(o,s,n);let i=(await Cr.readFile(o,"utf8")).trim().replace(/\s+/g," ");if(!i)throw new Error("beat file was written empty");return i.length>gu?`${i.slice(0,gu-1).trimEnd()}\u2026`:i}function Ty(t,e){let n=(e||"").trim();return n?t==="assistant"?n===xu||n===Au:n.startsWith(ku)||n===by||n.startsWith(Su)||n.startsWith(vu):!1}var _y=6e5;function _u(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-_y;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function Eu(t,e){let n=[];for(let r of t?.boxes||[]){let o=e?.(r.name);o?.startedAt&&n.push({box:r.name,from:o.startedAt,to:o.finishedAt||Date.now()+1e12})}return n}function Iu(t,{windows:e=[]}={}){if(Ty(t.role,t.text))return!0;if(t.role!=="assistant"||!t.ts)return!1;for(let n of e)if(t.box===n.box&&t.ts>=n.from&&t.ts<=n.to)return!0;return!1}var Ey=3500,Iy=3e5,La=new Map;function Ho(t){return La.get(t)||null}async function Ru(t){let e=La.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await z(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:Dn.join(n.cwd,yu,bu)};return La.set(t,r),Ry(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function Ry(t,e){t.step="summarize",await $u(t.name,e),t.step="clear";let n=await z(t.name);await Uo(t.name,n,"/clear"),await Mu(Ey),t.step="resume",await Uo(t.name,n,xy),t.step="done",t.finishedAt=Date.now()}async function $u(t,e){let n=Dn.join(e,yu),r=Dn.join(n,bu);await Cr.mkdir(n,{recursive:!0});let o=await Fo(r),s=await z(t);return await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Uo(t,s,ky(r)),await Cu(r,o,Iy),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Fo(r)}),await $y(n,r),r}async function Ou(t){let e=await z(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return $u(t,e.cwd)}async function Fo(t){try{return(await Cr.stat(t)).mtimeMs}catch{return 0}}async function Cu(t,e,n){let r=Date.now()+n;for(;;){if(await Fo(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Mu(2e3)}}async function $y(t,e){try{if(!yy.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Cr.copyFile(e,Dn.join(t,`${n}.md`))}catch{}}function Mu(t){return new Promise(e=>setTimeout(e,t))}async function Uo(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await Ae({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await z(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await kt(t,n,{kind:"orchestration-drive",...ce(r.agent)})}import j from"node:fs/promises";import L from"node:path";import{randomUUID as fo}from"node:crypto";import{execFile as mv}from"node:child_process";import{promisify as hv}from"node:util";import wf from"node:path";import Fa from"node:fs/promises";import jn from"node:path";import Oy from"node:os";import{EventEmitter as Cy}from"node:events";import{randomUUID as My}from"node:crypto";var Py=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","provider.connected","track.summary.recorded","stall.detected","error"]),Ny=new Set(["user","agent","orchestrator","system"]),By=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Dt=new Cy;Dt.setMaxListeners(0);var Ua=process.env.BOSTRAT_STATE_DIR||jn.join(process.env.HOME||Oy.tmpdir(),".bostrat");function qo(t){return t?.cwd?jn.join(t.cwd,".bostrat","events"):jn.join(Ua,"events")}var Pu=(t,e)=>jn.join(t,`${e}.ndjson`),Wo=new Map;async function Dy(t,e){if(!Wo.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Wo.set(e,o)}let n=Wo.get(e)+1;return Wo.set(e,n),n}function jy(t,e){let n=Array.isArray(t?.epochs)?t.epochs:null;if(!(!n||n.length===0)){if(!Number.isFinite(e))return n.length-1;for(let r=n.length-1;r>=0;r--){let o=n[r].startedAt;if(o==null||o<=e)return r}return 0}}async function C(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!Py.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!Ny.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!By.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?jn.join(Ua,"events"):qo(t),o=e.epoch;if(o==null&&typeof t=="object"&&e.boxId&&Array.isArray(t?.boxes)){let c=t.boxes.find(l=>l.name===e.boxId);o=jy(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await Dy(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||My(),trackId:n,boxId:e.boxId??null,...o!=null?{epoch:o}:{},...a?{userId:a}:{},seq:s,ts:e.ts??Date.now(),type:e.type,actor:e.actor??"system",provider:e.provider??null,source:e.source??"orchestrator",payload:e.payload??{},...e.causationId?{causationId:e.causationId}:{},...e.correlationId?{correlationId:e.correlationId}:{}};return await Fa.mkdir(r,{recursive:!0}),await Fa.appendFile(Pu(r,n),JSON.stringify(i)+`
|
|
24
|
+
`),Dt.emit("event",i),Dt.emit(`track:${n}`,i),i}async function F(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?jn.join(Ua,"events"):qo(t));let o;try{o=await Fa.readFile(Pu(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
25
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function Nu(t,e){let n=`track:${t}`;return Dt.on(n,e),()=>Dt.off(n,e)}var Ha={"claude-opus-4-8":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"claude-sonnet-5":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-sonnet-4-6":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-haiku-4-5":{input:1,output:5,cacheWrite:1.25,cacheRead:.1},"claude-fable-5":{input:10,output:50,cacheWrite:12.5,cacheRead:1},"gpt-5.5":{input:5,output:30,cacheWrite:5,cacheRead:.5},"gpt-5.6-sol":{input:5,output:30,cacheWrite:6.25,cacheRead:.5},"gpt-5.6-terra":{input:2.5,output:15,cacheWrite:3.125,cacheRead:.25},"gpt-5.6-luna":{input:1,output:6,cacheWrite:1.25,cacheRead:.1}},Bu={opus:"claude-opus-4-8",opusplan:"claude-opus-4-8",default:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"};function Du(t){if(!t)return null;let e=String(t).trim();return Ha[e]?e:Bu[e]?Bu[e]:Object.keys(Ha).find(r=>e.startsWith(r))||null}var Go=t=>Du(t)!==null;function Jt(t,e={}){let n=Ha[Du(t)];return n?((e.input||0)*n.input+(e.output||0)*n.output+(e.cacheWrite||0)*n.cacheWrite+(e.cacheRead||0)*n.cacheRead)/1e6:0}var ju=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function Ly(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Lu(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Te(t,{boxLabels:e={}}={}){let n=(t||[]).filter(d=>d.type==="cost.recorded"),r={},o={},s={},a=0,i=0,c=0,l=new Set,u=ju();for(let d of n){let f=d.payload?.model||"unknown",p=d.provider||"unknown",h=d.boxId||"unattributed",m=Ly(d.payload?.tokens),b=Number(d.payload?.usd)||0;!b&&m&&(b=Jt(f,m),b>0&&i++);let k=m?m.input+m.output+m.cacheRead+m.cacheWrite:0;if(!b&&k>0&&!Go(f)&&(c++,l.add(f)),a+=b,Lu(u,m),r[f]||={usd:0,tokens:ju(),events:0},r[f].usd+=b,r[f].events+=1,Lu(r[f].tokens,m),o[p]||={usd:0,events:0},o[p].usd+=b,o[p].events+=1,s[h]||={usd:0,events:0,provider:p,models:new Set,label:e[h]||h},s[h].usd+=b,s[h].events+=1,f!=="unknown"&&s[h].models.add(f),d.epoch!=null){let v=(s[h].epochs||={})[d.epoch]||={usd:0,events:0,provider:p,models:new Set};v.usd+=b,v.events+=1,v.provider=p,f!=="unknown"&&v.models.add(f)}}return{totalUsd:Mr(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Ko(r,d=>({...d,usd:Mr(d.usd)})),byProvider:Ko(o,d=>({...d,usd:Mr(d.usd)})),byBox:Ko(s,d=>({usd:Mr(d.usd),events:d.events,provider:d.provider,label:d.label,models:[...d.models],...d.epochs?{epochs:Ko(d.epochs,f=>({usd:Mr(f.usd),events:f.events,provider:f.provider,models:[...f.models]}))}:{}}))}}var Mr=t=>Math.round(t*1e4)/1e4,Ko=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import gt from"node:fs/promises";import Et from"node:path";import Pr from"node:os";import{randomUUID as Hy}from"node:crypto";import{execFile as Wy}from"node:child_process";import{promisify as qy}from"node:util";import{chromium as Gy}from"playwright-core";import Wa from"node:fs/promises";import Fy from"node:path";var Uy=0;async function zt(t,e,{mode:n}={}){await Wa.mkdir(Fy.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++Uy}.tmp`;await Wa.writeFile(r,JSON.stringify(e,null,2)+`
|
|
26
|
+
`,n!=null?{mode:n}:void 0),await Wa.rename(r,t)}var Uu=qy(Wy),Hu=process.env.BOSTRAT_STATE_DIR||Et.join(process.env.HOME||Pr.tmpdir(),".bostrat"),Wu=Et.join(Hu,"mocks.json"),Ky=process.env.BOSTRAT_MOCK_ENGINE||"chromium",Vy=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",Jy=process.env.BOSTRAT_FIREFOX_BIN||"firefox",zy=1300,Ln=2,Xy=32,qu=1500,Fn=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,Yy=700*1024*1024;async function Zy(){if(process.platform!=="darwin")return Pr.freemem();try{let{stdout:t}=await Uu("vm_stat",[],{timeout:3e3}),e=Number(/page size of (\d+) bytes/.exec(t)?.[1])||4096,n=o=>Number(new RegExp(`Pages ${o}:\\s+(\\d+)\\.`).exec(t)?.[1])||0,r=(n("free")+n("inactive")+n("speculative"))*e;return r>0?r:Pr.freemem()}catch{return Pr.freemem()}}var Ht=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
27
|
+
${n.trim().split(`
|
|
28
|
+
`).slice(-6).join(`
|
|
29
|
+
`)}`:e),this.name="MockRenderError"}},nn=null,qa=null;async function le(){return nn||(qa||=(async()=>{try{nn=JSON.parse(await gt.readFile(Wu,"utf8"))}catch{nn={mocks:[]}}finally{qa=null}return nn.mocks||=[],nn})(),qa)}async function Xt(){await zt(Wu,nn)}function Wt(t,e,n={}){return C({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Vo=t=>Et.join(t,".bostrat","mocks");function jt(t,e){let n=Vo(t.cwd);return{html:Et.join(n,`${t.id}.v${e}.html`),png:Et.join(n,`${t.id}.v${e}.png`)}}function rn(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Ka(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Qy=Buffer.from([137,80,78,71,13,10,26,10]);function Gu(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Qy)}async function tb(t){let e=Buffer.alloc(24),n=await gt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function eb({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function nb(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,m)=>{r.onload=h,r.onerror=()=>m(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,d=s,f=-1,p=-1;for(let h=0;h<s;h++)for(let m=h*o*4,b=0;b<o;b++,m+=4)Math.abs(c[m]-l[0])+Math.abs(c[m+1]-l[1])+Math.abs(c[m+2]-l[2])>24&&(b<u&&(u=b),b>f&&(f=b),h<d&&(d=h),p=h);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:p}}},e.toString("base64"))}async function rb(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((d,f)=>{c.onload=d,c.onerror=()=>f(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function ob({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await Gy.launch({executablePath:Vy,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Ht("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Ln})).newPage();a.setDefaultTimeout(Fn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Fn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Fn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await nb(a,i),u=l&&eb({...l,pad:Xy*Ln,align:Ln});u&&(i=await rb(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<qu)throw new Ht(`capture produced a ${i.length}-byte image`);let c=Ka(i);if(!c)throw new Ht("output is not a PNG");return await gt.writeFile(e,i),{width:Math.round(c.width/Ln),height:Math.round(c.height/Ln),dpr:Ln,renderMs:Date.now()-r}}catch(s){throw s instanceof Ht?s:new Ht(/timeout/i.test(s?.message||"")?`render timed out after ${Fn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Fu({htmlPath:t,outPath:e,width:n}){let r=await gt.mkdtemp(Et.join(Pr.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Uu(Jy,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Fn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Ht(c?.killed?`render timed out after ${Fn}ms`:"firefox exited non-zero",s)}let a=await gt.stat(e).catch(()=>null);if(!a||a.size<qu)throw new Ht(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ka(await tb(e));if(!i)throw new Ht("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await gt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function sb(t){if(Ky==="firefox")return Fu(t);try{return await ob(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Fu(t);throw e}}var ab=sb;var Ga=Promise.resolve();function Ku(t){return Ga=Ga.then(t,t),Ga}async function Vu(t,e){let n=await le(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Xt(),await Wt(r,"rendering",{version:e});try{let o=await Zy();if(o<Yy)throw new Ht(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=jt(r,e),a=await ab({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await gt.rm(s.png,{force:!0}).catch(()=>{});return}Ju(r,{v:e,source:"html",htmlRel:Et.relative(r.cwd,s.html),pngRel:Et.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Xt(),await Wt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Xt(),await Wt(r,"error",{version:e,error:r.error})}}}function Ju(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function ib({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?zy:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Jo({trackId:t,group:e}={}){let n=await le(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function _e(t){let n=(await le()).mocks.find(r=>r.id===t);return n?{...n}:null}async function zu({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=ib({trackId:t,name:e,cwd:o,width:s}),d=typeof n=="string"&&n.trim();if(!d&&!r)throw new Error("html (string) or image (png) required");if(r&&!Gu(r))throw new Error("image must be a PNG");let f=await le(),p={id:Hy().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:d?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await gt.mkdir(Vo(o),{recursive:!0}),f.mocks.push(p),d?(await gt.writeFile(jt(p,1).html,n),await Xt(),await Wt(p,"created"),Ku(()=>Vu(p.id,1))):(await Yu(p,1,r),await Xt(),await Wt(p,"created"),await Wt(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr})),{...p}}async function Xu(t,{html:e,image:n,width:r}={}){let s=(await le()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Gu(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await gt.mkdir(Vo(s.cwd),{recursive:!0}),a?(s.state="queued",await gt.writeFile(jt(s,i).html,e),await Xt(),await Wt(s,"updated",{version:i}),Ku(()=>Vu(s.id,i))):(await Yu(s,i,n),await Xt(),await Wt(s,"updated",{version:i}),await Wt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Yu(t,e,n){let r=Ka(n);if(!r)throw new Error("image must be a PNG");let o=jt(t,e);await gt.writeFile(o.png,n),Ju(t,{v:e,source:"image",htmlRel:null,pngRel:Et.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Zu(t){let e=await le(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await Wt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Qu(t){let e=await le(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=jt(r,o);await gt.rm(s.html,{force:!0}).catch(()=>{}),await gt.rm(s.png,{force:!0}).catch(()=>{})}return await Xt(),await Wt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function td(t){let n=(await le()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Et.join(Hu,"mock-archive",t.id),o=Vo(t.cwd),s=Et.join(r,".bostrat","mocks");await gt.mkdir(s,{recursive:!0});let a=await gt.readdir(o).catch(()=>[]);for(let i of a)await gt.copyFile(Et.join(o,i),Et.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await gt.stat(Et.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await Xt(),n.length}async function ed(t){let e=await le(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Xt()),n.length}import Br from"node:fs/promises";import It from"node:path";import cb from"node:os";import lb from"node:net";import{randomUUID as ub}from"node:crypto";import{execFile as db}from"node:child_process";import{promisify as fb}from"node:util";var Zt=fb(db),pb=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||cb.tmpdir(),".bostrat"),ad=It.join(pb,"views.json"),[nd,rd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Ee=Number(process.env.BOSTRAT_VIEW_MAX)||3,mb="bostrat-view-",Un=t=>`${mb}${t}`,od=new Set(["bostrat","www","api","views","app"]),zo=t=>Object.assign(new Error(t),{status:400}),hb=new Set(["npm","pnpm","yarn","bun"]),za="package.json",wb=/[|;&`]|\$\(/,gb=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),yb=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,id=t=>String(t).trim().split(/\s+/).filter(Boolean),bb=/^[A-Za-z_][A-Za-z0-9_]*=/;function kb(t){for(let e of id(t))if(!bb.test(e))return e;return null}function cd(t){let e=id(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function xb(t,e){let n=cd(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return It.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return It.resolve(t,n[r+1])}return t}var Sb=t=>Br.stat(t).then(e=>e.isDirectory(),()=>!1),ld=t=>Br.stat(t).then(e=>e.isFile(),()=>!1),Xo=t=>Br.realpath(t).catch(()=>It.resolve(t));async function vb(t,e){if(It.resolve(t)===It.resolve(e))return!0;let[n,r]=await Promise.all([Xo(t),Xo(e)]);return n===r}async function Ab(t){let n=(await Br.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await ld(It.join(t,o.name,za))?`./${o.name}`:null))).filter(Boolean)}async function Tb(t,e){let[n,r]=await Promise.all([Xo(t),Xo(e)]),o=It.relative(n,r);if(o&&(o.startsWith("..")||It.isAbsolute(o)))throw zo(`serve directory escapes the Track workspace: ${e}`)}async function _b(t,e){if(!await Sb(t))throw zo(`serve directory does not exist: ${t}`);let n=kb(e);if(!n)return;if(!n.includes("/")&&!await Ta(n,{loginShell:!0,failOpen:!0}))throw zo(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(wb.test(e)||gb.has(n)||!hb.has(n)||!cd(e).includes("run")||yb.test(e))return;let r=xb(t,e);if(await ld(It.join(r,za)))return;let o=await Ab(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw zo(`no ${za} in ${r}${s}`)}var Yt=null,Va=null;async function fe(){return Yt||(Va||=(async()=>{try{Yt=JSON.parse(await Br.readFile(ad,"utf8"))}catch{Yt={views:[],adoptWatermarks:{}}}finally{Va=null}return Yt.views||=[],Yt.adoptWatermarks||={},Yt})(),Va)}async function Qt(){await zt(ad,Yt)}function de(t,e,n={}){return C({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Nr=new Map,Za=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,ud=12e3;function Qa(t){return Nr.get(t)||null}function dd(t,{name:e,box:n}={}){let r=Nr.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return Nr.set(t,o),o}function Xa(t,e){let n=Nr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Yo(t,{error:e=null}={}){let n=Nr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function fd(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function Ya(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Eb(t,e){let n=Ya(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!od.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!od.has(s))return s}}function Dr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=lb.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function ti(t){if(process.platform!=="darwin"){await Zt("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await Zt("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}async function Ib(t,e){let n=new Set(t.views.map(r=>r.port));if(e){let r=Number(e);if(!Number.isInteger(r)||r<1024||r>65535)throw new Error("invalid port");if(n.has(r))throw new Error(`port ${r} is already owned by a view`);return r}for(let r=nd;r<=rd;r++)if(!n.has(r)&&!await Dr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${nd}-${rd}`)}async function Rb(t){try{let{stdout:e}=await Zt(P,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
30
|
+
`).some(n=>n.trim()===Un(t))}catch{return!1}}async function jr(t){try{await Zt(P,N("kill-session","-t",Un(t)))}catch{}}async function ei(t){if(!await Ir())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Un(t.slug),n=t.serveCwd??t.cwd,r=It.dirname(process.execPath);await Zt(P,N("new-session","-d","-s",e,"-c",n)),await Zt(P,N("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await Zt(P,N("send-keys","-t",e,"Enter")),await Zt(P,N("send-keys","-t",e,"-l",t.cmd)),await Zt(P,N("send-keys","-t",e,"Enter"))}async function sd(t,e=12){try{let{stdout:n}=await Zt(P,N("capture-pane","-t",Un(t),"-p"));return String(n||"").trimEnd().split(`
|
|
63
31
|
`).filter(Boolean).slice(-e).join(`
|
|
64
|
-
`)}catch{return""}}var oe=new Map,Fk=12e4;async function Hk(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Uk(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Zd(t.slug)||"server exited before listening"),oe.delete(t.id),await se(t,t.state,t.error?{error:t.error}:{}),!0}if(await Tr(t.port))return oe.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await se(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>Fk?(t.state="error",t.error=await Zd(t.slug)||"never started listening",await se(t,"error",{error:t.error}),!0):!1;let o=(oe.get(t.id)||0)+1;return oe.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var li=!1;async function qo(){if(!li){li=!0;try{let t=await ae(),e=!1;for(let n of t.views)await Hk(n)&&(e=!0);e&&await zt()}finally{li=!1}}}async function af({trackId:t}={}){await qo();let e=await ae();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function cf(t){let n=(await ae()).views.find(r=>r.id===t);return n?{...n}:null}function gi(t){return Vt&&Vt.views.find(e=>e.slug===t)||null}async function yi({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=At.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Bk(r,u),await Dk(u,n)},f=await ae();if(i!=="detected"){let g=fi(e),x=String(n).trim(),A=f.views.find(S=>S.trackId===t&&(S.slug===g||fi(S.name)===g||S.cmd===x&&At.resolve(S.serveCwd??S.cwd)===u));if(A)return Wk(f,A,{cmd:x,box:a,branch:c,serveCwd:l?u:void 0,validate:d})}if(d&&await d(),f.views.filter(g=>g.state==="live"||g.state==="starting").length>=ve)throw new Error(`view cap reached (${ve} live) \u2014 stop one first`);let m={id:xk().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:jk(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Lk(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await wi(m),f.views.push(m),await zt(),await se(m,"created",{origin:i}),di(t,m.id),i==="detected"&&await Tr(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await zt(),await se(m,"live")),{...m}}async function Wk(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await Pk(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Tr(e.port))return await zt(),di(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(d=>d.id!==e.id&&(d.state==="live"||d.state==="starting")).length>=ve)throw new Error(`view cap reached (${ve} live) \u2014 stop one first`);return e.origin==="detected"?(await hi(e.port),e.origin="agent"):await Ar(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),oe.delete(e.id),await wi(e),await zt(),await se(e,"created",{restarted:!0,reused:!0}),di(e.trackId,e.id),{...e,reused:!0}}async function lf(t){let n=(await ae()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await hi(n.port):await Ar(n.slug),n.state!=="stopped"&&(n.state="stopped",oe.delete(n.id),await zt(),await se(n,"stopped")),{...n}}async function uf(t){let e=await ae(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=ve)throw new Error(`view cap reached (${ve} live) \u2014 stop one first`);return await Ar(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),oe.delete(n.id),await wi(n),await zt(),await se(n,"created",{restarted:!0}),{...n}}async function df(t){let e=await ae(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await hi(r.port):await Ar(r.slug),e.views.splice(n,1),oe.delete(r.id),await zt(),await se(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function ff(t){let e=await ae(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Ar(r.slug),oe.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await zt()),n.length}var qk=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function pf(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await ae(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await zt()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(qk)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(c!==Number(process.env.PORT||3362)&&!n.views.some(l=>l.port===c)&&await Tr(c))try{i.push(await yi({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import{execFile as Gk}from"node:child_process";import{promisify as Kk}from"node:util";var Vo=Kk(Gk),Vk={timeout:3e4,maxBuffer:8*1024*1024};async function Ut(t,e,n={}){let r=await Gt({cwd:t}),{stdout:o}=await Vo("gh",e,{cwd:t,...Vk,...n,env:{...process.env,...r,...n.env||{}}});return String(o||"")}var Go=null;async function xi(){if(Go&&Date.now()-Go.at<6e4)return Go.data;let t;try{let e=await Vo("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await pr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Go={at:Date.now(),data:t},t}async function Jk(t){try{let{stdout:e}=await Vo("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function hf(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r;return t.__typename==="StatusContext"?r=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?r="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?r="pass":r="fail",{name:e,state:r,url:n}}function bi(t){let e=(t||[]).map(hf);return{total:e.length,passed:e.filter(n=>n.state==="pass").length,failed:e.filter(n=>n.state==="fail").length,pending:e.filter(n=>n.state==="pending").length,failing:e.filter(n=>n.state==="fail").map(({name:n,url:r})=>({name:n,url:r}))}}var Si="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",wf=Si.replace("statusCheckRollup,","");function vi(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function gf(t,e){if(!e)return null;let n=await Y(t);if(!n)return null;try{let r=await Ut(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function yf(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null}))}async function bf(t,e){let n=await gf(t,e);return n===null?{...bi([]),unavailable:!0}:bi(yf(n))}function Ko(t,e=bi(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var zk=9e4,Bn=new Map;function Xk(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Bn.delete(s);continue}r.add(s),Bn.has(s)||Bn.set(s,n),o.mergeabilityPending=n-Bn.get(s)<zk}for(let o of Bn.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Bn.delete(o);return e}async function Er(t,e){let n=await Te(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ut(n.path,["pr","view",String(r),"--json",Si]);return Ko(JSON.parse(o))}catch(o){if(!vi(o))throw o;let s=await Ut(n.path,["pr","view",String(r),"--json",wf]),a=JSON.parse(s);return Ko(a,await bf(n.path,a.headRefOid))}}var mf=(t,e)=>e.number-t.number;async function _r(t){try{let e=await Ut(t,["pr","list","--json",Si,"--limit","50"]);return JSON.parse(e||"[]").map(n=>Ko(n)).sort(mf)}catch(e){if(!vi(e))throw e;let n=await Ut(t,["pr","list","--json",wf,"--limit","50"]);return(await Promise.all(JSON.parse(n||"[]").map(async r=>Ko(r,await bf(t,r.headRefOid))))).sort(mf)}}var Yk=2e4,Zk=1500,Dn=null;function en(){Dn=null}async function Qk(t,e){if(!t)return e;for(let n of e){let r=await Tt(t,n.number).catch(()=>null);if(!r)continue;r.autoMerge&&(n.autoMergeArmed=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await Io(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await $o(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function kf(){let t=await xi();if(!t.authenticated)return{auth:t,repos:[]};let e=Dn?.pending?Zk:Yk;if(Dn&&Date.now()-Dn.at<e)return{auth:t,repos:Dn.data};let n=[];for(let a of await re())await Jk(a.path)&&n.push(a);let r=Date.now(),o=await Promise.all(n.map(async a=>{try{let i=Xk(a.path,await _r(a.path),r);return await Qk(await Y(a.path).catch(()=>null),i),{name:a.name,path:a.path,prs:i}}catch(i){return{name:a.name,path:a.path,prs:[],error:ie(i)}}})),s=o.some(a=>a.prs.some(i=>i.mergeabilityPending||i.checks.pending>0||i.autoMergeArmed));return Dn={at:Date.now(),data:o,pending:s},{auth:t,repos:o}}async function Te(t){let e=(await re()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Ti(t){if(!t)return null;for(let e of await re())if(await Y(e.path).catch(()=>null)===t)return e.path;return null}var Jo=new Set(["squash","merge","rebase"]);async function Ir(t,e,{method:n="squash"}={}){let r=await Te(t);if(!Jo.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await Gt({cwd:r.path})).GH_TOKEN){let a=await ua().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ut(r.path,s)}catch(a){throw new Error(ie(a))}finally{en()}return{ok:!0,number:o}}async function xf(t,e){let n=await Te(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ut(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ie(o))}finally{en()}return{ok:!0,number:r}}var tx=2500,ex=9e4,nx=t=>new Promise(e=>setTimeout(e,t));function Sf(t){return t.green&&!t.isDraft}function rx(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}var ki=new Map;function vf(){let t={};for(let[e,n]of ki)t[e]={...n};return t}async function Tf(t,{method:e="merge",onMerged:n=null}={}){let r=await Te(t);if(!Jo.has(e))throw new Error(`invalid merge method: ${e}`);if(ki.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await _r(r.path)).filter(Sf).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return ki.set(r.path,s),sx(s,o,{listPrs:()=>_r(r.path),merge:a=>Ir(r.path,a,{method:e}),sleep:nx,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function ox(t,e){let n=Date.now()+(e.settleMaxMs??ex),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(tx)}}async function sx(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await ox(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${ie(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!Sf(a)){t.skipped.push({number:a.number,title:a.title,reason:rx(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:ie(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),en()}}async function Af(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Vo("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ie(l)}`)}let o=n||"";if((await Gt({cwd:t})).GH_TOKEN){let l=await ua().catch(()=>null);l&&(o=`${o?`${o}
|
|
65
|
-
|
|
66
|
-
|
|
32
|
+
`)}catch{return""}}var ue=new Map,$b=12e4;async function Ob(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Rb(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await sd(t.slug)||"server exited before listening"),ue.delete(t.id),await de(t,t.state,t.error?{error:t.error}:{}),!0}if(await Dr(t.port))return ue.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await de(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>$b?(t.state="error",t.error=await sd(t.slug)||"never started listening",await de(t,"error",{error:t.error}),!0):!1;let o=(ue.get(t.id)||0)+1;return ue.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Ja=!1;async function Zo(){if(!Ja){Ja=!0;try{let t=await fe(),e=!1;for(let n of t.views)await Ob(n)&&(e=!0);e&&await Qt()}finally{Ja=!1}}}async function Qo({trackId:t}={}){await Zo();let e=await fe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function pd(t){let n=(await fe()).views.find(r=>r.id===t);return n?{...n}:null}function ni(t){return Yt&&Yt.views.find(e=>e.slug===t)||null}async function ri({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=It.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Tb(r,u),await _b(u,n)},f=await fe();if(i!=="detected"){let m=Ya(e),b=String(n).trim(),k=f.views.find(v=>v.trackId===t&&(v.slug===m||Ya(v.name)===m||v.cmd===b&&It.resolve(v.serveCwd??v.cwd)===u));if(k)return Cb(f,k,{cmd:b,box:a,branch:c,serveCwd:l?u:void 0,validate:d})}if(d&&await d(),f.views.filter(m=>m.state==="live"||m.state==="starting").length>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);let h={id:ub().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Eb(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Ib(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await ei(h),f.views.push(h),await Qt(),await de(h,"created",{origin:i}),Xa(t,h.id),i==="detected"&&await Dr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await Qt(),await de(h,"live")),{...h}}async function Cb(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await vb(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Dr(e.port))return await Qt(),Xa(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(d=>d.id!==e.id&&(d.state==="live"||d.state==="starting")).length>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);return e.origin==="detected"?(await ti(e.port),e.origin="agent"):await jr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),ue.delete(e.id),await ei(e),await Qt(),await de(e,"created",{restarted:!0,reused:!0}),Xa(e.trackId,e.id),{...e,reused:!0}}async function md(t){let n=(await fe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await ti(n.port):await jr(n.slug),n.state!=="stopped"&&(n.state="stopped",ue.delete(n.id),await Qt(),await de(n,"stopped")),{...n}}async function hd(t){let e=await fe(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);return await jr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),ue.delete(n.id),await ei(n),await Qt(),await de(n,"created",{restarted:!0}),{...n}}async function wd(t){let e=await fe(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await ti(r.port):await jr(r.slug),e.views.splice(n,1),ue.delete(r.id),await Qt(),await de(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function gd(t){let e=await fe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await jr(r.slug),ue.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await Qt()),n.length}var Mb=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function yd(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await fe(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await Qt()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(Mb)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(c!==Number(process.env.PORT||3362)&&!n.views.some(l=>l.port===c)&&await Dr(c))try{i.push(await ri({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import sn from"node:fs/promises";import Ie from"node:path";import Pb from"node:os";import{randomUUID as Nb}from"node:crypto";var Wn=process.env.BOSTRAT_STATE_DIR||Ie.join(process.env.HOME||Pb.tmpdir(),".bostrat"),kd=Ie.join(Wn,"runbooks.json"),si=Ie.join(Wn,"runbooks"),bd=256*1024,Bb=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function Db(t){return Bb.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var on=null,oi=null;async function Re(){return on||(oi||=(async()=>{try{on=JSON.parse(await sn.readFile(kd,"utf8"))}catch{on={runbooks:[]}}finally{oi=null}return on.runbooks||=[],on})(),oi)}async function qn(){await zt(kd,on)}function Gn(t,e,n={}){return C({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Hn(t,e){return Ie.join(si,`${t.id}.v${e}.md`)}function xd(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Sd(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>bd)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${bd/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Db(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function vd(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ts({trackId:t,status:e}={}){let n=await Re(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function ai(t){let n=(await Re()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Ad({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=Sd(r),c=await Re(),l={id:Nb().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await sn.mkdir(si,{recursive:!0}),await sn.writeFile(Hn(l,1),r),l.versions.push({v:1,mdRel:Ie.relative(Wn,Hn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await qn(),await Gn(l,"created",{version:1}),{...l}}async function Td(t,{md:e,note:n}={}){let o=(await Re()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");vd(o,"update");let s=Sd(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await sn.mkdir(si,{recursive:!0}),await sn.writeFile(Hn(o,a),e),o.versions.push({v:a,mdRel:Ie.relative(Wn,Hn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await qn(),await Gn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function _d(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Re()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return vd(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await qn(),await Gn(r,e==="done"?"done":"reopened"),{...r})}async function Ed(t){let e=await Re(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await sn.rm(Ie.join(Wn,o.mdRel),{force:!0}).catch(()=>{});return await qn(),r.trackDeletedAt||await Gn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Id(t){let n=(await Re()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Gn(r,"orphaned"),r.cwd=null;return n.length&&await qn(),n.length}async function Rd(t){let e=await Re(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Gn(r,"deleted");for(let o of r.versions||[])await sn.rm(Ie.join(Wn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await qn()),n.length}import Ur from"node:fs/promises";import qd from"node:path";import dk from"node:os";import{randomUUID as Gd}from"node:crypto";import jb from"node:fs/promises";import $d from"node:path";import Lb from"node:os";var Od=()=>$d.join(process.env.BOSTRAT_STATE_DIR||$d.join(process.env.HOME||Lb.tmpdir(),".bostrat"),"integrations.json");async function an(){try{return JSON.parse(await jb.readFile(Od(),"utf8"))||{}}catch{return{}}}var es=["linked","prOpened","prMerged","create"],ii=["prompt","auto","off"],Fb={create:["prompt","off"]},Cd=t=>Fb[t]??ii;function ns(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of es){let s=Cd(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function cn(){let t=await an(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:ns(t),defaultTeam:n}:null}async function rs(){let t=await an(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:ns(t)}}async function ci(){return ns(await an())}async function Md(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${es.join(", ")}`);for(let[r,o]of e){if(!es.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${es.join(", ")}`);let s=Cd(r);if(!s.includes(o))throw new Error(s.length<ii.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${ii.join(", ")}`)}let n=await an();return n.ticketFlow={...ns(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await zt(Od(),n,{mode:384}),n.ticketFlow}async function Lr(){let t=(await an()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Pd(){let t=(await an()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function ln(){let t=(await an()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var ui={};Ul(ui,{createComment:()=>zb,findIssueUrl:()=>qb,issueStates:()=>Zb,mdToAdf:()=>Dd,moveIssue:()=>Qb,parseIssueRef:()=>Wb,resolveIssue:()=>Gb,uploadImage:()=>Xb,upsertCard:()=>Kb,whoami:()=>li});var Kn=t=>String(t.baseUrl||"").replace(/\/$/,""),Bd=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function un(t,e,n,r){let o=await fetch(`${Kn(t)}${n}`,{method:e,headers:{authorization:Bd(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var Ub=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Hb=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function Wb(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(Ub);return r?r[1].toUpperCase():null}let n=e.match(Hb);return n?n[1]:null}function qb(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function Gb(t,e){let n;try{n=await un(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${Kn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function Kb(t,e,{url:n,title:r,subtitle:o}){await un(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var Vb=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function Jb(t){let e=[],n=0;for(let r of t.matchAll(Vb)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Dd(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
33
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...Jb(o))}),{type:"paragraph",content:r.length?r:[{type:"text",text:" "}]}});return{type:"doc",version:1,content:e.length?e:[{type:"paragraph",content:[{type:"text",text:" "}]}]}}async function zb(t,e,n){let r=await un(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Dd(n)});return r?.id?`${Kn(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function li(t){let e=await un(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(Kn(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function Xb(t,{issue:e,filename:n,contentType:r,buffer:o}){let s=new FormData;s.append("file",new Blob([o],{type:r}),n);let a=await fetch(`${Kn(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Bd(t),"x-atlassian-token":"no-check",accept:"application/json"},body:s}),i=await a.json().catch(()=>null),c=Array.isArray(i)?i[0]:null;if(!a.ok||!c)throw new Error(`jira: attachment upload failed (HTTP ${a.status})`);return c.content??`${Kn(t)}/rest/api/3/attachment/content/${c.id}`}var Yb={new:"unstarted",indeterminate:"started",done:"completed"},Nd=t=>Yb[t?.key]??"started";async function Zb(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([un(t,"GET",`/rest/api/3/issue/${n}?fields=status`),un(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Nd(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Nd(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function Qb(t,e,n){await un(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var mi={};Ul(mi,{createComment:()=>ik,createIssue:()=>pi,createProject:()=>fi,findIssueUrl:()=>ok,issueStates:()=>lk,moveIssue:()=>uk,parseIssueRef:()=>rk,resolveIssue:()=>sk,teams:()=>Vn,uploadImage:()=>ck,upsertCard:()=>ak,whoami:()=>di});var tk=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function te(t,e,n={}){let r=await fetch(tk(),{method:"POST",headers:{"content-type":"application/json",authorization:t.apiKey},body:JSON.stringify({query:e,variables:n})}),o=await r.json().catch(()=>null);if(!r.ok)throw new Error(`linear: HTTP ${r.status}${o?.errors?.[0]?.message?` \u2014 ${o.errors[0].message}`:""}`);if(o?.errors?.length){let s=o.errors[0],a=(s.extensions?.validationErrors??[]).flatMap(i=>Object.values(i?.constraints??{})).join("; ");throw new Error(`linear: ${s.message}${a?` \u2014 ${a}`:""}`)}return o?.data??{}}var ek=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,nk=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function rk(t){let e=String(t||"").trim(),n=e.match(ek);if(n)return n[1].toUpperCase();let r=e.match(nk);return r?r[1]:null}function ok(t){let e=String(t||"").match(/https?:\/\/linear\.app\/[^/\s]+\/issue\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/);return e?e[0]:null}async function sk(t,e){let r=(await te(t,"query($id: String!) { issue(id: $id) { id identifier title url state { name } team { key } } }",{id:e})).issue;if(!r)throw new Error(`linear: issue "${e}" not found`);return{issueId:r.id,key:r.identifier,title:r.title,url:r.url,state:r.state?.name??null,team:r.team?.key??null}}async function ak(t,e,{url:n,title:r,subtitle:o}){let s=await te(t,"mutation($input: AttachmentCreateInput!) { attachmentCreate(input: $input) { success attachment { id } } }",{input:{issueId:e.issueId,url:n,title:r,subtitle:o}});if(!s.attachmentCreate?.success)throw new Error("linear: attachmentCreate did not succeed");return s.attachmentCreate.attachment?.id??null}async function ik(t,e,n){let r=await te(t,"mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { url } } }",{input:{issueId:e.issueId,body:n}});if(!r.commentCreate?.success)throw new Error("linear: commentCreate did not succeed");return r.commentCreate.comment?.url??null}async function di(t){let e=await te(t,"query { viewer { name email } organization { name urlKey } }");return{user:e.viewer?.name??e.viewer?.email??null,org:e.organization?.name??e.organization?.urlKey??null}}async function ck(t,{filename:e,contentType:n,buffer:r}){let o=await te(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
34
|
+
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
35
|
+
success
|
|
36
|
+
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
37
|
+
}
|
|
38
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function lk(t,e){let n=await te(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function uk(t,e,n){if(!(await te(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function Vn(t){return((await te(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var jd=255;async function fi(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>jd,a=s?`${o.slice(0,jd-1).trimEnd()}\u2026`:o,i=await te(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function pi(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await te(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var Kd={linear:{api:mi,config:cn},jira:{api:ui,config:rs}};async function ki(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Kd)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function dn(t){let e=Kd[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function xi(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var Vd=()=>process.env.BOSTRAT_STATE_DIR||qd.join(process.env.HOME||dk.tmpdir(),".bostrat"),yi=()=>qd.join(Vd(),"tickets.json"),Jd=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),ss=t=>`${Jd()}/track/${t}`,os=null,hi=null,wi=Promise.resolve();async function as(){return os||(hi??=Ur.readFile(yi(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>os=t).finally(()=>hi=null),hi)}async function Ld(){let t=JSON.stringify({tickets:os},null,2),e=`${yi()}.${process.pid}.tmp`;await Ur.mkdir(Vd(),{recursive:!0}),await Ur.writeFile(e,t),await Ur.rename(e,yi())}async function $e(){wi=wi.then(Ld,Ld),await wi.catch(()=>{})}async function ee(t){return(await as())[t]??null}async function is(){return{...await as()}}async function zd(){return(await ki()).length>0}async function Jn(t,e,{source:n="api"}={}){let r=await ki();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let d=u.api.parseIssueRef(e);if(d)try{s=await u.api.resolveIssue(u.cfg,d),o=u;break}catch(f){a=f}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await as())[t.id]=c,await $e(),await C({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Hr(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await Fr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Zd(t.id,c,"linked"),c}async function Si(t){let e=await as();return e[t]?(delete e[t],await $e(),!0):!1}var bi=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Xd(t,e){if(!t.track?.cwd)return null;let n=Te(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function fk(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${bi(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${bi(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function pk(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(bi(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Hr(t,e){let n=await dn(e);if(!n)return;let r=await Xd(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:ss(t),title:`Bostrat Track: ${e.track.name}`,subtitle:pk(e,r)})}function cs(t){let e=new URL(Jd());return`${e.protocol}//${t}.${e.host}/`}var mk={linked:"linked",opened:"prOpened",merged:"prMerged"};function hk(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function Yd(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await $e(),await C({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Zd(t,e,n){let r=await dn(e);if(!r)return;let o=r.cfg.ticketFlow?.[mk[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=xi(s,a),c=hk(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await Yd(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await C({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Gd(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function Qd(t){let e=await ee(t);if(!e)throw new Error("no ticket linked to this Track");let n=await dn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=xi(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await $e()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function vi(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ee(t);if(!r)throw new Error("no ticket linked to this Track");let o=await dn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=xi(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await $e()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Yd(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function tf(t,e){let n=await ee(t);if(!n)throw new Error("no ticket linked to this Track");let r=await dn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await C({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function ef(t,e,n){if(!e)throw new Error("offerId required");let r=await F({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await vi(t.id,o.payload.to?.id,{mode:"offer"})),await C({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Fd=50;async function nf(){let t=await cn();if(!t)throw new Error("no ticketing integration configured");return Vn(t)}function wk(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>Fd)throw new Error(`too many issues (${n.length}); cap is ${Fd} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function gk(t,e,n,r){let o=r??await Vn(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await ee(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function rf(t,e){let n=await cn();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=wk(e),o=await Vn(n),s=await gk(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Gd();return await C({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function of(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await C({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await cn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await Vn(a)).find(m=>m.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],d=null;if(i.epic){let h=await fi(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});d=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let m=await pi(a,{teamId:c.id,title:h.title,description:h.description,projectId:d,priority:h.priority});l.push({kind:"issue",key:m.key,title:m.title,url:m.url})}catch(m){u.push({title:h.title,message:m?.message||String(m)})}await C({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await C({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let f=l.filter(h=>h.kind==="issue"),p=null;return i.linkCurrentTrack&&f.length===1&&!await ee(t.id)&&(p=await Jn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Fr(t,e){let n=await dn(t);n&&await n.api.createComment(n.cfg,t,e)}async function yk(t,e,n){let r=await dn(t);if(!r)return null;let o=await _e(e);if(!o)return null;let s=rn(o,n);if(!s)return null;let a=await Ur.readFile(jt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var Ud=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),gi=new Map;function bk(t,e){if(Ud()<=0)return Hr(t,e);if(gi.has(t))return;let n=setTimeout(()=>{gi.delete(t),ee(t).then(r=>r?Hr(t,r):null).catch(()=>{})},Ud());n.unref?.(),gi.set(t,n)}var Hd=new Map;async function Wd(t,e,n){let r=Hd.get(t)??0;Date.now()-r<600*1e3||(Hd.set(t,Date.now()),await C({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function kk(t,{resolveTrack:e=null}={}){let n=await ee(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await ki())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await Jn(s,o,{source:"chat"})}catch(a){await Wd(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await $e())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await $e(),await Hr(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await $e(),await Hr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Xd(n,t.trackId).catch(()=>null):null;await Fr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
39
|
+
|
|
40
|
+
Agent cost for this Track: ${fk(o)}.`:"")),await Zd(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await yk(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Fr(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
41
|
+
|
|
42
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await Fr(n,`**Bostrat** \u2014 design decision: **${r.name}** picked${o?` over ${o}`:""}.`)}break}case"view.state.changed":{if(r.change!=="created"||r.restarted||!r.slug)break;await Fr(n,`**Bostrat** \u2014 live preview **${r.name}**: ${cs(r.slug)}
|
|
43
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await bk(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Wd(t.trackId,s?.cwd??null,o)}}function sf({resolveTrack:t}){Dt.on("event",e=>{kk(e,{resolveTrack:t}).catch(()=>{})})}import ls from"node:fs/promises";import af from"node:path";import xk from"node:os";var cf=process.env.BOSTRAT_STATE_DIR||af.join(process.env.HOME||xk.tmpdir(),".bostrat"),Ti=af.join(cf,"pr-links.json"),st=(t,e)=>`${t}#${Number(e)}`,us=null,Ai=null,B=Promise.resolve();async function wt(){return us||(Ai??=ls.readFile(Ti,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>us=t).finally(()=>Ai=null),Ai)}async function X(){let t=JSON.stringify({links:us},null,2),e=`${Ti}.${process.pid}.tmp`;await ls.mkdir(cf,{recursive:!0}),await ls.writeFile(e,t),await ls.rename(e,Ti)}var Sk=new Set(["api","observed","reconciled"]);async function Mt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await wt(),c=st(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:Sk.has(a),firstSeen:Date.now()},B=B.then(X,X),await B.catch(()=>{}),{link:i[c],created:!0})}async function Wr(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||n.announced?null:(n.announced=!0,B=B.then(X,X),await B.catch(()=>{}),n)}async function St(t,e){return!t||e==null?null:(await wt())[st(t,e)]??null}async function Oe(t,e,n){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,B=B.then(X,X),await B.catch(()=>{}),r):null}async function lf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await wt())[st(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},B=B.then(X,X),await B.catch(()=>{}),o):null}async function uf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await wt())[st(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},B=B.then(X,X),await B.catch(()=>{}),o):null}async function ds(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,B=B.then(X,X),await B.catch(()=>{}),r)}async function fs(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,B=B.then(X,X),await B.catch(()=>{}),n)}async function ps(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,B=B.then(X,X),await B.catch(()=>{}),r)}async function ms(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,B=B.then(X,X),await B.catch(()=>{}),n)}async function df(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(r.baseFoldAt=n,B=B.then(X,X),await B.catch(()=>{}),r):null}async function ff(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,B=B.then(X,X),await B.catch(()=>{}),n)}async function pf(t,e,n){if(!t||e==null||!n)return null;let r=(await wt())[st(t,e)];return r?(r.lastHeadSha=n,B=B.then(X,X),await B.catch(()=>{}),r):null}async function qr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),B=B.then(X,X),await B.catch(()=>{}),r):null}async function zn(t,e,n=!0){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,B=B.then(X,X),await B.catch(()=>{}),r):null}async function hs(t,e,n=!0){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,B=B.then(X,X),await B.catch(()=>{}),r):null}async function mf(t,e,n){if(!t||e==null)return null;let r=(await wt())[st(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],B=B.then(X,X),await B.catch(()=>{}),r}async function Gr(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,B=B.then(X,X),await B.catch(()=>{}),n)}var vk=new Set(["merged","closed","lost"]);async function hf(){return Object.values(await wt()).filter(t=>!vk.has(t.state))}async function Ce(){return Object.values(await wt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var gf=t=>Math.round(t*100)/100;function Ak(t){return!t||t.kind==="setup"?[]:Rt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():wf.basename(e.base)}))}function yf(t){let e=new Map;for(let n of t||[])for(let r of Ak(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function bf(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var _i=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function Tk(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function Ei(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let d=u.payload||{};!d.repo||d.number==null||(d.change==="merged"?o.set(`${d.repo}#${d.number}`,d):d.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(d=>{let[f,p]=d.split("#");return`${f.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(_i(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(_i(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(d=>`${d.fromAgent}\u2192${d.toAgent}`).join(", ");n.push(`${_i(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=Te(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?Tk(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function _k(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function kf(t,e,n){let r=new Set(t.tracks.map(f=>f.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let f of t.tracks){f.archivedAt?s++:o++,f.beat?.text&&a++;let p=bf(f);p&&(!i||p>i)&&(i=p),f.createdAt&&(!c||f.createdAt<c)&&(c=f.createdAt),l+=e.get(f.id)||0}let u=0,d=0;for(let f of n)r.has(f.trackId)&&(t.slug&&f.repo!==t.slug||t.slug&&(f.state==="merged"?d++:f.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:d},usd:gf(l),firstActivityAt:c,lastActivityAt:i}}async function xf(t){let e=yf(t),n=await Ce().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await F(i).catch(()=>[]);r.set(i.id,Te(c).totalUsd)}let s=[...e.values()].map(a=>kf(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function Sf(t,e){let r=yf(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Ce().catch(()=>[]),Jo().catch(()=>[]),Qo().catch(()=>[]),ts().catch(()=>[]),is().catch(()=>({}))]),l=new Map,u=[];for(let d of[...r.tracks].sort((f,p)=>(p.createdAt||0)-(f.createdAt||0))){let f=await F(d).catch(()=>[]),p=Te(f);l.set(d.id,p.totalUsd);let h=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:_k(d),handoffs:(d.events||[]).filter(m=>m.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Rt(d.workspace).map(m=>m.repo||wf.basename(m.base||"")),prs:o.filter(m=>m.trackId===d.id&&(!r.slug||m.repo===r.slug)).map(m=>({repo:m.repo,number:m.number,state:m.state??null,title:m.title??null,url:m.url??null})),mocks:s.filter(m=>m.trackId===d.id).length,views:a.filter(m=>m.trackId===d.id).length,runbooks:i.filter(m=>m.trackId===d.id).map(m=>({name:m.name,status:m.status})),ticket:h?{provider:h.provider,key:h.key,url:h.url??null}:null,usd:gf(p.totalUsd),lastActivityAt:bf(d)})}return{...kf(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as Ek}from"node:child_process";import{promisify as Ik}from"node:util";import{randomUUID as Rk}from"node:crypto";var fn=Ik(Ek),ws=t=>new Promise(e=>setTimeout(e,t)),$k=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,vf=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,Ok=250,Ck=400,Af={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function _f(t){let e=await Yl(t);if(!e)return!1;try{return await fn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Ii(t,e){let n=Date.now()+e;for(;;){if(await _f(t))return!0;if(Date.now()>n)return!1;await ws(Ok)}}async function Mk(t,e){let n=Af[e]||Af.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await ws(150)),n.wipe&&await Mo(t,400,{kind:"swap-exit"}),await Po(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=ce(e).submitDelayMs||0;await ws(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function Pk(t,e){try{let{stdout:n}=await fn(P,N("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Tf(t){await fn(P,N("respawn-pane","-k","-t",t))}async function Ri(t,e={}){if(!pt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await z(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Mn(n),a=Number(e.exitTimeoutMs)||$k;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await _f(t)&&(await Mk(t,o),!await Ii(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Tf(t),!await Ii(t,vf))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Cn(n),l=c?Ro():null,u=await Pk(t,"ANTHROPIC_API_KEY"),d=!1;try{l?(await fn(P,N("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),d=!0):u&&(await fn(P,N("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),d=!0)}catch(b){throw l?xa(b,l):b}if(d&&!i&&(await Tf(t),!await Ii(t,vf)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let f=!!e.resumeSessionId,p=e.resumeSessionId||Rk(),h=f?n:Aa(n,p),m=f?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await ke(r.cwd),s==="codex"&&await Rn(r.cwd),s==="cursor"&&await $n(r.cwd),await en(t),await fn(P,N("set-option","-t",t,"@bostrat-sid",p)),await fn(P,N("set-option","-t",t,"@bostrat-agent",s)),await ws(Ck),await kt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:d}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:m,forced:i,envRespawned:d}}import Gk from"node:fs";import If from"node:fs/promises";import Kr from"node:path";import Kk from"node:os";import{fileURLToPath as Vk}from"node:url";import{execFile as Jk}from"node:child_process";import{promisify as zk}from"node:util";import Oi from"node:fs/promises";import Me from"node:path";import{execFile as Nk}from"node:child_process";import{promisify as Bk}from"node:util";var Dk=Bk(Nk),Ef=process.env.HOME||"/home/globo",jk=Me.join(process.env.BOSTRAT_STATE_DIR||Me.join(Ef,".bostrat"),"repos"),Lk=[...(process.env.BOSTRAT_REPOS_ROOT||Me.join(Ef,"Documents")).split(","),jk].map(t=>t.trim()).filter(Boolean),Fk=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Uk=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function Hk(t){try{return(await Oi.stat(Me.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function Wk(t){let e=i=>Oi.readFile(Me.join(t,".git",i),"utf8").then(c=>c.trim()).catch(()=>null),r=(await e("refs/remotes/origin/HEAD"))?.match(/^ref:\s*refs\/remotes\/origin\/(.+)/);if(r)return r[1].trim();let o;for(let i of["main","master"])if(await e(`refs/heads/${i}`)||(o??=await e("packed-refs")||"",new RegExp(`^[0-9a-f]{40} refs/heads/${i}$`,"m").test(o)))return i;let a=(await e("HEAD"))?.match(/^ref:\s*refs\/heads\/(.+)/);return a?a[1].trim():null}async function pe(){let t=new Map;async function e(n,r){if(t.has(n))return;let o=await Hk(n);if(o==="worktree")return;if(o==="repo"){t.set(n,{path:n,name:Me.basename(n),branch:await Wk(n)});return}if(r>=Fk)return;let s;try{s=await Oi.readdir(n,{withFileTypes:!0})}catch{return}for(let a of s)!a.isDirectory()||a.name.startsWith(".")||Uk.has(a.name)||await e(Me.join(n,a.name),r+1)}for(let n of Lk)await e(Me.resolve(n),0);return[...t.values()].sort((n,r)=>n.name.localeCompare(r.name))}function qk(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var $i=new Map;async function Y(t){if(!t)return null;if($i.has(t))return $i.get(t);let e=null;try{let{stdout:n}=await Dk("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=qk(n)}catch{e=null}return $i.set(t,e),e}var Rf=zk(Jk),Xk=process.env.BOSTRAT_STATE_DIR||Kr.join(process.env.HOME||Kk.tmpdir(),".bostrat"),Yk=Kr.join(Xk,"repos"),$f=/^[^/\s]+\/[^/\s]+$/,Zk=5*6e4,Qk=6e4,tx=10*6e4,Of=fetch,Ci=new Map,pn=null;function Mi(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function gs(t=process.env){return!!(Mi(t)&&t.BOSTRAT_PLANE_TOKEN)}var Cf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Xn=(t,e)=>Object.assign(new Error(t),{status:e});async function ys(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!$f.test(n))throw Xn("repo must be owner/name",400);let r=Ci.get(n);if(r&&r.expiresAt-Date.now()>Zk)return r;if(!gs(e))throw Xn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Of(`${Mi(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Cf(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Xn(`plane unreachable \u2014 GitHub auth unavailable (${i?.message||i})`,503)}let s=await o.json().catch(()=>({}));if(!o.ok){if(o.status>=500&&r&&r.expiresAt>Date.now())return r;throw Xn(s?.error||`plane replied ${o.status}`,o.status)}let a={token:s.token,expiresAt:Number(s.expiresAt)||Date.now()+55*6e4,username:s.username??null,installationId:s.installationId??null};return Ci.set(n,a),a}async function Vr({env:t=process.env,force:e=!1}={}){if(!gs(t))return{configured:!1,repos:[],username:null};if(!e&&pn&&Date.now()-pn.at<Qk)return pn.data;try{let n=await Of(`${Mi(t)}/v1/github/repos`,{headers:Cf(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return pn={at:Date.now(),data:r},r}catch(n){return pn?pn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Mf(){Ci.clear(),pn=null}async function ne({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Y(t):null);if(!r)return{};let{token:o}=await ys(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Pi({env:t=process.env}={}){return(await Vr({env:t})).username??null}var ex=()=>Vk(new URL("../bin/bostrat-git-credential",import.meta.url)),nx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function rx(t){let[e,n]=String(t).split("/");return Kr.join(Yk,e,n)}async function Ni(t,{env:e=process.env}={}){let n=(...r)=>Rf("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${ex()}" ${nx(e)}`),await n("config","credential.useHttpPath","true")}async function Bi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!$f.test(String(t||"")))throw Xn(`bad repo slug "${t}"`,400);let r=rx(t);if(Gk.existsSync(Kr.join(r,".git")))return r;let{token:o}=await ys(t,{env:e});await If.mkdir(Kr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Rf("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:tx,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await If.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Xn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ni(r,{env:e}),r}import Bx from"node:fs/promises";function ox(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
44
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function Jr(t){let e=[],n="",r=null,o=!1,s=()=>{if(o){if(/^\d*[<>]/.test(n)||n.startsWith(">")){n="",o=!1;return}e.push(n),n="",o=!1}};for(let a=0;a<t.length;a++){let i=t[a];if(r){i===r&&t[a-1]!=="\\"?r=null:n+=i,o=!0;continue}if(i==="'"||i==='"'){r=i,o=!0;continue}if(i==="<"&&t[a+1]==="<")return s(),e.push("<<"),e;if(/\s/.test(i)){s();continue}n+=i,o=!0}return s(),e}function zr(t){let e=t;for(;e.length&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(e[0]);)e=e.slice(1);for(;e.length&&(e[0]==="sudo"||e[0]==="time"||e[0]==="env");)e=e.slice(1);return e}var Zn=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,xt=t=>t&&!/[$`]/.test(t)?t:null,ut=t=>t.replace(/\/+$/,"").split("/").pop()||t,Yn=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},Bf=t=>t.startsWith("-"),Pf=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function Q(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(Bf(o)){e.has(o)&&r++;continue}n.push(o)}return n}var sx={status:()=>"Check git status",diff:()=>"Review changes",log:()=>"Review commit history",show:()=>"Inspect a commit",add:()=>"Stage files",commit:()=>"Commit changes",push:()=>"Push to remote",pull:()=>"Pull from remote",fetch:()=>"Fetch from remote",merge:()=>"Merge branches",rebase:()=>"Rebase branch",stash:()=>"Stash changes",clone:t=>t[1]?`Clone ${ut(t[1])}`:"Clone repository",checkout:()=>"Switch branches",switch:()=>"Switch branches",restore:()=>"Restore files",reset:()=>"Reset git state",branch:(t,e)=>e.includes("--show-current")?"Check current branch":"List branches",remote:()=>"Check git remotes","rev-parse":()=>"Resolve a git ref",worktree:()=>"Manage git worktrees","merge-base":()=>"Find the merge base","ls-files":()=>"List tracked files"},ax={pr:t=>({create:"Create a pull request",list:"List pull requests",view:"View a pull request",merge:"Merge a pull request",checks:"Check PR status"})[t[1]]||"Run a gh pr command",auth:()=>"Authenticate GitHub CLI",api:()=>"Call the GitHub API",repo:()=>"Inspect the GitHub repo",issue:()=>"Work with GitHub issues"};function Df(t){let e=t.indexOf("-n"),n=e!==-1?t[e+1]:null;if(!n)return null;let r=/^(\d+),(\d+)p$/.exec(n)||/^(\d+)p$/.exec(n);return r?r[2]?{from:r[1],to:r[2]}:{from:r[1],to:r[1]}:null}function ix(t){let e=Df(t),n=xt(Q(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${ut(n)}`:`Read lines ${e.from}\u2013${e.to} of ${ut(n)}`:t.includes("-i")&&n?`Edit ${ut(n)}`:null}function Di(t){if(t.includes("--files")){let s=xt(Q(t,Pf)[0]);return s&&s!=="."?`List files in ${Yn(s)}`:"List files"}let e=Q(t,Pf),n=e[0];if(!n)return null;let r=xt(e.slice(1).find(s=>s!==".")),o=Zn(n,40);return r?`Search for \u201C${o}\u201D in ${Yn(r)}`:`Search for \u201C${o}\u201D`}function ji(t){let e=Q(t,new Set(["--prefix"])),n=e[0];return n?n==="test"||n==="t"?"Run tests":n==="run"||n==="run-script"?e[1]?`Run ${e[1]}`:null:n==="ci"||n==="install"||n==="i"||n==="add"?"Install dependencies":null:null}function Li(t,e){if(t.includes("-e")||t.includes("--eval")||t.includes("-c")||t[1]==="-"||t.includes("<<"))return`Run an inline ${e} script`;if(t.includes("--test"))return"Run tests";let n=Q(t)[0];return n?`Run ${ut(n)}`:null}function cx(t){let e=Q(t),n=e[0],r=e.find(a=>/\.(service|timer|socket|target)$/.test(a))||e[1],o=r?r.replace(/\.service$/,""):"a service";return{status:`Check ${o} status`,show:`Inspect ${o}`,restart:`Restart ${o}`,start:`Start ${o}`,stop:`Stop ${o}`,"daemon-reload":"Reload systemd"}[n]||null}function Nf(t){return t.find(e=>/^https?:\/\//.test(e))||null}var lx={git:t=>{let e=Q(t,new Set(["-C","-c"])),n=sx[e[0]];return n?n(e,t):null},gh:t=>{let e=Q(t),n=ax[e[0]];return n?n(e):null},sed:ix,rg:Di,grep:Di,egrep:Di,nl:t=>{let e=xt(Q(t)[0]);return e?`Read ${ut(e)}`:null},cat:t=>{let e=xt(Q(t)[0]);return e?`Read ${ut(e)}`:null},head:t=>{let e=xt(Q(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${ut(e)}`:null},tail:t=>{let e=xt(Q(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${ut(e)}`:null},wc:t=>{let e=xt(Q(t)[0]);return e?`Count lines in ${ut(e)}`:"Count input lines"},ls:t=>{let e=xt(Q(t)[0]);return e&&e!=="."?`List files in ${Yn(e)}`:"List files"},find:t=>xt(t[1])&&!Bf(t[1])&&t[1]!=="."?`Find files in ${Yn(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=xt(Q(t)[0]);return e?`Create directory ${Yn(e)}`:null},touch:t=>{let e=xt(Q(t)[0]);return e?`Create ${ut(e)}`:null},rm:t=>{let e=xt(Q(t)[0]);return e?`Delete ${Yn(e)}`:null},mv:t=>{let e=xt(Q(t)[0]);return e?`Move ${ut(e)}`:null},cp:t=>{let e=xt(Q(t)[0]);return e?`Copy ${ut(e)}`:null},chmod:t=>{let e=xt(Q(t)[1]);return e?`Change permissions on ${ut(e)}`:null},diff:t=>{let e=Q(t);return e.length>=2?`Compare ${ut(e[0])} and ${ut(e[1])}`:null},curl:t=>{let e=Nf(t);return e?`Fetch ${Zn(e,56)}`:"Fetch a URL"},wget:t=>{let e=Nf(t);return e?`Fetch ${Zn(e,56)}`:"Fetch a URL"},npm:ji,pnpm:ji,yarn:ji,npx:t=>{let e=Q(t)[0];return e?`Run ${e}`:null},node:t=>Li(t,"Node"),python:t=>Li(t,"Python"),python3:t=>Li(t,"Python"),make:t=>{let e=Q(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:cx,journalctl:t=>{let e=t.findIndex(r=>r==="-u"||r==="--unit"||r==="--user-unit"),n=e!==-1?t[e+1]:null;return n?`Read ${n.replace(/\.service$/,"")} logs`:"Read system logs"},tmux:t=>{let e=Q(t,new Set(["-S","-L","-t","-f"]))[0];return{ls:"List tmux sessions","list-sessions":"List tmux sessions","kill-session":"Kill a tmux session","send-keys":"Send keys to a tmux session","capture-pane":"Capture a tmux pane","new-session":"Start a tmux session"}[e]||null},ps:()=>"List processes",which:t=>{let e=Q(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=Q(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},ux=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function dx(t){let e=zr(Jr(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(ut(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Fi(e[2]);let n=ut(e[0]);if(ux.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=lx[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Fi(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
45
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=ox(e);for(let i=0;i<r.length-1;i++){let c=zr(Jr(r[i])),l=zr(Jr(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(ut(c[0]))&&ut(l[0])==="sed"){let u=Df(l),d=Q(c)[0];if(u&&d)return Zn(u.from===u.to?`Read line ${u.from} of ${ut(d)}`:`Read lines ${u.from}\u2013${u.to} of ${ut(d)}`,88)}}let o=[];for(let i of r){let c=dx(i);c&&o.push({d:c,head:ut(zr(Jr(i))[0]||""),sub:Q(zr(Jr(i)))[0]})}if(!o.length)return null;let s=o.find(i=>i.head==="git"&&i.sub==="commit"),a=o.find(i=>i.head==="git"&&i.sub==="push");return s&&a?"Commit and push changes":Zn(s?s.d:o[0].d,88)}var Hi=new Set(["Bash"]);function Qn(t){let e=t.split(`
|
|
46
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function Pe(t,e=""){let n=e.trim();return n||Fi(t)||Qn(t)}function Ui(t,e){if(t){if(Array.isArray(t)){for(let n of t)Ui(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&Hi.has(t.name)){let n=String(t.input?.command||"").trim();n&&e.push({id:t.id||null,tool:t.name,command:n,description:String(t.input?.description||"").trim()})}for(let n of Object.values(t))Ui(n,e)}}}async function jf(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Tt(t,e)}catch{return[]}let o=[];for(let s of r.split(`
|
|
47
|
+
`)){let a=s.trim();if(!a||a[0]!=="{")continue;let i;try{i=JSON.parse(a)}catch{continue}let c=i.message;if(!c)continue;let l=[];if(Ui(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let d of l)o.push({...d,summary:Pe(d.command,d.description),ts:u})}return o.slice(-n)}function Lf(t,e){let n=e.filter(l=>l.role==="assistant"&&(l.text||"").trim()).sort((l,u)=>(l.ts||0)-(u.ts||0)),r=[...t].sort((l,u)=>(l.ts||0)-(u.ts||0)),o=[],s=0,a=0,i=l=>{let u=l.n??a+1;return u>a&&(a=u),u};for(let l of n){let u=r.findIndex((h,m)=>m>=s&&(h.ts||0)>(l.ts||0)),d=u===-1?r.length:u,f=r.slice(s,d);if(s=d,!f.length)continue;let p=f.map(h=>({...h,n:i(h)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:p,ts:Math.max(...p.map(h=>h.ts||0)),pending:!1})}let c=r.slice(s);if(c.length){let l=c.map(u=>({...u,n:i(u)}));o.push({id:`batch-pending-${l[0]?.box||"lead"}`,anchorTs:null,anchorBox:l[l.length-1]?.box??null,agent:l[l.length-1]?.agent??"claude",boxRole:l[l.length-1]?.boxRole??"lead",commands:l,ts:Math.max(...l.map(u=>u.ts||0)),pending:!0})}return o}var fx=/^Async (?:agent|task) launched/i,px=t=>{if(typeof t=="string")return t.trim();if(!Array.isArray(t))return"";let e=t.find(n=>n&&n.type==="text"&&typeof n.text=="string");return e?e.text.trim():""};async function Ff(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Tt(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
48
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let d=l.timestamp?Date.parse(l.timestamp):null;for(let f of u)if(!(!f||typeof f!="object"))if(f.type==="tool_use"&&f.id){if(f.name==="Agent"){let p=f.input||{};o.set(f.id,{id:f.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:d,proven:!1})}else if(f.name==="Bash"&&f.input?.run_in_background===!0){let p=String(f.input.command||"").trim();o.set(f.id,{id:f.id,kind:"command",name:String(f.input.description||"").trim()||(p?Qn(p):null),detail:p?Qn(p):null,ts:d,proven:!0})}}else f.type==="tool_result"&&f.tool_use_id&&fx.test(px(f.content))&&s.add(f.tool_use_id)}let a=[];for(let i of o.values()){if(!i.proven&&!s.has(i.id))continue;let{proven:c,...l}=i;a.push(l)}return a.slice(-n)}import Xr from"node:fs";import hx from"node:fs/promises";import mn from"node:path";import wx from"node:os";import gx from"node:crypto";import yx from"node:zlib";var bs=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),qt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,tr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function Ne(t,e){if(!t)return null;let n=String(t);if(e){let o=e.endsWith("/")?e:`${e}/`;if(n===e)return".";if(n.startsWith(o))return n.slice(o.length)||"."}let r=n.split("/").filter(Boolean);return n.startsWith("/")&&r.length>2?r.slice(-2).join("/"):n}var Wi=t=>`"${String(t).replace(/"/g,'\\"')}"`;function mx(t){let e=typeof t=="string"?t:String(t?.patch??t?.input??""),n=[];for(let r of e.matchAll(/^\*{3} (?:Update|Add|Delete) File: (.+)$/gm))n.push(r[1].trim());return n}function Uf(t,e,n){switch(t){case"Shell":{let r=String(e?.command||"").trim();return r?{command:r,description:String(e?.description||"").trim()}:null}case"Read":{let r=e?.path;if(!r)return null;let o=Number(e?.offset)||null,s=o&&Number(e?.limit)?o+Number(e.limit)-1:null;return{command:qt(`read ${Ne(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${tr(r)}`:`Read ${tr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?Ne(e.path,n):null,s=e?.glob?` --glob ${Wi(e.glob)}`:"",a=qt(String(r),40);return{command:qt(`grep ${Wi(r)}${o&&o!=="."?` ${o}`:""}${s}`,140),description:o&&o!=="."?`Search for \u201C${a}\u201D in ${o}`:`Search for \u201C${a}\u201D`}}case"Glob":{let r=e?.glob_pattern;if(!r)return null;let o=e?.target_directory?Ne(e.target_directory,n):null;return{command:qt(`glob ${Wi(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${qt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:qt(`edit ${Ne(e.path,n)}`,140),description:`Edit ${tr(e.path)}`}:null;case"Write":return e?.path?{command:qt(`write ${Ne(e.path,n)}`,140),description:`Write ${tr(e.path)}`}:null;case"Delete":return e?.path?{command:qt(`delete ${Ne(e.path,n)}`,140),description:`Delete ${tr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>Ne(s,n)).filter(Boolean);return{command:qt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=mx(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:qt(`apply_patch ${Ne(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${tr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:qt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var bx=process.env.BOSTRAT_CURSOR_CHATS||mn.join(wx.homedir(),".cursor","chats");function qf(t){let e=gx.createHash("md5").update(String(t)).digest("hex");return mn.join(bx,e)}function Gf(t){try{let e=JSON.parse(Xr.readFileSync(mn.join(t,"meta.json"),"utf8"));return{createdAtMs:Number(e.createdAtMs)||null,updatedAtMs:Number(e.updatedAtMs)||Number(e.createdAtMs)||null,hasConversation:e.hasConversation===!0}}catch{return null}}function kx(t){let e=qf(t),n;try{n=Xr.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=mn.join(e,o.name),a=mn.join(s,"store.db");if(!Xr.existsSync(a))continue;let i=Gf(s);if(!i)continue;let c={id:o.name,dir:s,store:a,...i};(!r||c.hasConversation&&!r.hasConversation||c.hasConversation===r.hasConversation&&(c.updatedAtMs||0)>(r.updatedAtMs||0))&&(r=c)}return r}function ks(t,e){if(!t)return null;if(e){let n=mn.join(qf(t),e),r=mn.join(n,"store.db");if(Xr.existsSync(r))return{id:e,dir:n,store:r,...Gf(n)||{createdAtMs:null,updatedAtMs:null}}}return kx(t)}function Hf(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function xx(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=yx.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function Sx(t){let e=[],n=0,r=t.length,o=()=>{let s=0,a=0;for(;n<r;){let i=t[n++];if(s+=(i&127)*2**a,!(i&128))break;a+=7}return s};for(;n<r;){let s=o(),a=s>>>3,i=s&7;if(i===2){let c=o(),l=t.subarray(n,n+c);n+=c,a===1&&c===32&&e.push(l.toString("hex"))}else if(i===0)o();else if(i===5)n+=4;else if(i===1)n+=8;else break}return e}function vx(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!bs.has(r.toolName))continue;let o=Uf(r.toolName,r.args,e);!o||!o.command||n.push({id:r.toolCallId||null,tool:r.toolName,command:o.command,description:o.description||""})}return n}var Ax=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
49
|
+
`).trim():"",Tx=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Wf=process.getBuiltinModule?.("node:sqlite"),er={turns:[],trailingCommands:[],model:null,requestIds:[]};async function qi(t,e){if(!Wf)return er;let{DatabaseSync:n}=Wf,r;try{r=new n(t,{readOnly:!0})}catch{return er}try{let o=r.prepare("SELECT value FROM meta WHERE key = '0'").get()||r.prepare("SELECT value FROM meta LIMIT 1").get();if(!o)return er;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return er}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Hf(r,s.latestRootBlobId):null;if(!i)return{...er,model:a};let c=[],l=[],u=[],d=null,f=[];for(let p of Sx(i)){let h=xx(Hf(r,p));if(!h)continue;let m=h.providerOptions?.cursor;if(m?.requestId&&f.push(m.requestId),m?.modelProviderMessageId&&f.push(m.modelProviderMessageId),h.role!=="assistant")continue;if(Array.isArray(h.content))for(let v of h.content){let E=v?.providerOptions?.cursor?.modelName;E&&(d=E)}l.push(...vx(h.content,e).map((v,E)=>({...v,id:v.id||`${p}:${E}`}))),u.push(...Tx(h.content));let b=Ax(h.content);if(!b)continue;let k=[...new Set(u)];u=[],c.push({id:p,text:b,tools:k.length?k:void 0,commands:l,model:d}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:f}}catch{return er}finally{try{r.close()}catch{}}}function Kf(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(Xr.statSync(t.store).mtimeMs)}catch{n=Date.now()}let r=t.createdAtMs;(!r||r>n)&&(r=n-e*1e3);let o=Math.max(0,n-r);return{end:n,tsAt:s=>Math.round(r+o*(s+1)/e)}}async function Vf(t,e,{limit:n=80}={}){let r=ks(t,e);if(!r)return[];let{turns:o,model:s}=await qi(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Kf(r,a.length);return a.map((c,l)=>({role:"assistant",text:c.text,tools:c.tools,ts:i(l),model:c.model||s,sourceId:c.id}))}async function Jf(t,e){let n=ks(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await qi(n.store,t);return{chatId:n.id,requestIds:o,models:[...new Set(r.map(a=>a.model).filter(Boolean))],configuredModel:s,updatedAtMs:n.updatedAtMs||null}}async function zf(t,e,{limit:n=80}={}){let r=ks(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await qi(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Kf(r,a.length||1),l=[];a.forEach((u,d)=>{for(let f of u.commands)l.push({...f,summary:Pe(f.command,f.description),ts:c(d)})});for(let u of s)l.push({...u,summary:Pe(u.command,u.description),ts:i+1});return l}async function Xf(t,e){let n=ks(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await hx.stat(n.store)).mtimeMs)}catch{return null}}import Yf from"node:fs";import Ki from"node:fs/promises";import Gi from"node:path";import _x from"node:os";var Ex=process.env.BOSTRAT_CODEX_SESSIONS||Gi.join(process.env.CODEX_HOME||Gi.join(_x.homedir(),".codex"),"sessions");async function Zf(t){try{return await Ki.realpath(t)}catch{return t}}function Ix(t=Ex){let e=[],n=r=>{let o;try{o=Yf.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=Gi.join(r,s.name);if(s.isDirectory())n(a);else if(s.isFile()&&s.name.startsWith("rollout-")&&s.name.endsWith(".jsonl")){let i=/-([0-9a-f-]{36})\.jsonl$/i.exec(s.name);try{e.push({id:i?i[1]:null,file:a,mtimeMs:Yf.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function Rx(t,e=1048576){let n;try{n=await Ki.open(t,"r")}catch{return""}try{let r=Buffer.alloc(Math.min(e,65536)),o="",s=0;for(;;){let{bytesRead:a}=await n.read(r,0,r.length,s);if(!a)break;o+=r.toString("utf8",0,a);let i=o.indexOf(`
|
|
50
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function $x(t){let e=await Rx(t);if(!e)return null;let n;try{n=JSON.parse(e)}catch{return null}return n?.type==="session_meta"&&n?.payload?.cwd||null}async function hn(t,e){if(!t&&!e)return null;let n=Ix();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Zf(t);for(let o of n){let s=await $x(o.file);if(s&&(s===t||await Zf(s)===r))return o}return null}var Ox=new Set(["output_text","text"]),Cx=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&Ox.has(e.type)).map(e=>e.text).join(`
|
|
51
|
+
`).trim():"";async function Qf(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await hn(t,e);if(!o)return[];let s;try{s=await Tt(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,d=null;for(let f of s.split(`
|
|
52
|
+
`)){let p=f.trim();if(!p||p[0]!=="{")continue;let h;try{h=JSON.parse(p)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let v=h.payload?.type;v==="task_started"?(l=!0,u=!1,d=h.payload?.turn_id||null):v==="task_complete"&&l&&(l=!1,!u&&h.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:Ji,sourceId:h.payload?.turn_id||d||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i}));continue}if(h.type!=="response_item")continue;let m=h.payload;if(!m)continue;if((m.type==="function_call"||m.type==="custom_tool_call")&&m.name){u=!0,Vi.has(m.name)||c.push(m.name);continue}if(m.type!=="message"||m.role!=="assistant")continue;u=!0;let b=Cx(m.content),k=c.length?c.slice():null;c=[],!(!b&&!k)&&a.push({role:"assistant",text:b,tools:k||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i})}if(i)for(let f of a)f.model||(f.model=i);return a.slice(-n)}var Yr=new Set(["exec_command","shell","exec"]),Vi=new Set(["wait","write_stdin"]),Ji="Codex ended the turn without a response \u2014 it hit an error the CLI shows only in the terminal (e.g. quota or billing). Check the Terminal tab.";function Mx(t){if(typeof t?.cmd=="string"&&t.cmd.trim())return t.cmd.trim();let e=t?.command;return typeof e=="string"&&e.trim()?e.trim():Array.isArray(e)&&e.length?e.length===3&&/^(ba)?sh$/.test(e[0])&&e[1]==="-lc"?String(e[2]).trim():e.map(String).join(" ").trim():null}function Px(t){let e=t[0];if(e!=='"'&&e!=="'"&&e!=="`")return null;let n="";for(let r=1;r<t.length;r++){let o=t[r];if(o==="\\"){let s=t[++r];if(s===void 0)return null;if(s==="n")n+=`
|
|
53
|
+
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function Nx(t){let e=String(t||""),n=[],r=/exec_command\s*\(/g;for(let s;s=r.exec(e);)n.push(s.index+s[0].length);let o=[];return n.forEach((s,a)=>{let i=e.slice(s,a+1<n.length?n[a+1]:e.length),c=/(?:^|[{,\s])["'`]?cmd["'`]?\s*:\s*/.exec(i);if(!c)return;let l=Px(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function tp(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await hn(t,e);if(!o)return[];let s;try{s=await Tt(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
54
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let d=[];if(u.type==="function_call"&&Yr.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let h=Mx(p);h&&(d=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(d=Nx(u.input));if(!d.length)continue;let f=u.call_id||u.id||null;d.forEach((p,h)=>{a.push({id:f?h?`${f}:${h}`:f:null,tool:u.name,command:p,description:"",summary:Pe(p),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function ep(t,e){let n=await hn(t,e);if(!n)return null;try{return Math.round((await Ki.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var nr=class{constructor(e,n=new Set){this.agent=e,this.cliTools=n}async readMessages(){return[]}async readCommands(){return[]}async readJobs(){return[]}async updatedAt(){return null}},zi=class extends nr{transcript(e){return!e?.cwd||!e?.sessionId?null:ie(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Ma(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?jf(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Ff(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await Bx.stat(n)).mtimeMs)}catch{return null}}},Xi=class extends nr{async readMessages(e,n){return Vf(e.cwd,e.sessionId,n)}async readCommands(e,n){return zf(e.cwd,e.sessionId,n)}async updatedAt(e){return Xf(e.cwd,e.sessionId)}},Yi=class extends nr{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Qf(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return tp(e.cwd,this.sessionId(e),n)}async updatedAt(e){return ep(e.cwd,this.sessionId(e))}},Dx=new nr("unknown"),np=new Map;function Zi(t){return np.set(t.agent,t),t}function Zr(t){return np.get(t)||Dx}Zi(new zi("claude",Hi));Zi(new Xi("cursor",bs));Zi(new Yi("codex",Yr));var Qi=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function rp(t,e={}){return(await Zr(t.agent).readMessages(t,e)).map(Qi(t))}async function op(t,e={}){return(await Zr(t.agent).readCommands(t,e)).map(Qi(t))}async function sp(t,e={}){return(await Zr(t.agent).readJobs(t,e)).map(Qi(t))}var jx=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,ap=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function Lx(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
55
|
+
`);if(!ap.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(jx);if(u)o.unshift({n:Number(u[1]),label:u[2]}),s=!0;else{if(s&&r[l].trim()==="")continue;if(s){var a=r[l].trim();break}}}if(o.length<2)return null;let i=o.sort((l,u)=>l.n-u.n);if(i.some((l,u)=>l.n!==u+1))return null;let c=typeof a=="string"&&ap.test(a)?a:"The agent is asking for approval";return{provider:e,box:n,promptId:`${e}:${c}:${i.map(l=>l.label).join("|")}`.slice(0,200),questions:[{question:c,header:"Approval",multiSelect:!1,options:i.map(l=>({label:l.label,description:""}))}]}}var xs=/(^|\n)\s*›/,ip=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function Fx(t,e=""){let n=String(t);if(!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n)||xs.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
56
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(ip);if(!u)continue;s<0&&(s=l);let d="";for(let f=l+1;f<Math.min(l+3,r.length);f++){let p=r[f].trim();if(p){if(ip.test(r[f])||/^Press enter/.test(p))break;d=p;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:d})}let a=o.sort((l,u)=>l.n-u.n);if(a.length<2||a.some((l,u)=>l.n!==u+1)||!a.some(l=>/Sign in with ChatGPT/.test(l.label)))return null;let i=[];for(let l=0;l<s;l++){let u=r[l].trim();u&&!/Welcome to Codex/.test(u)&&i.push(u)}let c=i.join(" ")||"Codex isn't signed in on this box \u2014 choose a sign-in method";return{provider:"codex",box:e,multiSelect:!1,cursorIndex:Math.max(0,a.findIndex(l=>l.cursor)),answerMode:"keys",promptId:`codex:signin:${a.map(l=>l.label).join("|")}`.slice(0,200),questions:[{question:c,header:"Sign in",multiSelect:!1,options:a.map(l=>({label:l.label,description:l.desc}))}]}}var cp=/Enter this one-time code/,Ux=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function Hx(t,e=""){let n=String(t);if(!cp.test(n)||xs.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
57
|
+
`),o=r.findIndex(i=>cp.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Ux);if(c){s=c[1];break}}if(!s)return null;let a=(n.match(/https:\/\/\S+/)||[])[0]||"https://auth.openai.com/codex/device";return{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:`codex:device-code:${s}`,questions:[{question:`Codex is waiting for you to finish signing in: open ${a} and enter code ${s} (expires in 15 minutes). The code goes straight to OpenAI \u2014 bostrat never sees your login. This card clears once OpenAI confirms.`,header:"Sign in",multiSelect:!1,options:[{label:"Cancel sign-in",description:"Go back to the sign-in options"}]}]}}function Wx(t,e=""){let n=String(t);return!/open the following link to authenticate/.test(n)||xs.test(n)?null:{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:"codex:signin-browser",questions:[{question:"Codex started its browser sign-in, which opens on the box itself and can't complete from another device. Go back and choose \u201CSign in with Device Code\u201D instead.",header:"Sign in",multiSelect:!1,options:[{label:"Go back to sign-in options",description:""}]}]}}function qx(t,e=""){let n=String(t);return!/Paste or type your API key/.test(n)||xs.test(n)?null:{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:"codex:signin-apikey",questions:[{question:"Codex is asking for an OpenAI API key. For safety a key can only be typed in the Terminal tab, never through chat. To use your ChatGPT subscription instead, go back and pick a sign-in option.",header:"Sign in",multiSelect:!1,options:[{label:"Go back to sign-in options",description:""}]}]}}var tc=/Run Everything|Add a follow-up|Plan, search, build anything/;function Gx(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||tc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
58
|
+
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var Kx=/click this link to log in|Signing in with the browser/;function Vx(t,e=""){let n=String(t);if(!Kx.test(n)||tc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
59
|
+
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function Jx(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||tc.test(n)?null:{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:"cursor:signin-qr",questions:[{question:"Cursor is showing a QR sign-in code in the Terminal tab \u2014 scan it from another device to log in. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function lp(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=Hx(e,n)||Wx(e,n)||qx(e,n)||Fx(e,n);if(r)return r}if(t==="cursor"){let r=Vx(e,n)||Jx(e,n)||Gx(e,n);if(r)return r}return Lx(e,{provider:t,box:n})}function up(t,e){let n=Array.isArray(e?.[0])?e[0]:[],o=(Number.isInteger(n[0])&&n[0]>=0?n[0]:0)+1;return String(o)}var pp=/to navigate\b.*\bEsc to cancel|Enter to select\b/,rc=/Chat about this/i,wn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,dp=/^(type something|chat about this|submit)\b/i,gn=/^[─—-]{6,}\s*$/,oc=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function yn(t){let e=t.search(oc);return(e>=0?t.slice(0,e):t).trim()}function zx(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function Xx(t){let e=String(t).trim();return!!(!e||gn.test(t)||oc.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function Yx(t,e=""){let n=[],r=/([☐☒])\s+([^☐☒←→✔]+)/g,o;for(;(o=r.exec(t))!==null;)n.push({label:o[2].trim(),answered:o[1]==="\u2612",current:!1});if(!n.length)return null;let s=Zx(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function Zx(t,e){let n=String(e).toLowerCase();if(n){let o=t.map((s,a)=>({i:a,label:s.label.toLowerCase()})).sort((s,a)=>a.label.length-s.label.length);for(let{i:s,label:a}of o){if(a.split(/\s+/).filter(l=>l.length>3).some(l=>n.includes(l)))return s;let c=a.slice(0,8).trim();if(c.length>=4&&n.includes(c))return s}}let r=t.findIndex(o=>!o.answered);return r>=0?r:t.length-1}function fp(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var mp=/^\s*Switch model\?\s*$/m;function Qx(t,e=""){let n=String(t);if(!mp.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
60
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let d=r[u].match(wn);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!gn.test(r[u])&&a.push(f)}}if(s.length<2)return null;let i=s.map(u=>({label:yn(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=["Switch model?",...a].join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:model-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Model",multiSelect:!1,options:i}]}}var hp=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,t0=/^\s*[─—▔-]{6,}\s*$/;function e0(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
61
|
+
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!hp.test(n[r]))return null;let o=r-1;for(;o>=0&&n[o].trim()==="";)o--;let s=[];for(;o>=0;o--){let c=n[o].match(wn);if(!c)break;s.unshift({cursor:!!c[1],n:Number(c[2]),label:c[4].trim()})}if(s.length<2||s.some((c,l)=>c.n!==l+1))return null;let a=[];for(let c=o;c>=0&&a.length<6&&!(t0.test(n[c])||gn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:yn(c.label),description:""}));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,s.findIndex(c=>c.cursor)),promptId:`claude:confirm-gate:${i.map(c=>c.label).join("|")}`.slice(0,240),questions:[{question:a.join(" ")||"Claude Code is asking you to confirm",header:"Confirm",multiSelect:!1,options:i}]}}function n0(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
62
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(wn);l&&o.push({cursor:!!l[1],label:l[4].trim()})}let s=o.filter(c=>/^(submit answers|cancel)$/i.test(c.label));if(s.length<2)return null;let a=s.map(c=>({label:c.label,description:""})),i=Math.max(0,s.findIndex(c=>c.cursor));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:i,review:!0,promptId:"claude:review:submit-answers",questions:[{question:"Ready to submit your answers?",header:"Review",multiSelect:!1,options:a}]}}var ec=/Resume from summary/i,r0=/^\s*This session is\b/;function o0(t,e=""){let n=String(t);if(!ec.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
63
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(r0.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[r[o].trim()];for(let u=o+1;u<r.length;u++){let d=r[u].match(wn);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!gn.test(r[u])&&a.push(f)}}if(s.length<2||!s.some(u=>ec.test(u.label)))return null;let i=s.map(u=>({label:yn(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=a.join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:resume-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Resume",multiSelect:!1,options:i}]}}var bn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function sc(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(wn);o&&e.push({line:r,cursor:!!o[1],n:Number(o[2]),label:o[4].trim()})}let n=e.sort((r,o)=>r.n-o.n);return n.length<2||n.some((r,o)=>r.n!==o+1)?null:n}var wp=/Choose the text style that looks best with your terminal/,gp=/Let's get started\./;function s0(t,e=""){let n=String(t);if(!wp.test(n)||!gp.test(n)||bn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
64
|
+
`),o=sc(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"theme",promptId:`claude:setup:theme:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks: choose the text style that looks best with your terminal. You can change it later with /theme.",header:"Theme",multiSelect:!1,options:o.map(s=>({label:yn(s.label),description:""}))}]}:null}var yp=/Select login method:/;function a0(t,e=""){let n=String(t);if(!yp.test(n)||bn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
65
|
+
`),o=sc(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:yn(i),description:c.join("\xB7").trim()}});return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(a=>a.cursor)),setup:"login-method",promptId:`claude:setup:login-method:${s.map(a=>a.label).join("|")}`.slice(0,200),questions:[{question:"How do you want to sign in to Claude? A Claude subscription (Pro/Max/Team) uses your existing plan; a Console account bills API usage. Your login stays on this box \u2014 bostrat never sees it.",header:"Log in",multiSelect:!1,options:s}]}}var ac=/Paste code here if prompted/,i0=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function c0(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!i0.test(o)||ac.test(t[r]))break;n+=o}return n}function l0(t,e=""){let n=String(t);if(!ac.test(n)||bn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
66
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=c0(r,s);break}return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,answerMode:"text",setup:"oauth",url:o,inputLabel:"Paste your sign-in code",promptId:"claude:setup:oauth",questions:[{question:"Open the sign-in link, approve access, then paste the code you're shown back here. The code goes straight to the Claude CLI on your box \u2014 bostrat relays the keystrokes and never stores it.",header:"Log in",multiSelect:!1,options:[]}]}:null}var nc=/OAuth error:/,u0=/Press Enter to retry\./;function d0(t,e=""){let n=String(t);if(!nc.test(n)||!u0.test(n)||bn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
67
|
+
`)].reverse().find(a=>nc.test(a))||"OAuth error").trim();return{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,setup:"oauth-error",promptId:`claude:setup:oauth-error:${s}`.slice(0,200),questions:[{question:`${s} \u2014 retry to get a fresh sign-in link.`,header:"Log in",multiSelect:!1,options:[{label:"Retry sign-in",description:"Mints a new sign-in link and code"}]}]}}var bp=/Quick safety check: Is this a project you created or one you trust/;function f0(t,e=""){let n=String(t);if(!bp.test(n)||bn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
68
|
+
`),o=sc(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"trust",promptId:`claude:setup:trust:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks whether it can read, edit, and run files in this workspace. This box's workspace is bostrat-managed scratch space, so trusting it is safe.",header:"Folder trust",multiSelect:!1,options:o.map(s=>({label:yn(s.label),description:""}))}]}:null}var kp=/^\s*Press Enter to continue\s*$/;function p0(t,e=""){let n=String(t);if(bn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
69
|
+
`),o=-1;for(let i=r.length-1;i>=0;i--)if(r[i].trim()!==""){o=i;break}if(o<0||!kp.test(r[o]))return null;let s=[];for(let i=o-1;i>=0&&s.length<3;i--){let c=r[i].trim();if(!c){if(s.length)break;continue}if(gn.test(r[i])||oc.test(c))break;s.unshift(c)}let a=s.join(" ")||"Claude Code is waiting for you to continue.";return{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,setup:"continue",promptId:`claude:setup:continue:${a}`.slice(0,200),questions:[{question:a,header:"Continue",multiSelect:!1,options:[{label:"Continue",description:""}]}]}}function m0(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(rc.test(t[r])){e=r;break}if(e<0)return null;let n=-1;for(let r=e-1;r>=0;r--)if(/[☐☒]/.test(t[r])){n=r;break}return n<0?null:{headerLine:n,chatLine:e,optStart:n+1,optEnd:e}}function h0(t,e=""){let n=String(t);if(!pp.test(n)||!rc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
70
|
+
`),o=m0(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let _=i;_<c;_++){let U=r[_].match(wn);U&&(u<0&&(u=_),l.push({line:_,cursor:!!U[1],checked:U[3]!=null&&U[3]!==" ",hasCheckbox:U[3]!=null,label:U[4].trim(),desc:""}))}if(!l.length)return null;for(let _ of l)for(let U=_.line+1;U<Math.min(r.length,_.line+4)&&!(wn.test(r[U])||gn.test(r[U]));U++){let V=r[U].trim();if(!(!V||Xx(r[U])||dp.test(V))){_.desc=zx(V);break}}let d=l.filter(_=>!dp.test(_.label));if(!d.length)return null;let f=d.some(_=>_.hasCheckbox),p=Math.max(0,d.findIndex(_=>_.cursor)),h=s>=0?s+1:0,m=[];for(let _=h;_<u;_++){let U=r[_].trim();U&&!gn.test(r[_])&&!/[☐☒]/.test(r[_])&&m.push(U)}let b=m.join(" ")||(s>=0?fp(r[s]):"")||"The agent is asking a question",k=s>=0?Yx(r[s],b):null,v=k?.currentTabLabel||(s>=0?fp(r[s]):""),E=d.map(_=>({label:yn(_.label),description:_.desc})),M=E.map(_=>_.label).join("|"),I=`claude:${k?`q${k.questionIndex+1}/${k.questionTotal}`:"q1/1"}:${v}:${b}:${M}`.slice(0,240),$=k?`claude:ask:${k.tabs.map(_=>_.label).join("|")}`.slice(0,240):I;return{provider:"claude",box:e,multiSelect:f,cursorIndex:p,promptId:I,groupId:$,...k?{questionIndex:k.questionIndex,questionTotal:k.questionTotal,currentTabLabel:k.currentTabLabel,tabs:k.tabs}:{},questions:[{question:b,header:v,multiSelect:f,options:E}]}}function rr(t,e){let n=String(e||"");return n?t==="claude"?pp.test(n)&&rc.test(n)||mp.test(n)||ec.test(n)||hp.test(n.trimEnd().split(`
|
|
71
|
+
`).pop()||"")?!0:bn.test(n)?!1:wp.test(n)&&gp.test(n)||yp.test(n)||ac.test(n)||nc.test(n)||bp.test(n)||kp.test(n.trimEnd().split(`
|
|
72
|
+
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function kn(t,e){return!t||!e?null:t.agent==="claude"?n0(e,t.name)||h0(e,t.name)||Qx(e,t.name)||o0(e,t.name)||d0(e,t.name)||l0(e,t.name)||a0(e,t.name)||s0(e,t.name)||f0(e,t.name)||p0(e,t.name)||e0(e,t.name):t.agent==="cursor"||t.agent==="codex"?lp(t.agent,e,t.name):null}async function ic(t){if(!t)return null;let e=await Se(t.name).catch(()=>"");return kn(t,e)}function xp(t,e){let n=Array.isArray(e?.[0])?e[0].filter(a=>Number.isInteger(a)&&a>=0):[],r=Number.isInteger(t?.cursorIndex)?t.cursorIndex:0,o=(a,i)=>{let c=i-a;return Array.from({length:Math.abs(c)},()=>c>0?"Down":"Up")},s=[];if(t?.multiSelect){let a=r;for(let i of[...n].sort((c,l)=>c-l))s.push(...o(a,i),"Enter"),a=i;s.push("Tab","Enter")}else{let a=n.length?n[0]:0;s.push(...o(r,a),"Enter")}return s}function Sp(t,e){let n=Number.isInteger(t?.questionIndex)?t.questionIndex:0,o=(Number.isInteger(e)?e:n)-n;if(!o)return[];let s=o>0?"Tab":"BTab";return Array.from({length:Math.abs(o)},()=>s)}function or(t){return typeof t=="string"?t.trim():""}var vp=/^\s*─{8,}\s*$/,w0=/^❯[ ]?(.*)$/;function cc(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
73
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(vp.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(vp.test(n[c])){o=c;break}if(o<0)return null;let s=n.slice(o+1,r);if(!s.length)return null;let a=s[0].match(w0);if(!a)return null;let i=[a[1].trim(),...s.slice(1).map(c=>c.trim())].filter(Boolean);return i.length?i.join(" "):null}function xn(t){return String(t||"").replace(/\s+/g," ").trim()}function Ap(t,e){let n=xn(t),r=xn(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import{execFile as g0}from"node:child_process";import{promisify as y0}from"node:util";var uc=y0(g0),Tp=64*1024*1024;async function dt(t,e,{input:n}={}){let r=uc("git",["-C",t,...e],{maxBuffer:Tp});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Ss(t){if(!t)return!1;try{let{stdout:e}=await dt(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function _p(t){let{stdout:e}=await dt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function b0(t){try{return await dt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function dc(t,e="bostrat: subtask snapshot"){let{stdout:n}=await dt(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>uc("git",["-C",t,...a],{maxBuffer:Tp,env:o,...i});try{let a=await b0(t);a&&await s(["read-tree","HEAD"]),await s(["add","-A"]);let{stdout:i}=await s(["write-tree"]),c=i.trim(),l=a?["commit-tree",c,"-p","HEAD","-m",e]:["commit-tree",c,"-m",e],{stdout:u}=await s(l);return u.trim()}finally{try{await uc("rm",["-f",r])}catch{}}}async function fc(t,e){let{stdout:n}=await dt(t,["status","--porcelain"]);return n.trim()?(await dt(t,["add","-A"]),await dt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function pc(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await dt(t,["status","--porcelain"]),r=n.split(`
|
|
74
|
+
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function mc(t,e){if(!e)return 0;try{let{stdout:n}=await dt(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function sr(t,e,n,r){return await dt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function Ep(t,e){try{return await dt(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function Ip(t,e,n){return await dt(t,["worktree","add",e,n]),e}async function vs(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await dt(t,["symbolic-ref","--short","refs/remotes/origin/HEAD"]);if(e.trim())return e.trim()}catch{}for(let[e,n]of[["refs/remotes/origin/main","origin/main"],["refs/remotes/origin/master","origin/master"],["refs/heads/main","main"],["refs/heads/master","master"]])try{return await dt(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function Rp(t){await dt(t,["init","-q"]),await dt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Lt(t,e){try{await dt(t,["worktree","remove","--force",e])}catch{try{await dt(t,["worktree","prune"])}catch{}}}function k0(t){let e=/(\d+) files? changed/.exec(t),n=/(\d+) insertions?\(\+\)/.exec(t),r=/(\d+) deletions?\(-\)/.exec(t);return{files:e?Number(e[1]):0,insertions:n?Number(n[1]):0,deletions:r?Number(r[1]):0}}function hc(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function wc(t,e,n,r){let{stdout:o}=await dt(t,hc(["diff","--shortstat",e,n],r));return k0(o)}async function gc(t,e,n,r){let{stdout:o}=await dt(t,hc(["diff","--name-only",e,n],r));return o.split(`
|
|
75
|
+
`).map(s=>s.trim()).filter(Boolean)}async function yc(t,e,n,r){let{stdout:o}=await dt(t,hc(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await lc(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await lc(t,o,["--3way","--whitespace=nowarn"]),a=await x0(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await lc(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function lc(t,e,n){try{return await dt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function x0(t){try{let{stdout:e}=await dt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
76
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import S0 from"node:fs/promises";import v0 from"node:os";import $p from"node:path";var As=t=>new Promise(e=>setTimeout(e,t)),A0=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,T0=300,Pp=[{alias:"default",label:"Default"},{alias:"opus",label:"Opus 4.8"},{alias:"sonnet",label:"Sonnet 4.6"},{alias:"haiku",label:"Haiku 4.5"},{alias:"fable",label:"Fable 5"},{alias:"opusplan",label:"Opus Plan"}],_0=new Set([...Pp.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),E0=[{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.4",label:"GPT-5.4"},{alias:"gpt-5.4-mini",label:"GPT-5.4 Mini"},{alias:"gpt-5.3-codex",label:"Codex 5.3"},{alias:"gpt-5.2",label:"GPT-5.2"}],Np=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Bp=[{alias:"auto",label:"Auto"},{alias:"composer-2.5",label:"Composer 2.5"},{alias:"claude-opus-4-8",label:"Opus 4.8"},{alias:"claude-fable-5",label:"Fable 5"},{alias:"claude-sonnet-5",label:"Sonnet 5"},{alias:"claude-sonnet-4-6",label:"Sonnet 4.6"},{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.3-codex",label:"Codex 5.3"}];function Dp(t){return t==="codex"?E0:t==="cursor"?Bp:Pp}function Op(t){let e=String(t||"").split(`
|
|
77
|
+
`),n=e.findIndex(o=>/Select Model and Effort/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+(\S+?)(?:\s+\((current|default)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),id:s[3],current:s[4]==="current",active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function Cp(t){let e=String(t||"").split(`
|
|
78
|
+
`),n=e.findIndex(o=>/Select Reasoning Level/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+([A-Za-z ]+?)(?:\s+\((default|current)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),label:s[3].trim(),isDefault:!!s[4],active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function I0(t){let e=String(t||"").split(`
|
|
79
|
+
`);for(let n=e.length-1;n>=0;n--){let r=e[n].match(/Model changed to\s+(\S+)(?:\s+(\S+))?\s*$/i);if(r)return{model:r[1],effort:r[2]||null}}return null}function Mp(t){let e=String(t||"").split(`
|
|
80
|
+
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function jp(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!Np.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Bp.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!_0.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Qr(t,e,{timeoutMs:n=A0}={}){let r=Date.now()+n;for(;;){let o=await ht(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await As(T0)}}async function R0(t,e){return await kt(t,`/model ${e.model}`,ce("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function $0(t,e){await Co(t),await kt(t,"/model",{enter:!1}),await As(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Qr(t,Op);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=I0(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await kt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Qr(t,i=>Cp(i)||!Op(i)&&o(i)&&{done:o(i)});if(!s)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Np.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await bt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await kt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Qr(t,i=>!Cp(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function O0(){let t=$p.join(process.env.CURSOR_CONFIG_DIR||$p.join(v0.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await S0.readFile(t,"utf8"))?.model||null}catch{return null}}async function C0(t,e){await Co(t),await Po(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Qr(t,Mp),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await Qr(t,Mp,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await bt(t,["Down"],{kind:"model-switch"}),await As(250)}if(!n||!s||!o(s)){await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await bt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await bt(t,["Enter"],{kind:"model-switch"}),await As(800);let a=await O0();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Lp(t,e,n){return t==="codex"?$0(e,n):t==="cursor"?C0(e,n):R0(e,n)}var Pt=t=>t||null;function _s(t){let e=t||{},n=M0(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function M0(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Pt(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Pt(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Pt(t.agent),payload:{...ar(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Pt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...ar(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{change:"subtask-resumed",...ar(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...ar(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...ar(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...ar(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Ts=Promise.resolve(),kc=t=>(Ts=Ts.then(t).catch(()=>{}),Ts),Up=()=>Ts;function Hp(t,e){let n=_s(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;kc(()=>C(r,n))}var Fp=new Set;async function Wp(t){if(!(!t?.id||Fp.has(t.id))){Fp.add(t.id);try{let e=await F(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=_s(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await C(t,o)}}catch{}}}function ar(t){let{type:e,at:n,...r}=t;return r}var bc=new Map;function qp(t,e,n){!n?.promptId||bc.get(t.id)===n.promptId||(bc.set(t.id,n.promptId),kc(()=>C(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Pt(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function Gp(t,e,n,r={}){bc.delete(t.id),kc(()=>C(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Pt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as dS}from"node:crypto";var Vp=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,Jp=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,zp=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,Xp=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,P0=new RegExp(`\\b(?:${Vp}|${Jp}|${zp}|${Xp})\\b`,"i"),N0=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,B0=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,D0=90;function j0(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Yp(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of j0(n).reverse()){if(!P0.test(r))continue;let o=r.endsWith("?")&&r.length<=D0&&B0.test(r);if(N0.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Zp=["commit","rebase","push","pr"],L0=new RegExp(`\\b(?:${Vp})\\b`,"i"),F0=new RegExp(`\\b(?:${Xp})\\b`,"i"),U0=new RegExp(`\\b(?:${Jp})\\b`,"i"),H0=new RegExp(`\\b(?:${zp})\\b`,"i"),W0=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function Qp(t){let e=String(t||""),n=[];L0.test(e)&&n.push("commit"),F0.test(e)&&n.push("rebase"),U0.test(e)&&n.push("push");let r=W0.test(e);!r&&H0.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function Es(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let d=[];a.has("commit")||d.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&d.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&d.push("don't push"),a.has("pr")||d.push("don't open a pull request");let f=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return d.length&&(f+=` ${d.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(f+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),f}var q0=3e4;function tm(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=q0&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function em(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Kp={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function nm(t){let e=(Array.isArray(t)?t:[]).filter(r=>Kp[r]).map(r=>Kp[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as z0}from"node:child_process";import{promisify as X0}from"node:util";import G0 from"node:fs";import xc from"node:fs/promises";import am from"node:path";import K0 from"node:os";var im=process.env.BOSTRAT_STATE_DIR||am.join(process.env.HOME||K0.tmpdir(),".bostrat"),Is=am.join(im,"github-metrics.json"),cm=60*6e4,rm=5*6e4,V0=6e4,ir=Date.now(),me=new Map,Be=null,Sn=0,om=0,Rs=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(G0.readFileSync(Is,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<cm?(ir=t.windowStart,me=new Map(Object.entries(t.byCaller||{})),Be=t.lastHour||null):Rs(new Map(Object.entries(t.byCaller||{})))&&(Be={start:t.windowStart,total:Rs(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var sm=Promise.resolve();function lm(t,e=!1){if(!e&&t-om<V0)return;om=t;let n=JSON.stringify({windowStart:ir,byCaller:Object.fromEntries(me),lastHour:Be});sm=sm.then(()=>xc.mkdir(im,{recursive:!0})).then(()=>xc.writeFile(`${Is}.tmp`,n)).then(()=>xc.rename(`${Is}.tmp`,Is)).catch(()=>{})}function um(t){t-ir<cm||(Be={start:ir,total:Rs(me),byCaller:Object.fromEntries(me)},Be.total&&console.log(`[github] ${Be.total} calls last hour`,Be.byCaller),ir=t,me=new Map,lm(t,!0))}function he(t="other",e=Date.now()){um(e),me.set(t,(me.get(t)||0)+1),lm(e)}function dm(t=Date.now()){return um(t),{windowStart:ir,total:Rs(me),byCaller:Object.fromEntries(me),lastHour:Be,backoffUntil:Sn>t?Sn:null}}var J0=/rate limit|too quickly|abuse detection|HTTP 429/i;function De(t){return J0.test(String(t?.message||t||""))}function je(t,e=Date.now()){return Sn<=e&&console.warn(`[github] rate limited \u2014 backing off ${rm/6e4} min:`,String(t?.message||t||"").slice(0,200)),Sn=Math.max(Sn,e+rm),Sn}function $s(t=Date.now()){return Math.max(0,Sn-t)}async function Sc(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await ht(t,{lines:At}).catch(()=>"");if(!Bn(e,r))return hu(e,r)?{working:!0,prompted:!1}:n;if(rr(e,r)){let o=await Se(t).catch(()=>r);if(kn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Os(t,e){return(await Sc(t,e)).working}var fm=X0(z0),pm=[4e3,15e3,45e3],Y0=2500,Z0=5*6e4;async function vc(t,e,{midTurn:n=Os,pollMs:r=Y0,capMs:o=Z0}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var Q0=6e4,tS=9e4,eS=2e3,nS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,rS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,oS=/\s--web\b/,mm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Ac(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function hm(t){let e=String(t||""),n=e.match(nS);if(!n)return null;let r=Ac(e,n.index);return oS.test(r)?null:{repo:r.match(mm)?.[1]??null}}var sS=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function wm(t){let e=String(t||""),n=e.match(rS);if(!n)return null;let r=Ac(e,n.index),o=Ac(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){sS.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(mm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var aS="number,url,title,headRefName,createdAt";async function iS(t,e){let n=async s=>{he("observer");let{stdout:a}=await fm("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",aS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw De(i)&&je(i),i});return JSON.parse(a||"[]")},r=await ne({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-Q0).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function cS(t,e){if(e)return[e];let n=[];for(let r of gm(t)){let o=await Y(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await pe().catch(()=>[])){let o=await Y(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function gm(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function lS(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await cS(t,e)){let a;try{a=await iS(s,n)}catch{continue}for(let i of a){let c=await St(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var Le=new Set;function ym(t,e,{delays:n=pm,turnEnd:r}={}){let o=hm(e.command);if(!o)return null;let s=!!wm(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Le.has(a))return null;Le.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(m=>setTimeout(m,l));let u=await lS(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:p,created:h}=await Mt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!h&&!(p?.trackId===t.id&&await Wr(d,f.number))?null:(await vc(e.box,e.agent,r),await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:f.number,url:f.url,title:f.title,branch:f.headRefName,repo:d,...s?{selfMerge:!0}:{}}}),f)}return null}finally{Le.delete(a)}})();return c.catch(()=>Le.delete(a)),c}var uS="number,url,title,headRefName,state";function bm(t,e,{delays:n=pm,turnEnd:r,openedWait:o}={}){let s=wm(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Le.has(a))return null;Le.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let d=[];if(c)d=[c];else{let h=gm(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let m of h){let b=await Y(m).catch(()=>null);b&&!d.includes(b)&&d.push(b)}}if(l||=t.workspace?.branch,!d.length||!l)return null;c=d[0];let f=async(h,m)=>{let b=await St(c,h.number);return b&&b.trackId!==t.id?null:(b||await Mt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await qr(c,h.number),m&&((await F(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===h.number)||(await vc(e.box,e.agent,r),await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},p=null;for(let h of n){await new Promise(I=>setTimeout(I,h));let m=[];for(let I of d)try{he("observer");let{stdout:$}=await fm("gh",["pr","view",String(l),"--repo",I,"--json",uS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ne({slug:I})}}),_=JSON.parse($||"null");_?.number&&m.push({slug:I,pr:_})}catch($){De($)&&je($)}let b=m.find(I=>I.pr.state==="MERGED")??(m.length===1?m[0]:null);if(!b)continue;c=b.slug;let k=b.pr;if(!k?.number)continue;if(k.state==="CLOSED")return null;if(k.state!=="MERGED"){if(p=k,s.auto)return f(k,!0);continue}let v=await St(c,k.number);if(v&&v.trackId!==t.id)return null;v||await Mt({repo:c,number:k.number,trackId:t.id,url:k.url,headRef:k.headRefName,title:k.title,source:"merge-observed"}),await vc(e.box,e.agent,r);let E=await F(t).catch(()=>[]);if(E.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===k.number))return null;let M=I=>I.some($=>$.type==="pr.state.changed"&&$.payload?.change==="opened"&&$.payload?.number===k.number),lt=0;if(!M(E)){if(hm(e.command)&&await Wr(c,k.number)){let I=Date.now();lt=I+1,await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c,selfMerge:!0}})}else if((await St(c,k.number))?.announced){let I=o?.capMs??tS,$=o?.pollMs??eS,_=Date.now()+I;for(;Date.now()<_&&(await new Promise(U=>setTimeout(U,$)),!M(await F(t).catch(()=>[]))););}}return await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),lt),change:"merged",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c}}),await Ue(c,k.number).catch(()=>{}),await Fe(c,k.number).catch(()=>{}),await Oe(c,k.number,"merged"),k}return p?f(p,!1):null}finally{Le.delete(a)}})();return i.catch(()=>Le.delete(a)),i}function _c(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function km(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function xm(t){return`job|${t.box}|id:${t.id}`}var Tc=new Map,Cs=new Map;async function Ec(t){return Tc.has(t.id)?Tc.get(t.id):(Cs.has(t.id)||Cs.set(t.id,F(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(_c({role:o,sourceId:r.payload.sourceId})),n.keys.add(_c({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(km({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(xm({id:r.payload.id,box:r.boxId}));return Tc.set(t.id,n),n}).finally(()=>Cs.delete(t.id))),Cs.get(t.id))}async function Sm(t,e){try{let n=(await Ec(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=_c(r);if(!n.has(o)&&(n.add(o),await C(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Yp(r.text);if(s){let{offered:a,toPr:i}=Qp(s),c;if(t.workspace?.kind==="multi"){let l=await to(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await C(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:dS(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function vm(t,e){try{let n=await Ec(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=km(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await C(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),ym(t,o),bm(t,o))}}catch{}}async function Am(t,e){try{let n=await Ec(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=xm(o);n.keys.has(s)||(n.keys.add(s),await C(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var fS=/^Base directory for this skill:\s*(\S+)/,pS=/^Run the "([^"]+)" workflow\./,mS=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,hS=/^<local-command-(?:stdout|caveat)>/i,wS=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,gS=/^<task-notification>/i,yS=/<summary>([\s\S]*?)<\/summary>/i,bS=/<task-id>([\s\S]*?)<\/task-id>/i,kS=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,xS=/<status>([\s\S]*?)<\/status>/i,SS=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Ic=new Set(["model","clear","compact","login"]);function Tm(t){return Ic.has(String(t||"").replace(/^\//,"").toLowerCase())}var vS=/^\/([a-z0-9:_-]+)$/i;function Ms(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(fS);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(pS);if(r)return{kind:"skill",name:r[1]};let o=e.match(mS);if(o){let a=o[1].toLowerCase();return Ic.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(vS);if(s&&Ic.has(s[1].toLowerCase()))return{kind:"suppress"};if(hS.test(e))return{kind:"suppress"};if(wS.test(e))return{kind:"suppress"};if(gS.test(e)){let a=c=>AS(e.match(c)?.[1].trim()||"")||null,i=a(SS);return{kind:"task",summary:a(yS)||"",taskId:a(bS),toolUseId:a(kS),status:a(xS)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function AS(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function _m(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function Em(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var et=t=>t.payload?.ts??t.ts,TS=new Set(["opened","ci-fixed","reconciled","pushed"]),Im=t=>`${t.repo??""}#${t.number}`;function Ps(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,d=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let w=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(w))for(let y of w.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let A=y[0].split("/").pop().toLowerCase();d.has(A)||d.set(A,[]),d.get(A).push(g.payload?.ts??g.ts)}}let f=3*6e4,p=20*6e4,h=(g,w)=>!!(g?.name&&d.get(String(g.name).toLowerCase()))?.some(A=>A>=w-f&&A<=w+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let w=xn(g.payload?.text);w&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(w))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let w=xn(g.payload?.text);w&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(w))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let w=g.payload.note,y={status:w.status||null,summary:w.summary||null,resultHead:w.resultHead??null,ts:et(g)};w.toolUseId&&i.set(w.toolUseId,y);let A=String(w.summary||"").match(u);A&&l.set(A[1],y)}let m=new Map,b=new Map,k=null,v=new Map,E=new Map,M=new Map,lt=new Map,I=new Map,$=new Map,_=new Map,U=new Map,V=(g,w)=>{let y=o.get(g);if(!y?.length)return!1;let A=xn(w),R=s.get(g);return y.some(D=>{if(!A.startsWith(D))return!1;let T=xn(A.slice(D.length));return!!T&&!!R?.has(T)})};for(let g of t||[]){let w=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(w.text||"").trim()&&!w.images?.length)break;{let y=w.note||Ms(w.text);if(y){if(y.kind==="task"){if(y.toolUseId&&a.has(y.toolUseId))break;let A=String(y.summary||"").match(u);if(!y.toolUseId&&A&&c.has(A[1]))break}if(y.kind==="command"&&Tm(y.command))break;y.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:Em(y),name:y.name??null,command:y.command??null,summary:y.summary??null,ts:et(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${et(g)}`)||g.source==="transcript"&&V(g.boxId,w.text))break;if(w.kind==="merge-notify"||w.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:w.kind==="ship-notify"?"ship-notified":"merge-notified",text:w.text,ts:et(g),box:g.boxId,agent:g.provider});break}if(w.kind==="mock-picked"||w.kind==="ci-fix"||w.kind==="reconcile"||w.kind==="pr-go"||w.kind==="push-go"||w.kind==="spin-up-view")break;for(let y of v.values())y.status="stale";v.clear(),e.push({id:g.id,kind:"message",role:"user",text:w.text,ts:et(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...g.actor==="orchestrator"?{orchestration:w.kind||!0}:{},...w.sourceId?{sourceId:w.sourceId}:{},...w.attachments?.length?{attachments:w.attachments}:{},...w.images?.length?{images:w.images}:{}});break}case"turn.agent":{_.clear();let y=(w.images||[]).filter(R=>!h(R,et(g)));if(!String(w.text||"").trim()&&!y.length)break;if(g.provider==="cursor"){let R=`${g.boxId}|${w.text}`;if(n.has(R))break;n.add(R)}let A=g.provider==="codex"?(w.tools||[]).filter(R=>!Vi.has(R)&&!Yr.has(R)):w.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:w.text,ts:et(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...A.length?{tools:A}:{},...w.model?{model:w.model}:{},...w.sourceId?{sourceId:w.sourceId}:{},...y.length?{images:y}:{}});break}case"job.started":{if(!w.id)break;let y=et(g),A=i.get(w.id);if(!A&&(w.kind||"agent")==="agent"&&w.name){let O=l.get(w.name);O&&O.ts>=y&&(A=O)}let R={jobId:w.id,jobKind:w.kind||"agent",name:w.name||null,detail:w.detail||null,status:A?A.status&&A.status!=="completed"?"failed":"done":"running",startedTs:y,finishedTs:A?.ts??null,summary:A?.summary??null,resultHead:A?.resultHead??null},D=U.get(g.boxId);if(D&&D.jobs.some(O=>(O.finishedTs??1/0)>=y))D.jobs.push(R);else{let O={id:g.id,kind:"job-dock",dockId:w.id,ts:y,box:g.boxId,agent:g.provider,jobs:[R]};U.set(g.boxId,O),e.push(O)}break}case"handoff.done":e.push(He(g,"lead-handoff"));break;case"subtask.delegated":e.push({...He(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(He(g,"subtask-merged"));break;case"pr.state.changed":{if(!w.change)break;if(w.change==="opened"){for(let A of v.values())A.status="stale";v.clear()}let y=He(g,`pr-${w.change}`);if(TS.has(w.change)&&w.number!=null){let A=Im(w);if(m.set(A,{number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null}),m.size>=2){for(let D of b.values())for(let T of D)T.grouped=!0;y.groupPrs=[...m.values()];let R=[y];for(let D of m.keys())b.set(D,R)}else{let R=b.get(A)??[];R.push(y),b.set(A,R)}}else if(w.change==="merged"||w.change==="closed"||w.change==="lost"){let A=Im(w),R=k&&!k.complete||m.size>=2;if(m.has(A))m.delete(A),b.delete(A);else if(w.number!=null)for(let D of[...m.keys()])D.endsWith(`#${w.number}`)&&(m.delete(D),b.delete(D));if(R&&w.number!=null){(!k||k.complete)&&(k={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:et(g),ts:et(g),box:g.boxId,agent:g.provider},e.push(k)),k.prs.push({number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null,change:w.change,ts:et(g)}),k.remaining=[...m.values()],k.complete=m.size===0;break}}e.push(y);break}case"pr.offered":{if(!w.offerId)break;for(let A of v.values())A.status="stale";v.clear();let y={id:g.id,kind:"pr-offer",offerId:w.offerId,excerpt:w.excerpt||null,branch:w.branch||null,base:w.base||null,offered:Array.isArray(w.offered)&&w.offered.length?w.offered:null,toPr:w.toPr&&typeof w.toPr=="object"?{number:w.toPr.number??null}:null,repos:Array.isArray(w.repos)&&w.repos.length?w.repos:null,status:"open",ts:et(g),box:g.boxId,agent:g.provider};v.set(w.offerId,y),E.set(w.offerId,y),e.push(y);break}case"pr.offer.answered":{let y=E.get(w.offerId);y&&(y.status=w.action||"dismiss",y.actions=Array.isArray(w.actions)?w.actions:null,y.notify=w.notify===!0,v.delete(w.offerId));break}case"box.state.changed":{let y=w.change==="model"?"model-change":w.change;if(!y)break;let A=He(g,y);y==="model-change"&&(A.to=w.model),e.push(A);break}case"view.state.changed":{if(w.change!=="created"||w.restarted)break;e.push({id:g.id,kind:"view-card",viewId:w.viewId,name:w.name,slug:w.slug,port:w.port,origin:w.origin,ts:et(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:w.name||null,reason:w.reason||"no-view",ts:et(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(w.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:w.mockId,name:w.name,group:w.group??null,version:w.version??null,siblings:w.siblings??[],ts:et(g),box:g.boxId,agent:g.provider});break}if(w.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:w.mockId,name:w.name,group:w.group??null,ts:et(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(w.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:w.runbookId,name:w.name,ts:et(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:w.provider||"linear",key:w.key,url:w.url,title:w.title??null,linkSource:w.linkSource??null,ts:et(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!w.offerId)break;for(let A of M.values())A.key===w.key&&(A.status="stale",M.delete(A.offerId));let y={id:g.id,kind:"ticket-offer",offerId:w.offerId,provider:w.provider||"linear",key:w.key,url:w.url??null,phase:w.phase??null,from:w.from??null,to:w.to?.name??null,prNumber:w.prNumber??null,status:"open",ts:et(g),box:g.boxId,agent:g.provider};M.set(w.offerId,y),lt.set(w.offerId,y),e.push(y);break}case"ticket.move.answered":{let y=lt.get(w.offerId);y&&(y.status=w.action==="moved"?"moved":"dismiss",M.delete(w.offerId));break}case"ticket.state.changed":{for(let y of M.values())y.key===w.key&&y.to===w.to&&(y.status="stale",M.delete(y.offerId));if(w.mode==="offer")break;e.push(He(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:w.key,url:w.url??null,excerpt:w.excerpt??null,ts:et(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(He(g,"ticket-flow"));break;case"ticket.create.offered":{if(!w.proposalId)break;for(let A of I.values())A.status="stale";I.clear();let y={id:g.id,kind:"ticket-create-offer",proposalId:w.proposalId,provider:w.provider||"linear",epic:w.epic??null,issues:Array.isArray(w.issues)?w.issues:[],team:w.team??null,needsTeam:!!w.needsTeam,linkCurrentTrack:!!w.linkCurrentTrack,status:"open",ts:et(g),box:g.boxId,agent:g.provider};I.set(w.proposalId,y),$.set(w.proposalId,y),e.push(y);break}case"ticket.create.answered":{let y=$.get(w.proposalId);y&&(y.status=w.action==="created"?"created":"dismiss",y.createdCount=w.createdCount??null,y.failedCount=w.failedCount??null,I.delete(w.proposalId));break}case"ticket.created":{let y={items:Array.isArray(w.items)?w.items:[],failed:Array.isArray(w.failed)?w.failed:[],team:w.team??null},A=w.proposalId?$.get(w.proposalId):null;if(A){A.created=y;break}e.push({id:g.id,kind:"ticket-created",...y,ts:et(g),box:g.boxId,agent:g.provider});break}case"error":{if(!w.kind)break;e.push(He(g,w.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:w.provider||g.provider,ts:et(g),at:et(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(w.control||!w.question)break;let y=w.groupId||w.promptId;if(!y)break;let A=_.get(y);A||(A={id:g.id,kind:"prompt",groupId:y,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:w.questionTotal??1,questions:[],ts:et(g)},_.set(y,A));let R=w.questionIndex??0,D={index:R,header:w.header||"",question:w.question,multiSelect:!!w.multiSelect,chosen:Array.isArray(w.chosen)?w.chosen:[]},T=A.questions.findIndex(W=>W.index===R);T>=0?A.questions[T]=D:A.questions.push(D),A.questions.sort((W,tt)=>W.index-tt.index),A.questionTotal=Math.max(A.questionTotal,w.questionTotal??1),new Set(A.questions.map(W=>W.index)).size>=A.questionTotal&&(e.push(A),_.delete(y));break}default:break}}return ES(e.sort((g,w)=>(g.ts??0)-(w.ts??0)))}var _S=2*6e4;function ES(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=_S){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function Rc(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:IS(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??Ps(t)).filter(o=>o.kind==="message");return Lf(n,r)}function IS(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Qn(t.command)?e:Pe(t.command,t.description||"")}function He(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:et(t),ts:et(t)}}function RS(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
81
|
+
`)){let o=r.trim();if(!o||o[0]!=="{")continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s.payload||{};if(s.type==="turn_context"&&a.model&&(n=a.model),s.type==="event_msg"&&a.type==="token_count"&&a.info?.last_token_usage){let i=a.info.last_token_usage;e.push({ts:s.timestamp?Date.parse(s.timestamp):null,model:n,last:{input:i.input_tokens||0,cachedInput:i.cached_input_tokens||0,output:i.output_tokens||0,reasoningOutput:i.reasoning_output_tokens||0},occupancy:i.total_tokens??null,contextWindow:a.info.model_context_window??null})}}return e}var $c=new Map;async function $S(t,e){let n=`${t.id}:${e.name}`;if(!$c.has(n)){let r=await F(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.codexTs!=null&&o.add(s.payload.codexTs);$c.set(n,o)}return $c.get(n)}async function Rm(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await hn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await $S(t,e),o=RS(await Tt(n.file,262144)),s=o[o.length-1];for(let a of o){if(a.ts==null||r.has(a.ts))continue;r.add(a.ts);let i={input:Math.max(0,a.last.input-a.last.cachedInput),output:a.last.output,cacheRead:a.last.cachedInput,cacheWrite:0},c=a.model||e.model||"gpt-5.5";await C(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:Jt(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await C(t,{type:"context.usage",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{occupancy:a.occupancy,window:a.contextWindow,pct:a.occupancy!=null?a.occupancy/a.contextWindow:null,model:a.model||null,ts:a.ts}})}}catch{}}import Ns from"node:fs";import Cc from"node:path";import OS from"node:os";var CS=process.env.BOSTRAT_STATE_DIR||Cc.join(process.env.HOME||OS.tmpdir(),".bostrat"),Om=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,MS="https://cursor.com/api/dashboard/get-filtered-usage-events",$m=100;async function PS({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(MS,{method:"POST",headers:{"content-type":"application/json",origin:"https://cursor.com",cookie:`WorkosCursorSessionToken=${e}`},body:JSON.stringify({teamId:0,startDate:String(t),endDate:String(Date.now()),page:o,pageSize:$m})});if(!s.ok)throw new Error(`cursor usage feed: HTTP ${s.status}`);let a=await s.json(),i=a?.usageEventsDisplay||a?.usageEvents||[];if(r.push(...i),i.length<$m)break}return r}function NS(t){if(!t)return null;let e=Number(t.timestamp)||null,n=t.tokenUsage||null,r=n?{input:n.inputTokens||0,output:n.outputTokens||0,cacheRead:n.cacheReadTokens||0,cacheWrite:n.cacheWriteTokens||0}:null,o=t.conversationId||null;return{id:[o||"?",e??"?",r?`${r.input}.${r.output}.${r.cacheRead}.${r.cacheWrite}`:"?"].join(":"),conversationId:o,ts:e,model:t.model||t.modelIntent||null,kind:t.kindLabel||t.kind||null,isHeadless:t.isHeadless===!0,tokens:r,vendorCents:n?.totalCents??null,chargedCents:t.chargedCents??null}}var BS=5*6e4,We={at:0,rows:null,error:null},Bs=()=>Cc.join(CS,"cursor-usage.json");function Cm(){try{return JSON.parse(Ns.readFileSync(Bs(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function DS(t){try{Ns.mkdirSync(Cc.dirname(Bs()),{recursive:!0});let e=Bs()+".tmp";Ns.writeFileSync(e,JSON.stringify(t)),Ns.renameSync(e,Bs())}catch{}}var jS=5e3;async function LS({fetchImpl:t,now:e=Date.now()}={}){if(We.rows&&e-We.at<BS)return We.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Cm(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await PS({sinceMs:o,cookie:n,fetchImpl:t});We={at:e,rows:s.map(NS).filter(Boolean),error:null}}catch(s){We={at:e,rows:null,error:String(s?.message||s)}}return We.rows}var Mm=()=>({enabled:Om(),lastPollAt:We.at||null,error:We.error}),Oc=new Map;async function FS(t,e){let n=`${t.id}:${e.name}`;if(!Oc.has(n)){let r=await F(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.cursorEventId&&o.add(s.payload.cursorEventId);Oc.set(n,o)}return Oc.get(n)}async function Pm(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Om())return;let r=await LS({fetchImpl:n});if(!r||!r.length)return;let o=await Jf(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await FS(t,e),a=Cm(),i=new Set(a.seen||[]),c=a.sinceMs||0;for(let l of r){if(!l.conversationId||l.conversationId!==o.chatId||s.has(l.id)||i.has(`${t.id}:${e.name}:${l.id}`))continue;s.add(l.id),i.add(`${t.id}:${e.name}:${l.id}`),l.ts&&l.ts>c&&(c=l.ts);let u=l.model||o.models[o.models.length-1]||"cursor-unknown",d=l.tokens||null,f=l.vendorCents!=null?l.vendorCents/100:0;!f&&d&&(f=Jt(u,d)),!f&&l.chargedCents!=null&&(f=l.chargedCents/100),await C(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:f,model:u,...d?{tokens:d}:{},...l.vendorCents!=null?{vendorCents:l.vendorCents}:{},...l.chargedCents!=null?{chargedCents:l.chargedCents}:{},...l.kind?{kind:l.kind}:{},conversationId:l.conversationId,cursorEventId:l.id,ts:l.ts??void 0}})}DS({sinceMs:c,seen:[...i].slice(-jS)})}catch{}}import{execFile as GS}from"node:child_process";import{promisify as KS}from"node:util";import Nm from"node:fs/promises";import Bm from"node:path";import US from"node:os";var Dm=process.env.BOSTRAT_STATE_DIR||Bm.join(process.env.HOME||US.tmpdir(),".bostrat"),HS=Bm.join(Dm,"github-latency.ndjson"),WS=100,Ds=[],Mc=new Map;function js({change:t,repo:e=null,number:n=null,githubAt:r=null,observedAt:o=Date.now(),lane:s=null,intervalMs:a=null,source:i=null}){let c=r?o-r:null,l={change:t,repo:e,number:n,githubAt:r,observedAt:o,latencyMs:c,lane:s,intervalMs:a,source:i};Ds.push(l),Ds.length>WS&&Ds.shift();let u=Mc.get(t)||{count:0,measured:0,totalMs:0,maxMs:0};return u.count+=1,c!=null&&(u.measured+=1,u.totalMs+=c,u.maxMs=Math.max(u.maxMs,c)),Mc.set(t,u),qS(l),l}async function qS(t){try{await Nm.mkdir(Dm,{recursive:!0}),await Nm.appendFile(HS,JSON.stringify(t)+`
|
|
82
|
+
`)}catch{}}function jm(){let t={};for(let[e,n]of Mc)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:Ds.slice(-20)}}var Us=KS(GS),VS={timeout:3e4,maxBuffer:8*1024*1024};async function Ft(t,e,n={}){let{caller:r="prs",...o}=n,s=await ne({cwd:t});he(r);try{let{stdout:a}=await Us("gh",e,{cwd:t,...VS,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw De(a)&&je(a),a}}var Ls=null;async function Bc(){if(Ls&&Date.now()-Ls.at<6e4)return Ls.data;let t;try{he("auth");let e=await Us("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Vr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ls={at:Date.now(),data:t},t}async function JS(t){try{let{stdout:e}=await Us("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Fm(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function Pc(t){let e=(t||[]).map(Fm),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var Dc="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Um=Dc.replace("statusCheckRollup,","");function jc(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Hm(t,e){if(!e)return null;let n=await Y(t);if(!n)return null;try{let r=await Ft(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function Wm(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function qm(t,e){let n=await Hm(t,e);return n===null?{...Pc([]),unavailable:!0}:Pc(Wm(n))}function Fs(t,e=Pc(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var zS=9e4,cr=new Map;function XS(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){cr.delete(s);continue}r.add(s),cr.has(s)||cr.set(s,n),o.mergeabilityPending=n-cr.get(s)<zS}for(let o of cr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&cr.delete(o);return e}async function lr(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ft(n.path,["pr","view",String(r),"--json",Dc]);return Fs(JSON.parse(o))}catch(o){if(!jc(o))throw o;let s=await Ft(n.path,["pr","view",String(r),"--json",Um]),a=JSON.parse(s);return Fs(a,await qm(n.path,a.headRefOid))}}var Lm=(t,e)=>t.number-e.number;async function no(t,{caller:e="prs"}={}){try{let n=await Ft(t,["pr","list","--json",Dc,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Fs(r)).sort(Lm)}catch(n){if(!jc(n))throw n;let r=await Ft(t,["pr","list","--json",Um,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Fs(o,await qm(t,o.headRefOid))))).sort(Lm)}}var YS=3e4,ZS=1500,eo=new Map;function oe(){eo.clear()}async function QS(t,e){if(!t)return e;for(let n of e){let r=await St(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await ds(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ps(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Gm(){let t=await Bc();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await pe())await JS(a.path)&&e.push(a);let n=Date.now(),r=$s(n)>0,o=await Promise.all(e.map(async a=>{let i=eo.get(a.path),c=i?.pending?ZS:YS;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Y(a.path).catch(()=>null),u=XS(a.path,await no(a.path,{caller:"tab"}),n);if(await QS(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(m=>m.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&js({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let d={name:a.name,path:a.path,prs:u},f=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return eo.set(a.path,{at:Date.now(),entry:d,pending:f}),d}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:re(l)}}})),s=new Set(e.map(a=>a.path));for(let a of eo.keys())s.has(a)||eo.delete(a);return{auth:t,repos:o}}async function we(t){let e=(await pe()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function vn(t){if(!t)return null;for(let e of await pe())if(await Y(e.path).catch(()=>null)===t)return e.path;return null}var ro=new Set(["squash","merge","rebase"]);async function ur(t,e,{method:n="squash"}={}){let r=await we(t);if(!ro.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ne({cwd:r.path})).GH_TOKEN){let a=await Pi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ft(r.path,s)}catch(a){throw new Error(re(a))}finally{oe()}return{ok:!0,number:o}}async function oo(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ft(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(re(s))}finally{oe()}let o=await Y(n.path).catch(()=>null);return o&&await df(o,r).catch(()=>{}),{ok:!0,number:r}}async function Lc(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ft(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(re(o))}finally{oe()}return{ok:!0,number:r}}var tv=2500,ev=9e4,nv=t=>new Promise(e=>setTimeout(e,t));function Fc(t){return t.green&&!t.isDraft}function rv(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Km(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Fc(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Vm(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Nc=new Map;function Jm(){let t={};for(let[e,n]of Nc)t[e]={...n};return t}async function zm(t,{method:e="merge",onMerged:n=null}={}){let r=await we(t);if(!ro.has(e))throw new Error(`invalid merge method: ${e}`);if(Nc.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await no(r.path)).filter(Fc).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return Nc.set(r.path,s),sv(s,o,{listPrs:()=>no(r.path),merge:a=>ur(r.path,a,{method:e}),sleep:nv,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function ov(t,e){let n=Date.now()+(e.settleMaxMs??ev),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(tv)}}async function sv(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await ov(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${re(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!Fc(a)){t.skipped.push({number:a.number,title:a.title,reason:rv(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:re(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),oe()}}async function Uc(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Us("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${re(l)}`)}let o=n||"";if((await ne({cwd:t})).GH_TOKEN){let l=await Pi().catch(()=>null);l&&(o=`${o?`${o}
|
|
83
|
+
|
|
84
|
+
`:""}Opened by bostrat on behalf of @${l}`)}let s=["pr","create","--title",e.trim(),"--body",o];r&&s.push("--base",r);let a;try{a=await Ft(t,s)}catch(l){throw new Error(re(l))}finally{oe()}let i=a.match(/https:\/\/\S+\/pull\/\d+/)?.[0]??null,c=i?Number(i.match(/\/pull\/(\d+)/)?.[1]):null;return{ok:!0,url:i,number:c}}async function Xm(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Ft(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(re(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!jc(i))throw i;let c=await Ft(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Hm(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(re(i));o=Wm(l).map(Fm).filter(u=>u.state==="fail").map(u=>({name:u.name,link:u.url}))}let s=new Set,a=[];for(let i of o){let c=av(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Ft(n.path,["run","view",c,"--log-failed"]).then(u=>iv(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function av(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function iv(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
67
85
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
68
86
|
`+o.slice(-e).join(`
|
|
69
87
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
70
|
-
`+r.slice(-n)),r}function
|
|
71
|
-
`).find(n=>n.trim())||"gh failed"}var
|
|
72
|
-
${n.trim().split(`
|
|
73
|
-
`).slice(-6).join(`
|
|
74
|
-
`)}`:e),this.name="MockRenderError"}},nn=null,_i=null;async function ce(){return nn||(_i||=(async()=>{try{nn=JSON.parse(await ht.readFile(Of,"utf8"))}catch{nn={mocks:[]}}finally{_i=null}return nn.mocks||=[],nn})(),_i)}async function Xt(){await qt(Of,nn)}function Ht(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Xo=t=>_t.join(t,".bostrat","mocks");function Dt(t,e){let n=Xo(t.cwd);return{html:_t.join(n,`${t.id}.v${e}.html`),png:_t.join(n,`${t.id}.v${e}.png`)}}function rn(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Ii(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var kx=Buffer.from([137,80,78,71,13,10,26,10]);function Mf(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(kx)}async function xx(t){let e=Buffer.alloc(24),n=await ht.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Sx({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function vx(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,g)=>{r.onload=m,r.onerror=()=>g(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,d=s,f=-1,w=-1;for(let m=0;m<s;m++)for(let g=m*o*4,x=0;x<o;x++,g+=4)Math.abs(c[g]-l[0])+Math.abs(c[g+1]-l[1])+Math.abs(c[g+2]-l[2])>24&&(x<u&&(u=x),x>f&&(f=x),m<d&&(d=m),w=m);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:w}}},e.toString("base64"))}async function Tx(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((d,f)=>{c.onload=d,c.onerror=()=>f(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function Ax({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await fx.launch({executablePath:mx,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Ft("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:jn})).newPage();a.setDefaultTimeout(Ln),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Ln}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Ln});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await vx(a,i),u=l&&Sx({...l,pad:gx*jn,align:jn});u&&(i=await Tx(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Cf)throw new Ft(`capture produced a ${i.length}-byte image`);let c=Ii(i);if(!c)throw new Ft("output is not a PNG");return await ht.writeFile(e,i),{width:Math.round(c.width/jn),height:Math.round(c.height/jn),dpr:jn,renderMs:Date.now()-r}}catch(s){throw s instanceof Ft?s:new Ft(/timeout/i.test(s?.message||"")?`render timed out after ${Ln}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function If({htmlPath:t,outPath:e,width:n}){let r=await ht.mkdtemp(_t.join(Rr.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Rf(hx,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Ln,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Ft(c?.killed?`render timed out after ${Ln}ms`:"firefox exited non-zero",s)}let a=await ht.stat(e).catch(()=>null);if(!a||a.size<Cf)throw new Ft(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ii(await xx(e));if(!i)throw new Ft("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await ht.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function _x(t){if(px==="firefox")return If(t);try{return await Ax(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return If(t);throw e}}var Ex=_x;var Ei=Promise.resolve();function Pf(t){return Ei=Ei.then(t,t),Ei}async function Nf(t,e){let n=await ce(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Xt(),await Ht(r,"rendering",{version:e});try{let o=await bx();if(o<yx)throw new Ft(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Dt(r,e),a=await Ex({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await ht.rm(s.png,{force:!0}).catch(()=>{});return}Bf(r,{v:e,source:"html",htmlRel:_t.relative(r.cwd,s.html),pngRel:_t.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Xt(),await Ht(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Xt(),await Ht(r,"error",{version:e,error:r.error})}}}function Bf(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function Ix({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?wx:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Df({trackId:t,group:e}={}){let n=await ce(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function Ae(t){let n=(await ce()).mocks.find(r=>r.id===t);return n?{...n}:null}async function jf({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Ix({trackId:t,name:e,cwd:o,width:s}),d=typeof n=="string"&&n.trim();if(!d&&!r)throw new Error("html (string) or image (png) required");if(r&&!Mf(r))throw new Error("image must be a PNG");let f=await ce(),w={id:lx().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:d?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await ht.mkdir(Xo(o),{recursive:!0}),f.mocks.push(w),d?(await ht.writeFile(Dt(w,1).html,n),await Xt(),await Ht(w,"created"),Pf(()=>Nf(w.id,1))):(await Uf(w,1,r),await Xt(),await Ht(w,"created"),await Ht(w,"ready",{version:1,width:w.width,height:w.height,dpr:w.dpr})),{...w}}async function Lf(t,{html:e,image:n,width:r}={}){let s=(await ce()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Mf(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await ht.mkdir(Xo(s.cwd),{recursive:!0}),a?(s.state="queued",await ht.writeFile(Dt(s,i).html,e),await Xt(),await Ht(s,"updated",{version:i}),Pf(()=>Nf(s.id,i))):(await Uf(s,i,n),await Xt(),await Ht(s,"updated",{version:i}),await Ht(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Uf(t,e,n){let r=Ii(n);if(!r)throw new Error("image must be a PNG");let o=Dt(t,e);await ht.writeFile(o.png,n),Bf(t,{v:e,source:"image",htmlRel:null,pngRel:_t.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Ff(t){let e=await ce(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await Ht(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Hf(t){let e=await ce(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Dt(r,o);await ht.rm(s.html,{force:!0}).catch(()=>{}),await ht.rm(s.png,{force:!0}).catch(()=>{})}return await Xt(),await Ht(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Wf(t){let n=(await ce()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=_t.join($f,"mock-archive",t.id),o=Xo(t.cwd),s=_t.join(r,".bostrat","mocks");await ht.mkdir(s,{recursive:!0});let a=await ht.readdir(o).catch(()=>[]);for(let i of a)await ht.copyFile(_t.join(o,i),_t.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await ht.stat(_t.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await Xt(),n.length}async function qf(t){let e=await ce(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Xt()),n.length}import sn from"node:fs/promises";import _e from"node:path";import Rx from"node:os";import{randomUUID as $x}from"node:crypto";var Fn=process.env.BOSTRAT_STATE_DIR||_e.join(process.env.HOME||Rx.tmpdir(),".bostrat"),Kf=_e.join(Fn,"runbooks.json"),$i=_e.join(Fn,"runbooks"),Gf=256*1024,Ox=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function Cx(t){return Ox.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var on=null,Ri=null;async function Ee(){return on||(Ri||=(async()=>{try{on=JSON.parse(await sn.readFile(Kf,"utf8"))}catch{on={runbooks:[]}}finally{Ri=null}return on.runbooks||=[],on})(),Ri)}async function Hn(){await qt(Kf,on)}function Wn(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Un(t,e){return _e.join($i,`${t.id}.v${e}.md`)}function Vf(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Jf(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Gf)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Gf/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Cx(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function zf(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Xf({trackId:t,status:e}={}){let n=await Ee(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function Oi(t){let n=(await Ee()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Yf({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=Jf(r),c=await Ee(),l={id:$x().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await sn.mkdir($i,{recursive:!0}),await sn.writeFile(Un(l,1),r),l.versions.push({v:1,mdRel:_e.relative(Fn,Un(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Hn(),await Wn(l,"created",{version:1}),{...l}}async function Zf(t,{md:e,note:n}={}){let o=(await Ee()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");zf(o,"update");let s=Jf(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await sn.mkdir($i,{recursive:!0}),await sn.writeFile(Un(o,a),e),o.versions.push({v:a,mdRel:_e.relative(Fn,Un(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Hn(),await Wn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Qf(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ee()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return zf(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Hn(),await Wn(r,e==="done"?"done":"reopened"),{...r})}async function tp(t){let e=await Ee(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await sn.rm(_e.join(Fn,o.mdRel),{force:!0}).catch(()=>{});return await Hn(),r.trackDeletedAt||await Wn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function ep(t){let n=(await Ee()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Wn(r,"orphaned"),r.cwd=null;return n.length&&await Hn(),n.length}async function np(t){let e=await Ee(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Wn(r,"deleted");for(let o of r.versions||[])await sn.rm(_e.join(Fn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Hn()),n.length}import rp from"node:fs/promises";import Yo from"node:path";import{randomUUID as Mx}from"node:crypto";var sp=".bostrat/uploads",Ci=t=>Yo.join(t,sp),op=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,Px=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),Nx=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp","rtf","txt","md","markdown","rst","csv","tsv","json","jsonl","yaml","yml","xml","html","htm","log","ini","toml","conf","env","js","jsx","ts","tsx","py","rb","go","rs","java","kt","c","h","cpp","cc","cs","php","swift","sh","bash","zsh","sql","css","scss"]);function Mi(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return Px.has(e)?"image":Nx.has(e)?"doc":null}function Bx(t){let r=Yo.basename(String(t||"")).replace(/[\x00-\x1f]/g,"").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"").slice(-80)||"file";return r.startsWith(".")?`f${r}`:r}async function ap(t,{filename:e,buffer:n}){if(!t)throw new Error("cwd required");if(!Buffer.isBuffer(n)||n.length===0)throw new Error("empty upload");if(n.length>op)throw new Error(`file too large \u2014 max ${Math.floor(op/(1024*1024))} MB`);let r=Bx(e),o=Yo.extname(r).slice(1),s=Mi(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=Mx().slice(0,8),i=Ci(t);await rp.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await rp.writeFile(Yo.join(i,c),n),{id:a,name:r,rel:`${sp}/${c}`,kind:s,size:n.length}}function ip(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var Dx=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Pi(t){return t.role==="user"&&Dx.test((t.text||"").trim())}function qn(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var Ux=Lx(jx),Ie=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||"/tmp",".bostrat"),mp=U.join(Ie,"tracks.json"),Fx=U.join(Ie,"events.jsonl"),Fi=U.join(Ie,"workspaces"),Hx=process.env.BOSTRAT_DEFAULT_REPO||null,es={claude:{envVar:"BOSTRAT_AGENT_CMD",base:"claude",skipFlag:"--dangerously-skip-permissions"},cursor:{envVar:"BOSTRAT_CURSOR_CMD",base:"cursor-agent",skipFlag:"--force"},codex:{envVar:"BOSTRAT_CODEX_CMD",base:"codex",skipFlag:"--dangerously-bypass-approvals-and-sandbox"}},Wi=Object.keys(es);function Wx(t,{skipPermissions:e=!0}={}){let n=es[t];if(!n)throw new Error(`unknown agent "${t}"`);let r=process.env[n.envVar];return r||(e&&n.skipFlag?`${n.base} ${n.skipFlag}`:n.base)}function hp(t,e,{skipPermissions:n=!0}={}){let r=es[t];if(!r)throw new Error(`unknown agent "${t}"`);let o=process.env[r.envVar];if(o)return o;let s=n&&r.skipFlag?` ${r.skipFlag}`:"";switch(t){case"claude":return`${r.base} --resume ${e}${s}`;case"cursor":return`${r.base} --continue${s}`;case"codex":return`${r.base} resume --last${s}`;default:return`${r.base}${s}`}}function ns(t){let e=es[t];if(e)return process.env[e.envVar]||e.base}async function rs(t,e=!0){let n=Wx(t,{skipPermissions:e});if(!await Us(n)){let r=n.split(/\s+/)[0];throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). Its CLI must be installed and on PATH before you can hand off / delegate to it.`)}return n}var os=".bostrat/handoff/latest.md";function qx(t,e=os){return`${t.trim()}
|
|
88
|
+
`+r.slice(-n)),r}function re(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
89
|
+
`).find(n=>n.trim())||"gh failed"}var cv=6e3,Hs=new Map;function Hc(t){let e=null;for(let n of t||[]){let r=n.payload?.ts??n.ts;n.type==="pr.offer.answered"?e=n.payload?.action==="pr"?{ts:r,offerId:n.payload?.offerId??null,box:n.boxId??null}:null:n.type==="pr.state.changed"&&n.payload?.change==="opened"&&e!=null&&r>=e.ts&&(e=null)}return e}async function Ym(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await F(t)}catch{return}if(Hc(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Os(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Hs.get(t.id)||0)<cv)return;Hs.set(t.id,o);let s;try{s=await no(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await Y(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Mt({repo:i,number:a.number,trackId:t.id,url:a.url,headRef:e,title:a.title,source:"reconciled"});if(!l&&!(c?.trackId===t.id&&await Wr(i,a.number))){Hs.delete(t.id);return}}await C(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e}}).catch(()=>{}),Hs.delete(t.id)}import Zm from"node:fs/promises";import Ws from"node:path";import{randomUUID as lv}from"node:crypto";var th=".bostrat/uploads",Wc=t=>Ws.join(t,th),Qm=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,uv=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),dv=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp","rtf","txt","md","markdown","rst","csv","tsv","json","jsonl","yaml","yml","xml","html","htm","log","ini","toml","conf","env","js","jsx","ts","tsx","py","rb","go","rs","java","kt","c","h","cpp","cc","cs","php","swift","sh","bash","zsh","sql","css","scss"]);function qc(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return uv.has(e)?"image":dv.has(e)?"doc":null}function fv(t){let r=Ws.basename(String(t||"")).replace(/[\x00-\x1f]/g,"").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"").slice(-80)||"file";return r.startsWith(".")?`f${r}`:r}async function eh(t,{filename:e,buffer:n}){if(!t)throw new Error("cwd required");if(!Buffer.isBuffer(n)||n.length===0)throw new Error("empty upload");if(n.length>Qm)throw new Error(`file too large \u2014 max ${Math.floor(Qm/(1024*1024))} MB`);let r=fv(e),o=Ws.extname(r).slice(1),s=qc(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=lv().slice(0,8),i=Wc(t);await Zm.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Zm.writeFile(Ws.join(i,c),n),{id:a,name:r,rel:`${th}/${c}`,kind:s,size:n.length}}function nh(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var pv=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Gc(t){return t.role==="user"&&pv.test((t.text||"").trim())}function dr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var wv=hv(mv),qe=process.env.BOSTRAT_STATE_DIR||L.join(process.env.HOME||"/tmp",".bostrat"),uh=L.join(qe,"tracks.json"),gv=L.join(qe,"events.jsonl"),Yc=L.join(qe,"workspaces"),yv=process.env.BOSTRAT_DEFAULT_REPO||null,Ks={claude:{envVar:"BOSTRAT_AGENT_CMD",base:"claude",skipFlag:"--dangerously-skip-permissions"},cursor:{envVar:"BOSTRAT_CURSOR_CMD",base:"cursor-agent",skipFlag:"--force"},codex:{envVar:"BOSTRAT_CODEX_CMD",base:"codex",skipFlag:"--dangerously-bypass-approvals-and-sandbox"}},Qc=Object.keys(Ks);function bv(t,{skipPermissions:e=!0}={}){let n=Ks[t];if(!n)throw new Error(`unknown agent "${t}"`);let r=process.env[n.envVar];return r||(e&&n.skipFlag?`${n.base} ${n.skipFlag}`:n.base)}function dh(t,e,{skipPermissions:n=!0}={}){let r=Ks[t];if(!r)throw new Error(`unknown agent "${t}"`);let o=process.env[r.envVar];if(o)return o;let s=n&&r.skipFlag?` ${r.skipFlag}`:"";switch(t){case"claude":return`${r.base} --resume ${e}${s}`;case"cursor":return`${r.base} --continue${s}`;case"codex":return`${r.base} resume --last${s}`;default:return`${r.base}${s}`}}function Vs(t){let e=Ks[t];if(e)return process.env[e.envVar]||e.base}async function Js(t,e=!0){let n=bv(t,{skipPermissions:e});if(!await _a(n)){let r=n.split(/\s+/)[0];throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). Its CLI must be installed and on PATH before you can hand off / delegate to it.`)}return n}var zs=".bostrat/handoff/latest.md";function kv(t,e=zs){return`${t.trim()}
|
|
75
90
|
|
|
76
|
-
(Read ${e} for handoff context from the prior lead \u2014 you are now the lead; none of the prior chat is in your context. Then respond to the user above.)`}var
|
|
91
|
+
(Read ${e} for handoff context from the prior lead \u2014 you are now the lead; none of the prior chat is in your context. Then respond to the user above.)`}var xv=/\(Read \S{1,300} for handoff context from the prior lead — you are now the lead; none of the prior chat is in your context\. Then respond to the user above\.\)$/;function rh(t){return t.role==="user"&&xv.test((t.text||"").trim())}var qs=".bostrat/handoff/subtask-result.md",ao=".bostrat/handoff/lead-context.md",Sv=(t,e=!1)=>e?`You are a SUBTASK agent working in an isolated multi-repo workspace, delegated by a lead agent on a shared workstream. This directory is NOT itself a git repo: each subdirectory is an isolated git worktree of one of the workstream's repos, already seeded with the lead's full working tree for that repo. The conversation so far is summarized in ${ao} \u2014 read that first for context. Your scoped task:
|
|
77
92
|
|
|
78
93
|
${t}
|
|
79
94
|
|
|
80
|
-
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them
|
|
81
|
-
`;return Bi=Bi.then(()=>P.mkdir(Ie,{recursive:!0})).then(()=>P.appendFile(Fx,n)).catch(()=>{}),Bi}var it=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Jx(t.id,r),cd(t,r),r};kl(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(it(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await K().catch(()=>{}))});var Di=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),zx=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function wp(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>zx.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function gp(t,e,n,{kind:r}={}){let o=await G(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ns(o.agent);await we({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await yt(e,n,{...ne(o.agent),kind:r||"orchestration"}),await L({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=Q(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);it(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await K()}var an=new Set(["merged","conflict","canceled","failed","handed-off"]),lp=new Map;async function Xx(t){let e=U.join(So(t),`${t.id}.ndjson`),n;try{n=await P.stat(e)}catch{return null}let r=lp.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await B(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return lp.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Yx(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Xa(n.name,n.agent)}async function Re(t){let e=new Map((await vn()).map(a=>[a.name,a])),n=t.boxes.map(a=>{let i=e.get(a.name),c=an.has(a.state)?a.state:i?a.role==="lead"?"live":a.state:"stopped";return{...a,state:c,cwd:i?.cwd||a.cwd,agent:i?.agent||a.agent,sessionId:i?.sessionId||a.sessionId}}),[r,{working:o,prompted:s}]=await Promise.all([Xx(t),Yx(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var Zx=5e3;function Qx(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),d=l.createdAt-Zx,f=r[u+1]?.createdAt??1/0,w=o.find(m=>!s.has(m.id)&&m.birthMs>=d&&m.birthMs<f);w&&(s.add(w.id),c.push({box:l.name,sessionId:w.id}))}return c}async function yp(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(f=>f.box.sessionId),a=ml(r),i=new Set(a.map(f=>f.id)),c=s.every(f=>f&&i.has(f)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(f=>f.box),d=Qx(u,a,f=>i.has(f));for(let f of d){let w=u.find(m=>m.name===f.box);w&&(w.sessionId=f.sessionId),await sl(f.box,f.sessionId),n=!0}}return n}async function dt(){let t=await H();return await yp(t)&&await K(),Promise.all(t.tracks.map(Re))}async function bp(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function rt(t){let e=await H(),n=Q(e,t);return n?(await yp(e)&&await K(),Re(n)):null}var t0={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function up(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(t0[t.agent]||[]).some(n=>n.test(e))}var e0=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function dp(t){let e=(t.text||"").trim();return e?e0.some(n=>n.test(e)):!1}var n0=10*6e4;async function Gi(t,e={}){let n=await rt(t);if(!n)return null;let r=new Set([...n.events.filter(y=>y.type==="orchestration-prompt"&&y.prompt).map(y=>String(y.prompt).trim()),...n.events.filter(y=>y.type==="said"&&y.text).map(y=>String(y.text).trim())]),o=Br(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=mi(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...sf(n),...l],f={windows:[...Ol(n),...Cl(n,fo),...a,...u]},m=(await Promise.all(n.boxes.map(y=>yu(y,e)))).flat(),g=m.map(y=>{let j={kind:"message",...y};if(j.role==="assistant"&&(j.text||"").trim()&&j.tools?.length){let wn=yr(j.agent).cliTools,Zr=j.tools.filter(je=>!wn.has(je));return{...j,tools:Zr.length?Zr:void 0}}return j}).map(y=>{if(y.role!=="user")return y;let j=Bo(y.text);return j?j.kind==="suppress"?null:{...y,note:j,text:Ld(j)}:y}).filter(Boolean).filter(y=>!(y.role==="user"&&r.has((y.text||"").trim()))).filter(y=>!up(y)).filter(y=>!dp(y)).filter(y=>!cp(y)).filter(y=>!Pi(y)).filter(y=>y.role!=="assistant"||!!(y.text||"").trim()||!!y.images?.length).filter(y=>!Ml(y,f)),x=g.filter(y=>y.failed&&y.text===Ta);if(x.length){let y=new Map;for(let j of x){if(!y.has(j.box)){let je=[...(await pt(j.box,{lines:$t}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Os=>Os[1]);y.set(j.box,je.length?je[je.length-1]:null)}let wn=y.get(j.box);wn&&(j.text=`\u25A0 ${wn}`)}}let A=await Promise.all(n.boxes.map(y=>ku(y,e)));await Dd(n,A.flat()),await Nd(n,g),await ld(n),await pf(n,g).catch(()=>{});for(let y of n.boxes)y.agent==="codex"&&y.state==="live"&&await Wd(n,y),y.agent==="cursor"&&y.state==="live"&&await Jd(n,y);let S=await Promise.all(n.boxes.map(y=>bu(y,e)));await Bd(n,S.flat().filter(y=>!Di(y.box,y.ts,u)));let T=await B(n),E=c&&i.viewId?i.viewId:null,J=yd(T),M=Do(T).filter(y=>!(y.kind==="message"&&(up(y)||dp(y)||cp(y)||Pi(y)))&&!(y.kind==="view-card"&&E&&y.viewId===E)),I=bd(M,J),h=M.filter(y=>!(y.kind==="message"&&y.role==="assistant"&&!I.has(y)&&Di(y.box,y.ts,J))),p=ri(T,h).map(y=>({kind:"command-batch",id:y.id,ts:y.pending?y.ts:(y.anchorTs??y.ts)-1,anchorTs:y.anchorTs,anchorBox:y.anchorBox,agent:y.agent,boxRole:y.boxRole,commands:y.commands,pending:y.pending})).filter(y=>!Di(y.anchorBox,y.anchorTs??y.ts,u)),b=[...h,...p].filter(y=>y.ts!=null).sort((y,j)=>y.ts-j.ts),v=r0(n),ot=v.size?b.filter(y=>y.kind==="event"||!v.has(y.box??y.anchorBox)):b,$=n.boxes.find(y=>y.name===n.leadBox)||null,z=$?await lr($.cwd,$.sessionId,{fallbackNewest:!1}):null;if(!z&&$){let y=[...T].reverse().find(j=>j.type==="context.usage"&&j.boxId===$.name);y?.payload?.window&&(z={pct:y.payload.pct??(y.payload.occupancy!=null?y.payload.occupancy/y.payload.window:null),occupancy:y.payload.occupancy??null,window:y.payload.window,windowKnown:!0,model:y.payload.model||null})}let N=[...m].reverse().find(y=>y.role==="assistant"&&y.model),W=N?.model||null,_=[...n.events||[]].reverse().find(y=>y.type==="model-change"),q=z?.model||W;_&&(!N?.ts||_.at>=N.ts)&&(q=_.to);let X=$?await pt($.name,{lines:$t}).catch(()=>""):"",lt=$&&Cn($.agent,X)?await me($.name).catch(()=>X):X,nt=$?Xe($,lt):null;nt&&ud(n,$,nt);let wt=!!$&&!nt&&dr($.agent,X);$&&!$.readyAt&&(wt||nt||X&&co($.agent,X))&&($.readyAt=Date.now(),await K());let Pt=!!$&&!$.readyAt&&Date.now()-(n.createdAt||0)<n0;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&$&&co($.agent,X)&&!nt){let y=await H(),j=Q(y,n.id);j?.setup&&!j.setup.connectedAt&&(j.setup.connectedAt=Date.now(),n.setup.connectedAt=j.setup.connectedAt,await R(j,{type:"provider.connected",actor:"system",source:"pane",boxId:$.name,provider:$.agent,payload:{provider:$.agent,ts:j.setup.connectedAt}}).catch(()=>{}),await K())}let De=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,jt=null;if(c){let y=Date.now()-i.startedAt;if(i.viewId&&!wt){let j=Wo(n.id);j&&(it(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:j.finishedAt}),await K())}else if(nt||y>rf&&!wt&&!i.viewId||y>pi){let j=nt?"needs-input":y>pi?"timeout":"no-view";Wo(n.id,{error:j})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:$?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:j}}).catch(()=>{})}else jt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:ot,context:z?{pct:z.pct,occupancy:z.occupancy,window:z.window,windowKnown:z.windowKnown,model:z.model}:null,models:{current:q,options:ed($?.agent)},pendingPrompt:nt,working:wt,leadStarting:Pt,handoff:De,view:jt,queued:s0(n.id)}}function r0(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function kp(t,e){let n=await rt(t);if(!n)return null;let r=Jn(t).find(d=>d.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await B(n),a=Do(s).filter(d=>d.box===o),i=ri(s.filter(d=>d.boxId===o),a.filter(d=>d.kind==="message")).map(d=>({kind:"command-batch",id:d.id,ts:d.pending?d.ts:(d.anchorTs??d.ts)-1,anchorTs:d.anchorTs,anchorBox:d.anchorBox,agent:d.agent,boxRole:d.boxRole,commands:d.commands,pending:d.pending})),c=[...a,...i].filter(d=>d.ts!=null).sort((d,f)=>d.ts-f.ts),l=n.boxes.find(d=>d.name===o)||null,u=!1;if(l&&l.state==="live"){let d=await pt(o,{lines:$t}).catch(()=>"");u=dr(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function Ki(t,{promptId:e,answers:n,text:r}={}){let o=Q(await H(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(f=>f.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await G(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Ba(s);if(!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let f=typeof r=="string"?r.trim():"";if(!f)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(f)||f.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await yt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await gt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.provider==="claude"||i.answerMode==="keys"){let f=Bu(i,n||[]);await gt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=Au(i,n||[]);await yt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:f,count:1}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(f=>l.options?.[f]).filter(Boolean).map(f=>({label:f.label,description:f.description||""})),d=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...d?{control:d}:{}},await L({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),dd(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function xp(t,{promptId:e,questionIndex:n}={}){let r=Q(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await G(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Ba(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=Du(a,n);return c.length?(await gt(r.leadBox,c,{kind:"navigate-prompt"}),await L({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var o0=1500,Or=new Map;function Sp(t){let e=Or.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Or.set(t,e)),e}function s0(t){return(Or.get(t)?.items??[]).map(e=>({...e}))}function ss(t){let e=Or.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function a0(t){let e=Sp(t);e.timer||(e.timer=setInterval(()=>i0(t).catch(()=>{}),o0))}async function i0(t){let e=Or.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=Q(await H(),t);if(!n||n.archivedAt){ss(t);return}let r=Br(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){qn(e,!1);return}let o=await G(n.leadBox);if(!o){qn(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await pt(n.leadBox,{lines:$t}).catch(()=>"");if(io(s?.agent,a)){qn(e,!1);return}if(Cn(s?.agent,a)){let c=await me(n.leadBox).catch(()=>a);if(Xe(s,c)){qn(e,!1);return}}if(!qn(e,!0))return;let i=e.items.shift();e.idleSince=null,await vp(n,s,o,i.text,i.kind??null,i.attachments??null),await K()}finally{e.flushing=!1}}}async function vp(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=qx(r,t.pendingHandoffHydrate.artifact||os),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(l=>l&&l.rel):[];if(c.length){let l=ip(c.map(u=>u.rel));a=a.trim()?`${a.trim()}
|
|
95
|
+
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${qs} (at this directory's root) with sections \u2014 Result (what you did), Files (paths you changed, prefixed with the repo directory), Notes (anything the lead must know). Writing that file is your DONE signal: do it last, then stop and take no further action.`:`You are a SUBTASK agent working in an isolated git worktree, delegated by a lead agent on a shared workstream. The lead's full working tree is already checked out here, and the conversation so far is summarized in ${ao} \u2014 read that first for context. Your scoped task:
|
|
96
|
+
|
|
97
|
+
${t}
|
|
82
98
|
|
|
83
|
-
|
|
99
|
+
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${qs} with sections \u2014 Result (what you did), Files (paths you changed), Notes (anything the lead must know). Writing that file is your DONE signal: do it last, then stop and take no further action.`,vv=(t,e,n,r)=>`A subtask box you delegated has finished: "${t}". Its changes are now merged into your working tree (${e.files} file${e.files===1?"":"s"}, +${e.insertions} \u2212${e.deletions}). Read ${r} for exactly what it did. `+(n.length?`IMPORTANT: ${n.length} merge conflict${n.length===1?"":"s"} need resolving \u2014 fix the conflict markers in: ${n.join(", ")}, then continue.`:"Review the merged changes and continue the work."),se=null,Kc=null;async function H(){return se||(Kc||=(async()=>{try{se=JSON.parse(await j.readFile(uh,"utf8"))}catch{se={tracks:[]}}finally{Kc=null}return se})(),Kc)}async function J(){await zt(uh,se)}var Z=(t,e)=>t.tracks.find(n=>n.id===e)||null,Vc=Promise.resolve();function Av(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
100
|
+
`;return Vc=Vc.then(()=>j.mkdir(qe,{recursive:!0})).then(()=>j.appendFile(gv,n)).catch(()=>{}),Vc}var ot=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Av(t.id,r),Hp(t,r),r};du(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ot(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await J().catch(()=>{}))});var Jc=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),Tv=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function fh(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>Tv.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function ph(t,e,n,{kind:r}={}){let o=await z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Vs(o.agent);await Ae({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await kt(e,n,{...ce(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=Z(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ot(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await J()}var An=new Set(["merged","conflict","canceled","failed","handed-off"]),oh=new Map;async function _v(t){let e=L.join(qo(t),`${t.id}.ndjson`),n;try{n=await j.stat(e)}catch{return null}let r=oh.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await F(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return oh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Ev(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Sc(n.name,n.agent)}async function Ge(t){let e=new Map((await Pn()).map(a=>[a.name,a])),n=t.boxes.map(a=>{let i=e.get(a.name),c=An.has(a.state)?a.state:i?a.role==="lead"?"live":a.state:"stopped";return{...a,state:c,cwd:i?.cwd||a.cwd,agent:i?.agent||a.agent,sessionId:i?.sessionId||a.sessionId}}),[r,{working:o,prompted:s}]=await Promise.all([_v(t),Ev(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var Iv=5e3;function Rv(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),d=l.createdAt-Iv,f=r[u+1]?.createdAt??1/0,p=o.find(h=>!s.has(h.id)&&h.birthMs>=d&&h.birthMs<f);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function mh(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(f=>f.box.sessionId),a=su(r),i=new Set(a.map(f=>f.id)),c=s.every(f=>f&&i.has(f)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(f=>f.box),d=Rv(u,a,f=>i.has(f));for(let f of d){let p=u.find(h=>h.name===f.box);p&&(p.sessionId=f.sessionId),await zl(f.box,f.sessionId),n=!0}}return n}async function ft(){let t=await H();return await mh(t)&&await J(),Promise.all(t.tracks.map(Ge))}async function hh(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function nt(t){let e=await H(),n=Z(e,t);return n?(await mh(e)&&await J(),Ge(n)):null}var $v={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function sh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return($v[t.agent]||[]).some(n=>n.test(e))}var Ov=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ah(t){let e=(t.text||"").trim();return e?Ov.some(n=>n.test(e)):!1}var Cv=10*6e4;async function tl(t,e={}){let n=await nt(t);if(!n)return null;let r=new Set([...n.events.filter(x=>x.type==="orchestration-prompt"&&x.prompt).map(x=>String(x.prompt).trim()),...n.events.filter(x=>x.type==="said"&&x.text).map(x=>String(x.text).trim())]),o=po(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=Qa(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...fd(n),...l],f={windows:[..._u(n),...Eu(n,Ho),...a,...u]},h=(await Promise.all(n.boxes.map(x=>rp(x,e)))).flat(),m=h.map(x=>{let q={kind:"message",...x};if(q.role==="assistant"&&(q.text||"").trim()&&q.tools?.length){let En=Zr(q.agent).cliTools,Io=q.tools.filter(Ze=>!En.has(Ze));return{...q,tools:Io.length?Io:void 0}}return q}).map(x=>{if(x.role!=="user")return x;let q=Ms(x.text);return q?q.kind==="suppress"?null:{...x,note:q,text:_m(q)}:x}).filter(Boolean).filter(x=>!(x.role==="user"&&r.has((x.text||"").trim()))).filter(x=>!sh(x)).filter(x=>!ah(x)).filter(x=>!rh(x)).filter(x=>!Gc(x)).filter(x=>x.role!=="assistant"||!!(x.text||"").trim()||!!x.images?.length).filter(x=>!Iu(x,f)),b=m.filter(x=>x.failed&&x.text===Ji);if(b.length){let x=new Map;for(let q of b){if(!x.has(q.box)){let Ze=[...(await ht(q.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(ga=>ga[1]);x.set(q.box,Ze.length?Ze[Ze.length-1]:null)}let En=x.get(q.box);En&&(q.text=`\u25A0 ${En}`)}}let k=await Promise.all(n.boxes.map(x=>sp(x,e)));await Am(n,k.flat()),await Sm(n,m),await Wp(n),await yd(n,m).catch(()=>{});for(let x of n.boxes)x.agent==="codex"&&x.state==="live"&&await Rm(n,x),x.agent==="cursor"&&x.state==="live"&&await Pm(n,x);let v=await Promise.all(n.boxes.map(x=>op(x,e)));await vm(n,v.flat().filter(x=>!Jc(x.box,x.ts,u)));let E=await F(n),M=c&&i.viewId?i.viewId:null,lt=tm(E),I=Ps(E).filter(x=>!(x.kind==="message"&&(sh(x)||ah(x)||rh(x)||Gc(x)))&&!(x.kind==="view-card"&&M&&x.viewId===M)),$=em(I,lt),_=I.filter(x=>!(x.kind==="message"&&x.role==="assistant"&&!$.has(x)&&Jc(x.box,x.ts,lt))),U=Rc(E,_).map(x=>({kind:"command-batch",id:x.id,ts:x.pending?x.ts:(x.anchorTs??x.ts)-1,anchorTs:x.anchorTs,anchorBox:x.anchorBox,agent:x.agent,boxRole:x.boxRole,commands:x.commands,pending:x.pending})).filter(x=>!Jc(x.anchorBox,x.anchorTs??x.ts,u)),V=[..._,...U].filter(x=>x.ts!=null).sort((x,q)=>x.ts-q.ts),g=Mv(n),w=g.size?V.filter(x=>x.kind==="event"||!g.has(x.box??x.anchorBox)):V,y=n.boxes.find(x=>x.name===n.leadBox)||null,A=y?await $r(y.cwd,y.sessionId,{fallbackNewest:!1}):null;if(!A&&y){let x=[...E].reverse().find(q=>q.type==="context.usage"&&q.boxId===y.name);x?.payload?.window&&(A={pct:x.payload.pct??(x.payload.occupancy!=null?x.payload.occupancy/x.payload.window:null),occupancy:x.payload.occupancy??null,window:x.payload.window,windowKnown:!0,model:x.payload.model||null})}let R=[...h].reverse().find(x=>x.role==="assistant"&&x.model),D=R?.model||null,T=[...n.events||[]].reverse().find(x=>x.type==="model-change"),O=A?.model||D;T&&(!R?.ts||T.at>=R.ts)&&(O=T.to);let W=y?await ht(y.name,{lines:At}).catch(()=>""):"",tt=y&&rr(y.agent,W)?await Se(y.name).catch(()=>W):W,it=y?kn(y,tt):null;it&&qp(n,y,it);let yt=!!y&&!it&&Bn(y.agent,W);y&&!y.readyAt&&(yt||it||W&&jo(y.agent,W))&&(y.readyAt=Date.now(),await J());let Bt=!!y&&!y.readyAt&&Date.now()-(n.createdAt||0)<Cv;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&y&&jo(y.agent,W)&&!it){let x=await H(),q=Z(x,n.id);q?.setup&&!q.setup.connectedAt&&(q.setup.connectedAt=Date.now(),n.setup.connectedAt=q.setup.connectedAt,await C(q,{type:"provider.connected",actor:"system",source:"pane",boxId:y.name,provider:y.agent,payload:{provider:y.agent,ts:q.setup.connectedAt}}).catch(()=>{}),await J())}let Ye=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Ut=null;if(c){let x=Date.now()-i.startedAt;if(i.viewId&&!yt){let q=Yo(n.id);q&&(ot(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:q.finishedAt}),await J())}else if(it||x>ud&&!yt&&!i.viewId||x>Za){let q=it?"needs-input":x>Za?"timeout":"no-view";Yo(n.id,{error:q})&&await C(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:y?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:q}}).catch(()=>{})}else Ut={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:w,context:A?{pct:A.pct,occupancy:A.occupancy,window:A.window,windowKnown:A.windowKnown,model:A.model}:null,models:{current:O,options:Dp(y?.agent)},pendingPrompt:it,working:yt,leadStarting:Bt,handoff:Ye,view:Ut,queued:Nv(n.id).filter(x=>x.kind!=="ship-notify")}}function Mv(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function wh(t,e){let n=await nt(t);if(!n)return null;let r=gr(t).find(d=>d.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await F(n),a=Ps(s).filter(d=>d.box===o),i=Rc(s.filter(d=>d.boxId===o),a.filter(d=>d.kind==="message")).map(d=>({kind:"command-batch",id:d.id,ts:d.pending?d.ts:(d.anchorTs??d.ts)-1,anchorTs:d.anchorTs,anchorBox:d.anchorBox,agent:d.agent,boxRole:d.boxRole,commands:d.commands,pending:d.pending})),c=[...a,...i].filter(d=>d.ts!=null).sort((d,f)=>d.ts-f.ts),l=n.boxes.find(d=>d.name===o)||null,u=!1;if(l&&l.state==="live"){let d=await ht(o,{lines:At}).catch(()=>"");u=Bn(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function el(t,{promptId:e,answers:n,text:r}={}){let o=Z(await H(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(f=>f.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await z(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await ic(s);if(!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let f=typeof r=="string"?r.trim():"";if(!f)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(f)||f.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await kt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await bt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.provider==="claude"||i.answerMode==="keys"){let f=xp(i,n||[]);await bt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=up(i,n||[]);await kt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:f,count:1}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(f=>l.options?.[f]).filter(Boolean).map(f=>({label:f.label,description:f.description||""})),d=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...d?{control:d}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Gp(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function gh(t,{promptId:e,questionIndex:n}={}){let r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await z(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await ic(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=Sp(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await G({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var Pv=1500,io=new Map;function nl(t){let e=io.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},io.set(t,e)),e}function Nv(t){return(io.get(t)?.items??[]).map(e=>({...e}))}function Xs(t){let e=io.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function yh(t){let e=nl(t);e.timer||(e.timer=setInterval(()=>Bv(t).catch(()=>{}),Pv))}async function Bv(t){let e=io.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=Z(await H(),t);if(!n||n.archivedAt){Xs(t);return}let r=po(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){dr(e,!1);return}let o=await z(n.leadBox);if(!o){dr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await ht(n.leadBox,{lines:At}).catch(()=>"");if(Do(s?.agent,a)){dr(e,!1);return}if(rr(s?.agent,a)){let c=await Se(n.leadBox).catch(()=>a);if(kn(s,c)){dr(e,!1);return}}if(!dr(e,!0))return;let i=e.items.shift();e.idleSince=null,await bh(n,s,o,i.text,i.kind??null,i.attachments??null),await J()}finally{e.flushing=!1}}}async function bh(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=kv(r,t.pendingHandoffHydrate.artifact||zs),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=nh(c.map(d=>d.rel));a=a.trim()?`${a.trim()}
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
${u}`:u}let l=ot(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}});delete t.draft;try{await Ae({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:Vs(n.agent)}),await kt(t.leadBox,a,{...ce(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw ot(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}i&&await G({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function kh(t,{actions:e}={}){nl(t).items.push({id:fo(),text:nm(e),ts:Date.now(),kind:"ship-notify"}),yh(t)}async function ge(t,e,{kind:n=null,attachments:r=null}={}){let o=Array.isArray(r)?r.filter(f=>f&&typeof f.rel=="string"):[];if((typeof e!="string"||!or(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=await z(s.leadBox);if(!a&&await al(s)&&(await J(),a=await z(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(f=>f.name===s.leadBox),c=or(e),l=nl(t),u=await ht(s.leadBox,{lines:At}).catch(()=>""),d=l.items.length>0||Do(i?.agent,u);if(!d&&rr(i?.agent,u)){let f=await Se(s.leadBox).catch(()=>u);d=!!kn(i,f)}if(d){let f={id:fo(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(f),yh(t),delete s.draft,await J(),{ok:!0,leadBox:s.leadBox,queued:!0,id:f.id,position:l.items.length}}return await bh(s,i,a,c,n,o),await J(),{ok:!0,leadBox:s.leadBox}}var ih="Spin up a live preview View of this app so I can see it on another device. From the repo root, run `bostrat-view up --name \"<short name>\" -- <serve command>` \u2014 pick the right dev/serve command for this repo yourself (e.g. `npm run dev`), pass $PORT through if the command needs an explicit port, and share the public URL it prints. If a suitable View is already live, just re-share its URL instead of starting another. Don't paste a localhost link \u2014 it's dead on my device.\n\nIMPORTANT: if you cannot confidently identify WHICH project/app to serve, or which serve command to use (e.g. several apps in this tree, or no obvious dev script), do NOT guess and do NOT run `bostrat-view up`. Instead, reply with a short question asking me which one you should preview, and stop.";async function xh(t,{hint:e}={}){let n=Z(await H(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=Qa(n.id);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o=n.boxes.find(i=>i.name===n.leadBox),s=String(e||"").trim()?String(e).trim().slice(0,40):"preview";dd(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${ih}
|
|
103
|
+
|
|
104
|
+
What to preview: ${String(e).trim()}`:ih;try{await ge(t,a,{kind:"spin-up-view"})}catch(i){throw Yo(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function rl(t){let e=Z(await H(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);let n=e.boxes.find(c=>c.name===e.leadBox),r=Xs(t),o=await z(e.leadBox),s=o?await Lv(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await Hv(e,n):null,i=o?await Fv(e).catch(()=>null):null;return await G({flow:"interrupt",phase:"sent",trackId:t,sessionName:e.leadBox,agent:n?.agent,dropped:r.length,escSends:s.sends,stopped:s.stopped,unsent:!!a,shipDismissed:!!i}).catch(()=>{}),{ok:!0,interrupted:!!o,stopped:s.stopped,dropped:r.length,restored:a}}var Dv=[300,350,400],jv=3;async function Lv(t,e){let n=0;for(let r=0;r<jv;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of Dv){await Zs(o);let s=await ht(t,{lines:At}).catch(()=>"");if(!Bn(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function Fv(t){let e=Hc(await F(t));return e?.offerId?(await C(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var Uv=[250,400,500,600];async function Hv(t,e){if(e?.agent!=="claude")return null;let n=[...t.events||[]].reverse().find(r=>r.type==="said"&&r.text);if(!n)return null;for(let r of Uv){await Zs(r);let o=await ht(t.leadBox,{lines:At}).catch(()=>""),s=cc(e.agent,o);if(s&&Ap(n.text,s))return await Wv(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},ot(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await J(),n.text}return null}async function Wv(t,e,n){for(let r=0;r<3;r++){await Mo(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Zs(200);let o=await ht(t,{lines:At}).catch(()=>""),s=cc(e,o);if(!s)return!0;n=s.length+8}return!1}async function Sh(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=Z(await H(),t);if(!n)throw new Error(`track "${t}" not found`);let r=or(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await J(),{ok:!0,draft:n.draft||null}}async function vh(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await z(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(d=>d.name===r.leadBox)?.agent||o.agent||"claude",i=jp(a,n),l=[...r.events||[]].reverse().find(d=>d.type==="model-change")?.to??null;l||(l=(await $r(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await Ae({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:Vs(o.agent)});let u=await Lp(a,r.leadBox,i);return ot(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await J(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Ah(t){for(let e of t){if(!e||!await Ss(e))continue;let n=await _p(e).catch(()=>null);if(!n)continue;if((await j.stat(L.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Rt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:L.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function to(t){let e=[];for(let n of Rt(t?.workspace)){if(!n?.dir)continue;let r=await pc(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await mc(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Th(t){let e=t;typeof e=="string"&&!L.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await Bi(e));let n=await Ah([e]);if(!n)throw new Error(`tagged repo "${t}" is not a usable base (need a primary checkout or a synced owner/name)`);return n}async function qv(t,e,n,r){let o=`track/${ll(e)}-${t}`,s=[];try{await j.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Th(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await Y(c).catch(()=>null),u=L.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${L.basename(c)}-${p}`;a.add(u);let d=L.join(n,u),f=await vs(c);await sr(c,d,o,f),s.push({name:u,dir:d,base:c,baseRef:f,repo:l})}return await Ys(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Lt(i.base,i.dir).catch(()=>{});throw await j.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var _h=t=>({branch:t.branch,kind:t.kind,repos:(t.repos??[]).filter(Boolean).map(({name:e,base:n,baseRef:r,repo:o})=>({name:e,base:n,baseRef:r,repo:o}))});async function Eh(t,e){let n=await H(),r=Z(n,t);if(!r)throw new Error(`track "${t}" not found`);if(r.archivedAt)throw new Error("track is archived \u2014 unarchive it first");let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track \u2014 a single-repo Track's workspace IS its repo checkout; create the Track with multiple repos tagged instead");let s=await Th(typeof e=="string"?e.trim():e);if((o.repos??[]).some(f=>f?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await Y(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(f=>f?.name).filter(Boolean)]),c=L.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let f=2;i.has(c);f++)c=`${L.basename(s)}-${f}`;let l=L.join(o.dir,c),u=await vs(s);await Ep(s,o.branch)?await Ip(s,l,o.branch):await sr(s,l,o.branch,u);let d={name:c,dir:l,base:s,baseRef:u,repo:a};return o.repos=[...(o.repos??[]).filter(Boolean),d],await Ys(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"add",repo:a??s,name:c,workspace:_h(o)}),await J(),d}async function Ih(t,e){let n=await H(),r=Z(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of Gt.values())if(c.trackId===t&&!c.finishedAt)throw new Error("a subtask is still running on this Track \u2014 wait for it to merge back before untagging");let a=await pc(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await mc(s.dir,s.baseRef).catch(()=>0);if(a||i>0)throw new Error(`"${s.name}" has work (${a?"uncommitted changes":`${i} commit${i===1?"":"s"} ahead of ${s.baseRef??"base"}`}) \u2014 ship or discard it first`);return await Lt(s.base,s.dir).catch(()=>{}),await j.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await Ys(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:_h(o)}),await J(),{removed:s.name}}async function Gv(t,e,{repo:n,repos:r,cwd:o}={}){await j.mkdir(Yc,{recursive:!0});let s=L.join(Yc,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return qv(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!L.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await Bi(n));let i=await Ah([n,o,yv]);if(i){let c=`track/${ll(e)}-${t}`;try{let l=await vs(i);await sr(i,s,c,l),await Zc(s);let u=await Y(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await j.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await j.mkdir(s,{recursive:!0}),await Rp(s),await Zc(s),{dir:s,base:null,branch:null,kind:"repo"}}var co="# bostrat workspace",so=`${co}
|
|
86
105
|
|
|
87
106
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
88
107
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -154,6 +173,22 @@ configurable ticket automations \u2014 if asked to automate or suppress a ticket
|
|
|
154
173
|
behavior that isn't one of them, say it isn't configurable yet instead of
|
|
155
174
|
working around it. Never move the issue or edit its status by other means.
|
|
156
175
|
|
|
176
|
+
When the operator asks you to CREATE tracker work from scoping ("make tickets
|
|
177
|
+
for this", "we should have a project + issues for each item", "do the task
|
|
178
|
+
ceremony"), do NOT create issues by other means \u2014 write the plan as JSON and run:
|
|
179
|
+
|
|
180
|
+
bostrat-ticket propose <plan.json>
|
|
181
|
+
|
|
182
|
+
Plan shape: \`{ "epic": {"name","description"}, "issues":
|
|
183
|
+
[{"title","description","priority"}], "team": "KEY", "linkCurrentTrack": true }\`
|
|
184
|
+
(epic optional; the \`bostrat-ticket\` header documents the details). This posts a
|
|
185
|
+
one-tap review card in the operator's chat; NOTHING is created until they
|
|
186
|
+
confirm. Proposing is a BLOCKING prompt: once the CLI reports the card was
|
|
187
|
+
posted, STOP and end your turn \u2014 do not start (or resume) implementation, even
|
|
188
|
+
under an earlier go-ahead, until the operator's decision on the card comes back
|
|
189
|
+
to you as a chat message. A ticket ask mid-build means they want the tickets
|
|
190
|
+
agreed BEFORE the build continues.
|
|
191
|
+
|
|
157
192
|
When you FINISH a piece of work and steps remain that ONLY the operator can
|
|
158
193
|
perform (vendor/app registrations, secrets to set, interactive logins, device or
|
|
159
194
|
cert installs, DNS changes, subscriptions, physical steps), write those steps as
|
|
@@ -188,9 +223,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
188
223
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
189
224
|
implementation turn this way, every time. When the operator has already asked you
|
|
190
225
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
191
|
-
`;async function
|
|
192
|
-
`)),
|
|
193
|
-
`);return`${
|
|
226
|
+
`;async function Zc(t){try{await j.writeFile(L.join(t,"AGENTS.md"),so,{flag:"wx"})}catch{return}await Jv(t,["/AGENTS.md"])}var zc=t=>typeof t=="string"&&(t===co||t.startsWith(`${co}
|
|
227
|
+
`)),Kv="# bostrat multi-repo workspace";function Vv(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
228
|
+
`);return`${Kv}
|
|
194
229
|
|
|
195
230
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
196
231
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -202,22 +237,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
202
237
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
203
238
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
204
239
|
or preview an app, run it from that app's own repo directory.
|
|
205
|
-
`}async function
|
|
206
|
-
`,`${
|
|
240
|
+
`}async function Ys(t,e,n){let r=Vv(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await j.writeFile(L.join(t,o),r).catch(()=>{})}var Xc=so.replace(`${co}
|
|
241
|
+
`,`${co}
|
|
207
242
|
|
|
208
243
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
209
244
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
210
245
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
211
246
|
When working anywhere else, ignore everything below.
|
|
212
|
-
`);async function
|
|
213
|
-
`).includes(s));o.length&&await
|
|
247
|
+
`);async function Rh(t){let{stdout:e}=await wv("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return L.resolve(t,e.trim())}async function Jv(t,e){try{let n=await Rh(t);await j.mkdir(L.dirname(n),{recursive:!0});let r=await j.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
248
|
+
`).includes(s));o.length&&await j.appendFile(n,(r.endsWith(`
|
|
214
249
|
`)||!r?"":`
|
|
215
250
|
`)+o.join(`
|
|
216
251
|
`)+`
|
|
217
|
-
`)}catch{}}async function
|
|
252
|
+
`)}catch{}}async function zv(t,e){try{let n=await Rh(t),r=await j.readFile(n,"utf8"),o=r.split(`
|
|
218
253
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
219
|
-
`).length&&await
|
|
220
|
-
`))}catch{}}async function Rp(){let t={refreshed:0,removed:0,codexGlobal:null};try{await P.mkdir(Ie,{recursive:!0});for(let r of["CLAUDE.md","AGENTS.md"])await P.writeFile(U.join(Ie,r),$r).catch(()=>{});let e=process.env.CODEX_HOME||U.join(process.env.HOME||"/tmp",".codex");if((await P.stat(e).catch(()=>null))?.isDirectory()){let r=U.join(e,"AGENTS.md"),o=await P.readFile(r,"utf8").catch(()=>null);o===null?(await P.writeFile(r,Li,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):ji(o)&&o!==Li&&(await P.writeFile(r,Li).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Fi,Kn]){let o=await P.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=U.join(r,s.name),i=U.join(a,"AGENTS.md"),c=await P.readFile(i,"utf8").catch(()=>null);c!==null&&ji(c)&&c!==$r&&(await P.writeFile(i,$r).catch(()=>{}),t.refreshed++);let l=U.join(a,"CLAUDE.md"),u=await P.readFile(l,"utf8").catch(()=>null);u!==null&&ji(u)&&(await P.rm(l,{force:!0}).catch(()=>{}),await k0(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function x0(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of f0(e))n.base&&n.dir&&await Ze(n.base,n.dir).catch(()=>{});await P.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Ze(e.base,e.dir):await P.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Ji({name:t,cwd:e,repo:n,repos:r,agent:o="claude",skipPermissions:s=!0}){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");let a=await rs(o,s),i=await H(),c=ts().slice(0,8),l=await h0(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(g=>g.name))),d=`${t.trim()} lead`;u.has(Fs(d))&&(d=`${t.trim().slice(0,50).trim()} ${c} lead`);let f=await Ue(d,l.dir,{agentCmd:a,agent:o});lo({cwd:f.cwd,sessionName:f.name,agent:o,agentCmd:a});let w={id:c,name:t.trim(),cwd:f.cwd,workspace:l,leadBox:f.name,boxes:[Bp(f,"lead","live")],events:[],createdAt:Date.now()};return it(w,"opened",{agent:o,box:f.name,skipPermissions:s,name:w.name,cwd:w.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:m,base:g,baseRef:x,repo:A})=>({name:m,base:g,baseRef:x,repo:A}))}:{}}}),i.tracks.push(w),await K(),Re(w)}var S0=new Set(["claude","codex","cursor"]);async function $p({provider:t="claude",fresh:e=!1}={}){if(!S0.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await H()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await Re(r);if(u.boxes.find(f=>f.name===u.leadBox)?.state==="live")return u}await Vn(r.id).catch(()=>{})}let o=(await rs(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=ts().slice(0,8),a=U.join(Ie,"setup",s);await P.mkdir(a,{recursive:!0});let i=await Ue(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&lo({cwd:i.cwd,sessionName:i.name,agent:t,agentCmd:o});let c={id:s,name:`Connect ${t}`,kind:"setup",setup:{provider:t,startedAt:Date.now(),connectedAt:null},cwd:i.cwd,workspace:{dir:a,base:null,branch:null,kind:"scratch"},leadBox:i.name,boxes:[Bp(i,"lead","live")],events:[],createdAt:Date.now()};return it(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await H()).tracks.push(c),await K(),Re(c)}async function Op(t,e,n){await ff(t).catch(()=>{});let r=Mr.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),Mr.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of Jn(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await tc(a).catch(()=>{}),Zt.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await H(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await Wt(a.name).catch(()=>{})}async function Cp(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await H(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await Wf(s),await Op(t,s,"track deleted"),n?await np(t).catch(()=>{}):await ep(t).catch(()=>{}),await id(),await x0(s),e&&await qf(t).catch(()=>{}),it(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await K(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Vn(t){let e=await H(),n=Q(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);await Op(t,n,"track archived");for(let r of n.boxes)an.has(r.state)||(r.state="stopped");return n.archivedAt=Date.now(),it(n,"archived",{boxes:n.boxes.length}),await K(),Re(n)}async function tn(t,e){let n=await Tt(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;try{return await Vn(n.trackId),!0}catch(r){if(/already archived/i.test(String(r?.message||r)))return!1;throw r}}async function zi(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||an.has(e.state)||await G(e.name))return!1;let n=hp(e.agent,e.sessionId);if(!await Us(n))return it(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{return await Ue(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0}),lo({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",it(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return it(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Mp(t){let e=await H(),n=Q(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,it(n,"unarchived",{}),await zi(n),await K(),Re(n)}async function Pp(){let t=await H(),e=new Set((await vn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await Wt(a.name).catch(()=>{}),it(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!an.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await K(),r}async function Np(t){let e=await H(),n=Q(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(an.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await G(n.leadBox)){let a=await pt(n.leadBox,{lines:$t}).catch(()=>"");if(io(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Wt(n.leadBox),r.state="stopped"}let s=await zi(n);if(await K(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Re(n)}var Bp=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,createdAt:Date.now()});function Dp(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var Mr=new Map,Br=t=>Mr.get(t)||(Yt?Q(Yt,t)?.handoff:null)||null;async function Qo(t,e){let n=await H(),r=Q(n,t);r&&(r.handoff={...r.handoff||{},...e},await K())}function v0(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function jp(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=v0(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await G(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),d=Dp(l),f=d[d.length-1],w=f.agent,m=f.sessionId??null;f.endedAt=u,r.reason&&(f.endReason=r.reason);let g=c.agent==="claude"&&!!c.sessionId;d.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:g,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=g,l.state="live",n.pendingHandoffHydrate={artifact:os,at:u},it(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:w,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:d.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await L({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await L({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Wt(i).catch(()=>{}),a&&await Wt(a).catch(()=>{})}return e&&await K(),e}async function Xi(t,e,n=!0,r=null){let o=Mr.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Q(await H(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await rs(e,n),i=await G(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:wp(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return Mr.set(t,c),await Qo(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),T0(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Qo(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),L({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),L({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function T0(t,e,n,r,o){let s=async(T,E={})=>{t.step=T,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${T}`),await Qo(t.trackId,{step:T,toBox:t.toBox}),await L({flow:"lead-handoff",phase:T,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:E.sessionName,...E})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=Q(a,t.trackId)?.boxes.find(T=>T.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Bl(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await sa(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await we({cwd:n.cwd,sessionId:d.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ns(o)});let f=await pt(t.fromBox,{lines:$t}).catch(()=>"");if(!co(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let T=hp(c,l,{skipPermissions:t.skipPermissions});await sa(t.fromBox,{agentCmd:T,agent:c,resumeSessionId:l,pinned:u});let E=await H(),J=Q(E,t.trackId);if(J){let M=J.boxes.find(I=>I.name===t.fromBox);M&&(M.state="live"),it(J,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await K()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let w=await H(),m=Q(w,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let g=m.boxes.find(T=>T.name===t.fromBox);if(!g)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let x=Date.now(),A=Dp(g),S=A[A.length-1];S.endedAt=x,t.reason&&(S.endReason=t.reason),A.push({agent:o,sessionId:d.sessionId,pinned:d.pinned,startedAt:x,endedAt:null}),g.agent=o,g.sessionId=d.sessionId,g.pinned=d.pinned,g.state="live",g.role="lead",m.pendingHandoffHydrate={artifact:os,at:x},it(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:d.sessionId,epoch:A.length-1,singleBox:!0,forced:d.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await K(),await s("done",{sessionName:t.fromBox}),await Qo(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var A0=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,_0=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Kn=U.join(Ie,"worktrees"),Ui=[Zo,qi],Zt=new Map;var Jn=t=>{let e=new Map([...Zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Yt?Q(Yt,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},Yi=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function E0(t,e,n){let r=await H(),o=Q(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await K())}var Pr=t=>E0(t.trackId,t.id,Yi(t));async function Gn(t,e){t.step=e,await Pr(t)}function I0(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function R0(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function $0(t,e,n){if(!R0(n)||Zt.has(e)||!await G(n.box).catch(()=>null))return!1;try{await P.access(n.dir)}catch{return!1}let o=await G(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Zt.set(e,s),Wp(s,o).catch(a=>Fp(s,a)),!0}async function Lp(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Zt.has(r))continue;if(await $0(n,r,o)){e=!0,it(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await L({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=I0(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!an.has(i.state)&&(i.state="canceled"),it(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await L({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await pp(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||an.has(r.state)||Zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?U.join(Kn,`${n.id}-${r.subtaskId}`):null;await L({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await pp(n,r.name,o)}}return e=await O0(t)||e,e&&await K(),e}async function pp(t,e,n){if(e&&await Wt(e).catch(()=>{}),n){let r=await G(t.leadBox).catch(()=>null);r?.cwd&&await Ze(r.cwd,n).catch(()=>{})}}async function O0(t){let e;try{e=await P.readdir(Kn)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Zt.values())o.dir&&n.add(U.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=U.join(Kn,o),a=(t.tracks||[]).find(c=>o.startsWith(`${c.id}-`)),i=a?await G(a.leadBox).catch(()=>null):null;i?.cwd?await Ze(i.cwd,s).catch(()=>{}):await P.rm(s,{recursive:!0,force:!0}).catch(()=>{}),await L({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Zi=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Nr=class extends Error{};async function Up(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Mn(n))throw new Error("task (string) required");let s=await rs(e,r),a=Q(await H(),t);if(!a)throw new Error(`track "${t}" not found`);if(a.workspace?.kind==="multi")throw new Error("subtask delegation isn't available on a multi-repo Track yet \u2014 ask the lead directly, or use a single-repo Track");let i=await G(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");if(!await Ua(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let c=ts().slice(0,8),l={id:c,trackId:t,trackName:a.name,agent:e,task:Mn(n),skipPermissions:r,reason:wp(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Zi(a.name)}-${c}`,base:null,dir:U.join(Kn,`${t}-${c}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Zt.set(c,l),await Pr(l),C0(l,i,s).catch(u=>Fp(l,u)),l}async function Fp(t,e){t.failedStep=t.step,t.error=e instanceof Nr?"canceled":e?.message||String(e),t.step=e instanceof Nr?"canceled":"error",t.finishedAt=Date.now(),await tc(t).catch(()=>{}),await Pr(t).catch(()=>{}),await B0(t).catch(()=>{})}function Qi(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function Hp(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function C0(t,e,n){await Gn(t,"snapshot"),t.base=await Hu(e.cwd,`bostrat: snapshot for subtask ${t.id}`),await Gn(t,"spawn"),await P.mkdir(Kn,{recursive:!0}),await bo(e.cwd,t.dir,t.branch,t.base),await Hi(t.dir),await M0(t,e);let r=t.trackName.slice(0,38).trim(),o=await Ue(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await P0(t,o),await gp(t.trackId,o.name,Kx(t.task),{kind:"subtask"}),await Wp(t,e)}async function Wp(t,e){await Gn(t,"working");let n=U.join(t.dir,qi);if(await j0(n,t),t.canceled)throw new Nr;await Gn(t,"merge"),await Wu(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Gu(e.cwd,t.base,t.branch,Ui),t.files=await Ku(e.cwd,t.base,t.branch,Ui);let r=await Vu(e.cwd,t.base,t.branch,Ui);t.applied=r.applied,t.conflicts=r.conflicts,await Pr(t);let o=await D0(t,e,n);await Gn(t,"resume"),await gp(t.trackId,t.leadBox,Vx(t.task,t.stat,t.conflicts,o),{kind:"subtask-resume"}),await Gn(t,"reap"),await tc(t),t.step="done",t.finishedAt=Date.now(),await N0(t),await Pr(t)}async function M0(t,e){let n=U.join(t.dir,U.dirname(Zo));await P.mkdir(n,{recursive:!0});let r="";try{r=await wl(e.cwd,e.sessionId)}catch{}if(!r)try{r=await P.readFile(U.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
254
|
+
`).length&&await j.writeFile(n,o.join(`
|
|
255
|
+
`))}catch{}}async function $h(){let t={refreshed:0,removed:0,codexGlobal:null};try{await j.mkdir(qe,{recursive:!0});for(let r of["CLAUDE.md","AGENTS.md"])await j.writeFile(L.join(qe,r),so).catch(()=>{});let e=process.env.CODEX_HOME||L.join(process.env.HOME||"/tmp",".codex");if((await j.stat(e).catch(()=>null))?.isDirectory()){let r=L.join(e,"AGENTS.md"),o=await j.readFile(r,"utf8").catch(()=>null);o===null?(await j.writeFile(r,Xc,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):zc(o)&&o!==Xc&&(await j.writeFile(r,Xc).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Yc,mr]){let o=await j.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=L.join(r,s.name),i=L.join(a,"AGENTS.md"),c=await j.readFile(i,"utf8").catch(()=>null);c!==null&&zc(c)&&c!==so&&(await j.writeFile(i,so).catch(()=>{}),t.refreshed++);let l=L.join(a,"CLAUDE.md"),u=await j.readFile(l,"utf8").catch(()=>null);u!==null&&zc(u)&&(await j.rm(l,{force:!0}).catch(()=>{}),await zv(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function Xv(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Rt(e))n.base&&n.dir&&await Lt(n.base,n.dir).catch(()=>{});await j.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Lt(e.base,e.dir):await j.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function ol({name:t,cwd:e,repo:n,repos:r,agent:o="claude",skipPermissions:s=!0}){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");let a=await Js(o,s),i=await H(),c=fo().slice(0,8),l=await Gv(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(h=>(h.boxes||[]).map(m=>m.name))),d=`${t.trim()} lead`;u.has(Ea(d))&&(d=`${t.trim().slice(0,50).trim()} ${c} lead`);let f=await tn(d,l.dir,{agentCmd:a,agent:o});Lo({cwd:f.cwd,sessionName:f.name,agent:o,agentCmd:a});let p={id:c,name:t.trim(),cwd:f.cwd,workspace:l,leadBox:f.name,boxes:[Fh(f,"lead","live")],events:[],createdAt:Date.now()};return ot(p,"opened",{agent:o,box:f.name,skipPermissions:s,name:p.name,cwd:p.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:h,base:m,baseRef:b,repo:k})=>({name:h,base:m,baseRef:b,repo:k}))}:{}}}),i.tracks.push(p),await J(),Ge(p)}var Yv=new Set(["claude","codex","cursor"]);async function Oh({provider:t="claude",fresh:e=!1}={}){if(!Yv.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await H()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await Ge(r);if(u.boxes.find(f=>f.name===u.leadBox)?.state==="live")return u}await wr(r.id).catch(()=>{})}let o=(await Js(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=fo().slice(0,8),a=L.join(qe,"setup",s);await j.mkdir(a,{recursive:!0});let i=await tn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&Lo({cwd:i.cwd,sessionName:i.name,agent:t,agentCmd:o});let c={id:s,name:`Connect ${t}`,kind:"setup",setup:{provider:t,startedAt:Date.now(),connectedAt:null},cwd:i.cwd,workspace:{dir:a,base:null,branch:null,kind:"scratch"},leadBox:i.name,boxes:[Fh(i,"lead","live")],events:[],createdAt:Date.now()};return ot(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await H()).tracks.push(c),await J(),Ge(c)}async function Ch(t,e,n){await gd(t).catch(()=>{});let r=lo.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),lo.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of gr(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await dl(a).catch(()=>{}),Gt.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await H(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await Vt(a.name).catch(()=>{})}async function Mh(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await H(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await td(s),await Ch(t,s,"track deleted"),n?await Rd(t).catch(()=>{}):await Id(t).catch(()=>{}),await Up(),await Xv(s),e&&await ed(t).catch(()=>{}),ot(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await J(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function wr(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);return n.archivedAt=Date.now(),ot(n,"archived",{boxes:n.boxes.length}),await J(),n.kind==="setup"?await Ph(t):Qv(t).catch(()=>{}),Ge(n)}var Zv=9e4;async function Qv(t){try{await tA(t)}catch{}await Ph(t).catch(()=>{})}async function Ph(t){let e=await H(),n=Z(e,t);if(!(!n||!n.archivedAt)){await Ch(t,n,"track archived");for(let r of n.boxes)An.has(r.state)||(r.state="stopped");await J()}}async function tA(t){let e=await H(),n=Z(e,t);if(!n||!n.archivedAt)return;let r=null,o="derived",s=(n.events||[]).some(i=>i.type==="said"),a=n.leadBox?await z(n.leadBox).catch(()=>null):null;if(a?.cwd&&s)try{r=await Tu(n.leadBox,a.cwd,{timeoutMs:Zv}),o="lead-cli"}catch{}r||(r=Ei(n,await F(n).catch(()=>[]))),r&&(n.beat={text:r,source:o,at:Date.now()},await C(n,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",boxId:n.leadBox||null,provider:a?.agent||null,payload:{text:r,source:o}}).catch(()=>{}),await J())}async function Nh(){let t=await H(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=Ei(o,await F(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await C(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await J(),{recorded:e,skipped:n,empty:r}}async function Fe(t,e){let n=await St(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await Bh(n);if(r.length){for(let o of r)await zn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await wr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function Bh(t){return(await Ce().catch(()=>[])).filter(n=>n.trackId===t.trackId&&n.state!=="merged"&&n.state!=="closed"&&n.state!=="lost"&&!(n.repo===t.repo&&n.number===t.number))}async function Ue(t,e){return sl(t,e,"merged")}async function sl(t,e,n="merged"){let r=await St(t,e).catch(()=>null);if(!r?.notifyOnMerge||r.archiveOnMerge||!r.trackId)return!1;let o={repo:r.repo??null,number:r.number,title:r.title??null,url:r.url??null,headRef:r.headRef??null,outcome:n},s=await Bh(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await mf(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await ge(r.trackId,eA(a),{kind:"merge-notify"}),!0}catch{return!1}}var ch={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function eA(t){let e=i=>`${i.repo?`${i.repo}`:""}#${i.number}${i.title?` "${i.title}"`:""}${i.url?` (${i.url})`:""}`;if(t.length===1){let i=t[0],c=`#${i.number}${i.title?` "${i.title}"`:""}`;return i.outcome!=="merged"?`PR ${c}${i.url?` (${i.url})`:""}${i.headRef?` \u2014 branch ${i.headRef} \u2014`:""} was ${ch[i.outcome]??i.outcome}. This is an automated notification: the work did NOT land \u2014 check whether it still needs to ship another way. If not, say so briefly.`:`PR ${c}${i.url?` (${i.url})`:""}${i.headRef?` \u2014 branch ${i.headRef} \u2014`:""} has merged. This is an automated merge notification: pick up whatever follow-up work this merge unblocks (dependent tasks, cleanup, next steps that were waiting on it). If nothing needs doing, say so briefly.`}let n=t.filter(i=>i.outcome==="merged"),r=t.filter(i=>i.outcome!=="merged"),o=r.length?`${n.length} of the ${t.length} PRs on this Track merged; ${r.length} did not land:`:`All ${t.length} PRs on this Track have merged:`,s=t.map(i=>`- ${e(i)} \u2014 ${ch[i.outcome]??i.outcome}`).join(`
|
|
256
|
+
`),a=n.length?r.length?`This is an automated merge notification: pick up whatever follow-up work the merged PR${n.length===1?"":"s"} unblock${n.length===1?"s":""} \u2014 and note the unmerged one${r.length===1?"":"s"}: that work did NOT land, so check whether it still needs to ship. If nothing needs doing, say so briefly.`:"This is an automated merge notification: pick up whatever follow-up work these merges unblock (dependent tasks, cleanup, next steps that were waiting on them). If nothing needs doing, say so briefly.":"This is an automated notification: NONE of the PRs landed \u2014 the work did not ship. Check whether it still needs to go out another way. If not, say so briefly.";return`${o}
|
|
257
|
+
${s}
|
|
258
|
+
${a}`}async function al(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||An.has(e.state)||await z(e.name))return!1;let n=dh(e.agent,e.sessionId);if(!await _a(n))return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{return await tn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0}),Lo({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",ot(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Dh(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,ot(n,"unarchived",{}),await al(n),await J(),Ge(n)}async function jh(){let t=await H(),e=new Set((await Pn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await Vt(a.name).catch(()=>{}),ot(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!An.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await J(),r}async function Lh(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(An.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await z(n.leadBox)){let a=await ht(n.leadBox,{lines:At}).catch(()=>"");if(Do(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Vt(n.leadBox),r.state="stopped"}let s=await al(n);if(await J(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ge(n)}var Fh=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,createdAt:Date.now()});function Uh(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var lo=new Map,po=t=>lo.get(t)||(se?Z(se,t)?.handoff:null)||null;async function Gs(t,e){let n=await H(),r=Z(n,t);r&&(r.handoff={...r.handoff||{},...e},await J())}function nA(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function Hh(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=nA(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await z(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),d=Uh(l),f=d[d.length-1],p=f.agent,h=f.sessionId??null;f.endedAt=u,r.reason&&(f.endReason=r.reason);let m=c.agent==="claude"&&!!c.sessionId;d.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:m,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=m,l.state="live",n.pendingHandoffHydrate={artifact:zs,at:u},ot(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:d.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Vt(i).catch(()=>{}),a&&await Vt(a).catch(()=>{})}return e&&await J(),e}async function il(t,e,n=!0,r=null){let o=lo.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Js(e,n),i=await z(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:fh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return lo.set(t,c),await Gs(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),rA(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Gs(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function rA(t,e,n,r,o){let s=async(E,M={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await Gs(t.trackId,{step:E,toBox:t.toBox}),await G({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=Z(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Ou(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await Ri(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ae({cwd:n.cwd,sessionId:d.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Vs(o)});let f=await ht(t.fromBox,{lines:At}).catch(()=>"");if(!jo(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=dh(c,l,{skipPermissions:t.skipPermissions});await Ri(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let M=await H(),lt=Z(M,t.trackId);if(lt){let I=lt.boxes.find($=>$.name===t.fromBox);I&&(I.state="live"),ot(lt,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await J()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),h=Z(p,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let m=h.boxes.find(E=>E.name===t.fromBox);if(!m)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let b=Date.now(),k=Uh(m),v=k[k.length-1];v.endedAt=b,t.reason&&(v.endReason=t.reason),k.push({agent:o,sessionId:d.sessionId,pinned:d.pinned,startedAt:b,endedAt:null}),m.agent=o,m.sessionId=d.sessionId,m.pinned=d.pinned,m.state="live",m.role="lead",h.pendingHandoffHydrate={artifact:zs,at:b},ot(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:d.sessionId,epoch:k.length-1,singleBox:!0,forced:d.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await J(),await s("done",{sessionName:t.fromBox}),await Gs(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var oA=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,sA=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,mr=L.join(qe,"worktrees"),fr=[ao,qs],Gt=new Map;var gr=t=>{let e=new Map([...Gt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=se?Z(se,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},cl=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function aA(t,e,n){let r=await H(),o=Z(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await J())}var hr=t=>aA(t.trackId,t.id,cl(t));async function pr(t,e){t.step=e,await hr(t)}function iA(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function cA(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function lA(t,e,n){if(!cA(n)||Gt.has(e)||!await z(n.box).catch(()=>null))return!1;try{await j.access(n.dir)}catch{return!1}let o=await z(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Gt.set(e,s),Vh(s,o).catch(a=>Gh(s,a)),!0}async function Wh(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Gt.has(r))continue;if(await lA(n,r,o)){e=!0,ot(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=iA(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!An.has(i.state)&&(i.state="canceled"),ot(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await lh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||An.has(r.state)||Gt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?L.join(mr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await lh(n,r.name,o)}}return e=await uA(t)||e,e&&await J(),e}async function lh(t,e,n){if(e&&await Vt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Rt(t.workspace)){let s=L.join(n,o.name);await Lt(o.base,s).then(()=>!0,()=>!1)||await Lt(o.dir,s).catch(()=>{})}await j.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await z(t.leadBox).catch(()=>null);r?.cwd&&await Lt(r.cwd,n).catch(()=>{})}async function uA(t){let e;try{e=await j.readdir(mr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Gt.values())o.dir&&n.add(L.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=L.join(mr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Rt(a.workspace))await Lt(i.base,L.join(s,i.name)).catch(()=>{});await j.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await z(a.leadBox).catch(()=>null):null;i?.cwd?await Lt(i.cwd,s).catch(()=>{}):await j.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var ll=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",uo=class extends Error{};async function qh(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!or(n))throw new Error("task (string) required");let s=await Js(e,r),a=Z(await H(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await z(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let f=Rt(a.workspace);if(!f.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of f)if(!p?.dir||!await Ss(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=f.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await Ss(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=fo().slice(0,8),d={id:u,trackId:t,trackName:a.name,agent:e,task:or(n),skipPermissions:r,reason:fh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${ll(a.name)}-${u}`,base:null,repos:l,dir:L.join(mr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Gt.set(u,d),await hr(d),dA(d,i,s).catch(f=>Gh(d,f)),d}async function Gh(t,e){t.failedStep=t.step,t.error=e instanceof uo?"canceled":e?.message||String(e),t.step=e instanceof uo?"canceled":"error",t.finishedAt=Date.now(),await dl(t).catch(()=>{}),await hr(t).catch(()=>{}),await hA(t).catch(()=>{})}function ul(t){let e=Gt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function Kh(t){let e=Gt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function dA(t,e,n){if(await pr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await dc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await hr(t)}else t.base=await dc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await pr(t,"spawn"),await j.mkdir(mr,{recursive:!0}),t.repos){await j.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await sr(a.leadDir,L.join(t.dir,a.name),t.branch,a.base);let s=Rt((Z(await H(),t.trackId)||{}).workspace);await Ys(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await sr(e.cwd,t.dir,t.branch,t.base),await Zc(t.dir);await fA(t,e);let r=t.trackName.slice(0,38).trim(),o=await tn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await pA(t,o),await ph(t.trackId,o.name,Sv(t.task,!!t.repos),{kind:"subtask"}),await Vh(t,e)}async function Vh(t,e){await pr(t,"working");let n=L.join(t.dir,qs);if(await gA(n,t),t.canceled)throw new uo;if(await pr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=L.join(t.dir,c.name);await fc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await wc(c.leadDir,c.base,t.branch,fr),c.files=await gc(c.leadDir,c.base,t.branch,fr);let u=await yc(c.leadDir,c.base,t.branch,fr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(d=>L.join(c.name,d))),a.push(...c.conflicts.map(d=>d.startsWith("patch did not apply cleanly")?`${c.name}: ${d}`:L.join(c.name,d))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await fc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await wc(e.cwd,t.base,t.branch,fr),t.files=await gc(e.cwd,t.base,t.branch,fr);let o=await yc(e.cwd,t.base,t.branch,fr);t.applied=o.applied,t.conflicts=o.conflicts}await hr(t);let r=await wA(t,e,n);await pr(t,"resume"),await ph(t.trackId,t.leadBox,vv(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await pr(t,"reap"),await dl(t),t.step="done",t.finishedAt=Date.now(),await mA(t),await hr(t)}async function fA(t,e){let n=L.join(t.dir,L.dirname(ao));await j.mkdir(n,{recursive:!0});let r="";try{r=await iu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await j.readFile(L.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
221
259
|
|
|
222
260
|
**Delegated task:** ${t.task}
|
|
223
261
|
|
|
@@ -228,53 +266,42 @@ When working anywhere else, ignore everything below.
|
|
|
228
266
|
## Conversation so far (lead \u2192 you)
|
|
229
267
|
|
|
230
268
|
${r}
|
|
231
|
-
`;await
|
|
232
|
-
|
|
233
|
-
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
234
|
-
success
|
|
235
|
-
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
236
|
-
}
|
|
237
|
-
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function dS(t,e){let n=await Qt(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function fS(t,e,n){if(!(await Qt(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function Xn(t){return((await Qt(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}async function ic(t,{teamId:e,name:n,description:r}){let o=await Qt(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...r?{description:r}:{}}}),s=o.projectCreate?.project;if(!o.projectCreate?.success||!s)throw new Error("linear: projectCreate did not succeed");return{id:s.id,url:s.url,name:s.name}}async function cc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await Qt(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var im={linear:{api:lc,config:ln},jira:{api:sc,config:cs}};async function hc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(im)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function fn(t){let e=im[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function wc(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var cm=()=>process.env.BOSTRAT_STATE_DIR||sm.join(process.env.HOME||pS.tmpdir(),".bostrat"),pc=()=>sm.join(cm(),"tickets.json"),lm=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),fs=t=>`${lm()}/track/${t}`,ds=null,uc=null,dc=Promise.resolve();async function ps(){return ds||(uc??=Ur.readFile(pc(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>ds=t).finally(()=>uc=null),uc)}async function tm(){let t=JSON.stringify({tickets:ds},null,2),e=`${pc()}.${process.pid}.tmp`;await Ur.mkdir(cm(),{recursive:!0}),await Ur.writeFile(e,t),await Ur.rename(e,pc())}async function Oe(){dc=dc.then(tm,tm),await dc.catch(()=>{})}async function te(t){return(await ps())[t]??null}async function um(){return{...await ps()}}async function dm(){return(await hc()).length>0}async function Yn(t,e,{source:n="api"}={}){let r=await hc();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let d=u.api.parseIssueRef(e);if(d)try{s=await u.api.resolveIssue(u.cfg,d),o=u;break}catch(f){a=f}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null},pr:null};(await ps())[t.id]=c,await Oe(),await R({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Fr(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await Lr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await mm(t.id,c,"linked"),c}async function gc(t){let e=await ps();return e[t]?(delete e[t],await Oe(),!0):!1}var mc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function fm(t,e){if(!t.track?.cwd)return null;let n=us(await B({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function mS(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${mc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${mc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function hS(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(mc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Fr(t,e){let n=await fn(e);if(!n)return;let r=await fm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:fs(t),title:`Bostrat Track: ${e.track.name}`,subtitle:hS(e,r)})}function ms(t){let e=new URL(lm());return`${e.protocol}//${t}.${e.host}/`}var wS={linked:"linked",opened:"prOpened",merged:"prMerged"};function gS(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function pm(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Oe(),await R({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function mm(t,e,n){let r=await fn(e);if(!r)return;let o=r.cfg.ticketFlow?.[wS[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=wc(s,a),c=gS(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await pm(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await R({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:am(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function hm(t){let e=await te(t);if(!e)throw new Error("no ticket linked to this Track");let n=await fn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=wc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Oe()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function yc(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await te(t);if(!r)throw new Error("no ticket linked to this Track");let o=await fn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=wc(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await Oe()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await pm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function wm(t,e){let n=await te(t);if(!n)throw new Error("no ticket linked to this Track");let r=await fn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await R({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function gm(t,e,n){if(!e)throw new Error("offerId required");let r=await B({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await yc(t.id,o.payload.to?.id,{mode:"offer"})),await R({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var em=50;async function ym(){let t=await ln();if(!t)throw new Error("no ticketing integration configured");return Xn(t)}function yS(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>em)throw new Error(`too many issues (${n.length}); cap is ${em} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function bS(t,e,n,r){let o=r??await Xn(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await te(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function bm(t,e){let n=await ln();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=yS(e),o=await Xn(n),s=await bS(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=am();return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function km(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await B({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await ln();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Xn(a)).find(g=>g.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],d=null;if(i.epic){let m=await ic(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});d=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let g=await cc(a,{teamId:c.id,title:m.title,description:m.description,projectId:d,priority:m.priority});l.push({kind:"issue",key:g.key,title:g.title,url:g.url})}catch(g){u.push({title:m.title,message:g?.message||String(g)})}await R({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let f=l.filter(m=>m.kind==="issue"),w=null;return i.linkCurrentTrack&&f.length===1&&!await te(t.id)&&(w=await Yn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:w?w.key:null}}async function Lr(t,e){let n=await fn(t);n&&await n.api.createComment(n.cfg,t,e)}async function kS(t,e,n){let r=await fn(t);if(!r)return null;let o=await Ae(e);if(!o)return null;let s=rn(o,n);if(!s)return null;let a=await Ur.readFile(Dt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var nm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),fc=new Map;function xS(t,e){if(nm()<=0)return Fr(t,e);if(fc.has(t))return;let n=setTimeout(()=>{fc.delete(t),te(t).then(r=>r?Fr(t,r):null).catch(()=>{})},nm());n.unref?.(),fc.set(t,n)}var rm=new Map;async function om(t,e,n){let r=rm.get(t)??0;Date.now()-r<600*1e3||(rm.set(t,Date.now()),await R({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function SS(t,{resolveTrack:e=null}={}){let n=await te(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await hc())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await Yn(s,o,{source:"chat"})}catch(a){await om(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await Oe())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Oe(),await Fr(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await Oe(),await Fr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await fm(n,t.trackId).catch(()=>null):null;await Lr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
238
|
-
|
|
239
|
-
Agent cost for this Track: ${mS(o)}.`:"")),await mm(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await kS(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Lr(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
240
|
-
|
|
241
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await Lr(n,`**Bostrat** \u2014 design decision: **${r.name}** picked${o?` over ${o}`:""}.`)}break}case"view.state.changed":{if(r.change!=="created"||r.restarted||!r.slug)break;await Lr(n,`**Bostrat** \u2014 live preview **${r.name}**: ${ms(r.slug)}
|
|
242
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await xS(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await om(t.trackId,s?.cwd??null,o)}}function xm({resolveTrack:t}){Nt.on("event",e=>{SS(e,{resolveTrack:t}).catch(()=>{})})}import vS from"ws";var Sm=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function hs(t,e,n={}){let r=await fetch(`${Sm()}/${t}`,{method:"POST",headers:{authorization:`Bearer ${e}`,"content-type":"application/json; charset=utf-8"},body:JSON.stringify(n)}),o=await r.json().catch(()=>null);if(!o?.ok)throw new Error(`slack ${t}: ${o?.error||`http ${r.status}`}`);return o}var bc=(t,e)=>hs("chat.postMessage",t,e),Zn=(t,e)=>hs("chat.update",t,e);async function Et(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function TS(t,e,n={}){let r=await fetch(`${Sm()}/${t}`,{method:"POST",headers:{authorization:`Bearer ${e}`,"content-type":"application/x-www-form-urlencoded; charset=utf-8"},body:new URLSearchParams(n).toString()}),o=await r.json().catch(()=>null);if(!o?.ok)throw new Error(`slack ${t}: ${o?.error||`http ${r.status}`}`);return o}async function vm(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await TS("files.getUploadURLExternal",t,{filename:e,length:String(n.length)}),l=await fetch(i,{method:"POST",body:n});if(!l.ok)throw new Error(`slack upload: http ${l.status}`);return hs("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var AS=2200;function Tm({getToken:t,onEnvelope:e,log:n=()=>{}}){let r=!1,o=null,s=null,a=1e3;async function i(){if(r)return;let l;try{let u=await t();if(!u)throw new Error("app token missing");({url:l}=await hs("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new vS(l),o.on("message",u=>{let d;try{d=JSON.parse(u)}catch{return}if(d.type==="hello"){a=1e3,n("socket connected");return}if(d.type==="disconnect"){try{o.close()}catch{}return}if(!d.envelope_id)return;let f=g=>{try{o.send(JSON.stringify(g?{envelope_id:d.envelope_id,payload:g}:{envelope_id:d.envelope_id}))}catch{}},w=!1,m=setTimeout(()=>{w=!0,f()},AS);Promise.resolve().then(()=>e(d)).then(g=>{clearTimeout(m),!w&&(w=!0,f(d.accepts_response_payload?g:void 0))},g=>{clearTimeout(m),w||(w=!0,f()),n(`envelope handler failed: ${g?.message||g}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var Em=()=>process.env.BOSTRAT_STATE_DIR||gs.join(process.env.HOME||ES.tmpdir(),".bostrat"),Tc=()=>gs.join(Em(),"slack.json"),ws=null,kc=null,xc=Promise.resolve();async function _c(){return ws||(kc??=Hr.readFile(Tc(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>ws=t).finally(()=>kc=null),kc)}async function Am(){let t=JSON.stringify({threads:ws},null,2),e=`${Tc()}.${process.pid}.tmp`;await Hr.mkdir(Em(),{recursive:!0}),await Hr.writeFile(e,t),await Hr.rename(e,Tc())}async function le(){xc=xc.then(Am,Am),await xc.catch(()=>{})}async function Ec(t){return(await _c())[t]??null}async function IS(t,e){let n=await _c();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var F=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function Wr(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function RS(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Wr(n.replace(/\n+/g," "),e)}function Im(t){let e=`*${F(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${F(t.repoLabel)} \xB7 \`${F(t.branch||"?")}\` \xB7 lead *${F(t.lead)}*`,r=t.kickoff?`> ${F(Wr(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
243
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${fs(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function Ac(t,e){await Zn(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:Im(e)})}async function mn(t,e,n,r=void 0){return bc(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var _m=new Set;function pn(t){_m.add(t),t.catch(()=>{}).finally(()=>_m.delete(t))}var et=t=>({response_type:"ephemeral",text:t}),$S="*/bostrat* \u2014 `start <name> [repo]` \xB7 `archive <track>` \xB7 `handoff <track> <agent>` \xB7 `stop <track>` \xB7 `list`\n_Reply in a Track's thread to steer its lead; question prompts and ship offers appear there as buttons._",OS=["claude","cursor","codex"];function Sc(t,e){let n=(e||"").toLowerCase(),r=t.filter(s=>!s.archivedAt),o=r.filter(s=>s.name.toLowerCase().startsWith(n));return o.sort((s,a)=>(a.lastResponseAt??a.createdAt??0)-(s.lastResponseAt??s.createdAt??0)),{match:o[0]??null,live:r}}var vc=(t,e)=>et(`No live Track matches \u201C${F(e)}\u201D. Live: ${t.map(n=>`\`${F(n.name)}\``).join(" \xB7 ")||"none"}`);async function Rm(t,e,{name:n,repo:r,responseUrl:o}){let s=t.deps;try{let a=(await s.listTracks()).filter(d=>!d.archivedAt);if(a.length>=e.maxConcurrent){await Et(o,{...et(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await Et(o,{...et("No designated channel configured \u2014 set `slack.channel` in integrations.json."),replace_original:!0});return}let i=await s.createTrack({name:n,...r?{repo:r}:{}}),c=i.workspace||{},l={trackId:i.id,channel:e.channel,name:i.name,branch:c.branch||null,repoLabel:c.repo||(c.base?gs.basename(c.base):"fresh workspace"),lead:"claude"},u=await bc(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:Im(l),unfurl_links:!1});l.ts=u.ts,(await _c())[i.id]=l,await le(),await Et(o,{...et(`\u2713 *${F(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Et(o,{...et(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function CS(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await Ec(n.id);s&&(s.archivedAt=Date.now(),await le(),await Ac(e,s).catch(()=>{})),await Et(r,{...et(`\u2713 *${F(n.name)}* archived.`),replace_original:!0})}catch(s){await Et(r,{...et(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function MS(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Et(o,{...et(`\u2713 Handing *${F(n.name)}* to ${F(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Et(o,{...et(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function PS(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.interruptLead(n.id);let s=(o.listSubtaskJobs?.(n.id)??[]).filter(a=>!a.finishedAt);for(let a of s)try{o.cancelSubtask(a.id)}catch{}await Et(r,{...et(`\u2713 Stopped *${F(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await Et(r,{...et(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function NS(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Wr(`${r.name} (${r.branch})`,75)},value:r.path})),{text:{type:"plain_text",text:"fresh workspace"},value:"__fresh__"}];return[{type:"section",block_id:JSON.stringify({name:t}),text:{type:"mrkdwn",text:`Repo for *${F(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function BS(t){let e=await t.listTracks(),n=e.filter(s=>!s.archivedAt);if(n.sort((s,a)=>(a.lastResponseAt??a.createdAt??0)-(s.lastResponseAt??s.createdAt??0)),!n.length)return"No live Tracks.";let r=n.map(s=>{let a=s.workspace||{},i=a.repo||(a.base?gs.basename(a.base):"fresh"),c=[...s.boxes||[]].reverse().find(u=>u.role==="lead")?.agent||"?",l=s.prompted?"\u26A0 needs an answer":s.working?"working":"idle";return`\u2022 *${F(s.name)}* \u2014 ${F(i)}${a.branch?` \`${F(a.branch)}\``:""} \xB7 lead ${F(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
269
|
+
`;await j.writeFile(L.join(t.dir,ao),o)}async function pA(t,e){let n=await H(),r=Z(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),ot(r,"subtask-delegated",{to:e.name,toAgent:t.agent,task:t.task,subtaskId:t.id,skipPermissions:t.skipPermissions,reason:t.reason}),r.subtasks=r.subtasks||{},r.subtasks[t.id]={...r.subtasks[t.id]||{},...cl(t)},await J())}async function mA(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),ot(n,"subtask-merged",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,stat:t.stat,conflicts:t.conflicts.length,files:t.files,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),await J()}async function hA(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),ot(n,r?"subtask-canceled":"subtask-failed",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,failedStep:t.failedStep||t.step,error:t.error,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),n.subtasks=n.subtasks||{},n.subtasks[t.id]={...n.subtasks[t.id]||{},...cl(t)},await J()}async function wA(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=L.join(e.cwd,r);await j.mkdir(L.dirname(o),{recursive:!0}),await j.copyFile(n,o)}catch{}return r}async function dl(t){if(t.box&&await Vt(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Lt(e.leadDir,L.join(t.dir,e.name)).catch(()=>{});await j.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await z(t.leadBox).catch(()=>null))?.cwd;n&&await Lt(n,t.dir).catch(()=>{})}}async function gA(t,e){let n=(e.startedAt||Date.now())+oA;for(;;){if(e.canceled||e.forceMerge)return;try{await j.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Zs(sA)}}function Zs(t){return new Promise(e=>setTimeout(e,t))}import mo from"node:fs/promises";import ea from"node:path";import SA from"node:os";import yA from"ws";var Jh=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function Qs(t,e,n={}){let r=await fetch(`${Jh()}/${t}`,{method:"POST",headers:{authorization:`Bearer ${e}`,"content-type":"application/json; charset=utf-8"},body:JSON.stringify(n)}),o=await r.json().catch(()=>null);if(!o?.ok)throw new Error(`slack ${t}: ${o?.error||`http ${r.status}`}`);return o}var fl=(t,e)=>Qs("chat.postMessage",t,e),yr=(t,e)=>Qs("chat.update",t,e);async function $t(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function bA(t,e,n={}){let r=await fetch(`${Jh()}/${t}`,{method:"POST",headers:{authorization:`Bearer ${e}`,"content-type":"application/x-www-form-urlencoded; charset=utf-8"},body:new URLSearchParams(n).toString()}),o=await r.json().catch(()=>null);if(!o?.ok)throw new Error(`slack ${t}: ${o?.error||`http ${r.status}`}`);return o}async function zh(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await bA("files.getUploadURLExternal",t,{filename:e,length:String(n.length)}),l=await fetch(i,{method:"POST",body:n});if(!l.ok)throw new Error(`slack upload: http ${l.status}`);return Qs("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var kA=2200;function Xh({getToken:t,onEnvelope:e,log:n=()=>{}}){let r=!1,o=null,s=null,a=1e3;async function i(){if(r)return;let l;try{let u=await t();if(!u)throw new Error("app token missing");({url:l}=await Qs("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new yA(l),o.on("message",u=>{let d;try{d=JSON.parse(u)}catch{return}if(d.type==="hello"){a=1e3,n("socket connected");return}if(d.type==="disconnect"){try{o.close()}catch{}return}if(!d.envelope_id)return;let f=m=>{try{o.send(JSON.stringify(m?{envelope_id:d.envelope_id,payload:m}:{envelope_id:d.envelope_id}))}catch{}},p=!1,h=setTimeout(()=>{p=!0,f()},kA);Promise.resolve().then(()=>e(d)).then(m=>{clearTimeout(h),!p&&(p=!0,f(d.accepts_response_payload?m:void 0))},m=>{clearTimeout(h),p||(p=!0,f()),n(`envelope handler failed: ${m?.message||m}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var Qh=()=>process.env.BOSTRAT_STATE_DIR||ea.join(process.env.HOME||SA.tmpdir(),".bostrat"),gl=()=>ea.join(Qh(),"slack.json"),ta=null,pl=null,ml=Promise.resolve();async function bl(){return ta||(pl??=mo.readFile(gl(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>ta=t).finally(()=>pl=null),pl)}async function Yh(){let t=JSON.stringify({threads:ta},null,2),e=`${gl()}.${process.pid}.tmp`;await mo.mkdir(Qh(),{recursive:!0}),await mo.writeFile(e,t),await mo.rename(e,gl())}async function ye(){ml=ml.then(Yh,Yh),await ml.catch(()=>{})}async function kl(t){return(await bl())[t]??null}async function vA(t,e){let n=await bl();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var K=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function ho(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function AA(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return ho(n.replace(/\n+/g," "),e)}function tw(t){let e=`*${K(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${K(t.repoLabel)} \xB7 \`${K(t.branch||"?")}\` \xB7 lead *${K(t.lead)}*`,r=t.kickoff?`> ${K(ho(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
270
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${ss(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function yl(t,e){await yr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:tw(e)})}async function _n(t,e,n,r=void 0){return fl(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var Zh=new Set;function Tn(t){Zh.add(t),t.catch(()=>{}).finally(()=>Zh.delete(t))}var at=t=>({response_type:"ephemeral",text:t}),TA="*/bostrat* \u2014 `start <name> [repo]` \xB7 `archive <track>` \xB7 `handoff <track> <agent>` \xB7 `stop <track>` \xB7 `list`\n_Reply in a Track's thread to steer its lead; question prompts and ship offers appear there as buttons._",_A=["claude","cursor","codex"];function hl(t,e){let n=(e||"").toLowerCase(),r=t.filter(s=>!s.archivedAt),o=r.filter(s=>s.name.toLowerCase().startsWith(n));return o.sort((s,a)=>(a.lastResponseAt??a.createdAt??0)-(s.lastResponseAt??s.createdAt??0)),{match:o[0]??null,live:r}}var wl=(t,e)=>at(`No live Track matches \u201C${K(e)}\u201D. Live: ${t.map(n=>`\`${K(n.name)}\``).join(" \xB7 ")||"none"}`);async function ew(t,e,{name:n,repo:r,responseUrl:o}){let s=t.deps;try{let a=(await s.listTracks()).filter(d=>!d.archivedAt);if(a.length>=e.maxConcurrent){await $t(o,{...at(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await $t(o,{...at("No designated channel configured \u2014 set `slack.channel` in integrations.json."),replace_original:!0});return}let i=await s.createTrack({name:n,...r?{repo:r}:{}}),c=i.workspace||{},l={trackId:i.id,channel:e.channel,name:i.name,branch:c.branch||null,repoLabel:c.repo||(c.base?ea.basename(c.base):"fresh workspace"),lead:"claude"},u=await fl(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:tw(l),unfurl_links:!1});l.ts=u.ts,(await bl())[i.id]=l,await ye(),await $t(o,{...at(`\u2713 *${K(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await $t(o,{...at(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function EA(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await kl(n.id);s&&(s.archivedAt=Date.now(),await ye(),await yl(e,s).catch(()=>{})),await $t(r,{...at(`\u2713 *${K(n.name)}* archived.`),replace_original:!0})}catch(s){await $t(r,{...at(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function IA(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await $t(o,{...at(`\u2713 Handing *${K(n.name)}* to ${K(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await $t(o,{...at(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function RA(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.interruptLead(n.id);let s=(o.listSubtaskJobs?.(n.id)??[]).filter(a=>!a.finishedAt);for(let a of s)try{o.cancelSubtask(a.id)}catch{}await $t(r,{...at(`\u2713 Stopped *${K(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await $t(r,{...at(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function $A(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:ho(`${r.name} (${r.branch})`,75)},value:r.path})),{text:{type:"plain_text",text:"fresh workspace"},value:"__fresh__"}];return[{type:"section",block_id:JSON.stringify({name:t}),text:{type:"mrkdwn",text:`Repo for *${K(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function OA(t){let e=await t.listTracks(),n=e.filter(s=>!s.archivedAt);if(n.sort((s,a)=>(a.lastResponseAt??a.createdAt??0)-(s.lastResponseAt??s.createdAt??0)),!n.length)return"No live Tracks.";let r=n.map(s=>{let a=s.workspace||{},i=a.repo||(a.base?ea.basename(a.base):"fresh"),c=[...s.boxes||[]].reverse().find(u=>u.role==="lead")?.agent||"?",l=s.prompted?"\u26A0 needs an answer":s.working?"working":"idle";return`\u2022 *${K(s.name)}* \u2014 ${K(i)}${a.branch?` \`${K(a.branch)}\``:""} \xB7 lead ${K(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
244
271
|
`)+(o?`
|
|
245
|
-
_${o} archived_`:"")}async function
|
|
246
|
-
\u2713 *${
|
|
247
|
-
\u2713 _answered_`}}]})}async function
|
|
248
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
249
|
-
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function tv(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function ev(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ee(e.cwd,e.sessionId):e?.cwd?Gs(e.cwd):null}async function Om(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await R(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await nv(r,o,t,i);break;default:break}return a}async function nv(t,e,n,r){let o=[],s=ev(n,e);if(s)try{o=QS(await ZS.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=tv(await B(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let d of c){let f=d.model||"unknown",w=u.get(f)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(w.tokens))w.tokens[m]+=d.tokens[m];w.messageIds.push(d.messageId),u.set(f,w)}for(let[d,f]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:d,usd:Kt(d,f.tokens),tokens:f.tokens,messageId:f.messageIds[f.messageIds.length-1],messageIds:f.messageIds,apiCalls:f.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import sv from"node:fs";import Ic from"node:fs/promises";import av from"node:http";import Qn from"node:path";import bs from"node:os";import{createRequire as iv}from"node:module";import Rc from"ws";import rv from"node:path";import ov from"node:os";var Z$=process.env.BOSTRAT_STATE_DIR||rv.join(process.env.HOME||ov.tmpdir(),".bostrat");function Cm(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function Mm(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var ys=(()=>{try{return iv(import.meta.url)("../package.json").version||null}catch{return null}})(),cv=process.env.BOSTRAT_STATE_DIR||Qn.join(process.env.HOME||bs.tmpdir(),".bostrat");function lv(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function uv({home:t=process.env.HOME||bs.homedir(),env:e=process.env}={}){let n=r=>{try{return sv.statSync(r),!0}catch{return!1}};return{claude:n(Qn.join(t,".claude",".credentials.json")),codex:n(Qn.join(e.CODEX_HOME||Qn.join(t,".codex"),"auth.json"))}}var St=1,Pm=1e3,dv=3e4,Nm=100,fv=2e3,pv=25e3;function Bm({url:t,token:e,nodeId:n=bs.hostname(),name:r=bs.hostname(),listTracks:o,selfUrl:s=`http://127.0.0.1:${process.env.PORT||3362}`,log:a=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=Mm(t);let i=s.replace(/^http/,"ws"),c=new Map,l=new Map,u=Qn.join(cv,"plane-link","cursor.json"),d={},f=new Map,w=null,m=!1,g=!1,x=Pm,A=null,S=null,T=null,E=!1;async function J(){try{d=JSON.parse(await Ic.readFile(u,"utf8"))}catch{d={}}}function M(){E=!0,!S&&(S=setTimeout(async()=>{if(S=null,!!E){E=!1;try{await Ic.mkdir(Qn.dirname(u),{recursive:!0}),await Ic.writeFile(u,JSON.stringify(d))}catch(N){a.error("[plane-link] cursor flush failed:",N?.message||N)}}},fv),S.unref?.())}function I(N){return!w||w.readyState!==Rc.OPEN?!1:(w.send(JSON.stringify(N)),!0)}function h(N){for(let{trackId:W,event:_}of N)f.set(_.id,{trackId:W,seq:_.seq});return I({v:St,ch:"events",type:"append",events:N})}async function p(){let N;try{N=await o()}catch(W){a.error("[plane-link] backfill listTracks failed:",W?.message||W);return}I({v:St,ch:"ctl",type:"tracks",ids:N.map(W=>W.id).filter(W=>typeof W=="string")});for(let W of N){if(!m)return;let _;try{_=await B(W)}catch{continue}let q=d[W.id]??0,X=_.filter(lt=>lt.seq>q&&typeof lt.id=="string");for(let lt=0;lt<X.length;lt+=Nm){let nt=X.slice(lt,lt+Nm).map(wt=>({trackId:W.id,event:wt}));if(!h(nt))return}}}function b(N){!m||!N?.trackId||typeof N.id!="string"||h([{trackId:N.trackId,event:N}])}let v=new Map;async function ot(N){let{reqId:W,method:_,path:q,headers:X={},body:lt}=N,nt=Object.fromEntries(Object.entries(X).filter(([,Pt])=>Pt!=null)),wt=new AbortController;v.set(W,wt);try{if(N.host){await $(N,nt,wt);return}let Pt=await fetch(s+q,{method:_,headers:nt,body:lt?Buffer.from(lt,"base64"):void 0,signal:wt.signal});if(I({v:St,ch:"control",type:"http_head",reqId:W,status:Pt.status,headers:{"content-type":Pt.headers.get("content-type")||"application/octet-stream"}}),Pt.body){for await(let De of Pt.body)if(!I({v:St,ch:"control",type:"http_data",reqId:W,data:Buffer.from(De).toString("base64")}))break}I({v:St,ch:"control",type:"http_end",reqId:W,error:null})}finally{v.delete(W)}}function $(N,W,_){let{reqId:q,method:X,path:lt,body:nt}=N,wt=new URL(s);return new Promise(Pt=>{let De=av.request({host:wt.hostname,port:wt.port,method:X,path:lt,headers:{...W,host:N.host},signal:_.signal},jt=>{let{connection:y,"keep-alive":j,"transfer-encoding":wn,upgrade:Zr,...je}=jt.headers;I({v:St,ch:"control",type:"http_head",reqId:q,status:jt.statusCode||502,headers:je}),jt.on("data",Os=>{I({v:St,ch:"control",type:"http_data",reqId:q,data:Buffer.from(Os).toString("base64")})||jt.destroy()}),jt.on("end",()=>{I({v:St,ch:"control",type:"http_end",reqId:q,error:null}),Pt()}),jt.on("error",()=>{I({v:St,ch:"control",type:"http_end",reqId:q,error:"view upstream error"}),Pt()})});De.on("error",jt=>{I({v:St,ch:"control",type:"http_end",reqId:q,error:jt?.message||"view request failed"}),Pt()}),nt&&De.write(Buffer.from(nt,"base64")),De.end()})}function z(){if(g)return;w=new Rc(t,{headers:{authorization:`Bearer ${e}`}}),w.on("open",()=>{I({v:St,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:ys,providers:uv()})}),w.on("message",W=>{let _;try{_=JSON.parse(W)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){m=!0,x=Pm,a.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&ys&&lv(ys,_.minNodeVersion)&&a.error(`[plane-link] this node is bostrat-node ${ys}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),T&&clearInterval(T),T=setInterval(()=>I({v:St,ch:"ctl",type:"ping"}),pv),T.unref?.(),p().catch(q=>a.error("[plane-link] backfill failed:",q?.message||q));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let q of _.ids){let X=f.get(q);X&&(f.delete(q),(d[X.trackId]??0)<X.seq&&(d[X.trackId]=X.seq))}M();return}if(_.ch==="control"&&_.type==="http_open"){ot(_).catch(q=>I({v:St,ch:"control",type:"http_end",reqId:_.reqId,error:q?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){v.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let q=new Rc(i+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);c.set(_.streamId,q),l.set(_.streamId,[]),q.on("open",()=>{let lt=l.get(_.streamId)||[];l.delete(_.streamId);for(let{buf:nt,bin:wt}of lt)q.send(nt,{binary:wt})}),q.on("message",(lt,nt)=>{let wt=nt?lt:Buffer.from(String(lt));I({v:St,ch:"attach",type:"data",streamId:_.streamId,data:wt.toString("base64"),...nt?{bin:!0}:{}})});let X=()=>{l.delete(_.streamId),c.delete(_.streamId)&&I({v:St,ch:"attach",type:"close",streamId:_.streamId})};q.on("close",X),q.on("error",X);return}if(_.ch==="attach"&&_.type==="data"){let q=c.get(_.streamId);if(!q)return;let X=Buffer.from(_.data,"base64"),lt=_.bin===!0,nt=l.get(_.streamId);nt?nt.push({buf:X,bin:lt}):q.send(X,{binary:lt});return}if(_.ch==="attach"&&_.type==="close"){let q=c.get(_.streamId);c.delete(_.streamId),l.delete(_.streamId),q?.close();return}});let N=()=>{m&&a.log("[plane-link] disconnected from plane"),m=!1,T&&(clearInterval(T),T=null),f.clear();for(let W of c.values())W.close();c.clear(),l.clear();for(let W of v.values())W.abort();v.clear(),!(g||A)&&(A=setTimeout(()=>{A=null,z()},x),A.unref?.(),x=Math.min(x*2,dv))};w.on("close",N),w.on("error",N)}return Nt.on("event",b),J().then(z),{stop:()=>{g=!0,Nt.off("event",b),A&&clearTimeout(A),S&&clearTimeout(S),T&&clearInterval(T),w?.close()},status:()=>({connected:m,nodeId:n,cursor:{...d},inflight:f.size})}}function mv(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var Dm=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),jm=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},$c=t=>Math.round(t*1e4)/1e4;async function Lm(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=Dm(),u=0,d=[];for(let f of t||[]){let w=await B(f),m=us(w,{boxLabels:mv(f)});if(m.turns===0){f.archivedAt?u++:d.push(f.name||f.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let g of m.unpricedModels||[])c.add(g);jm(l,m.totalTokens);for(let[g,x]of Object.entries(m.byModel))n[g]||={usd:0,events:0,tokens:Dm()},n[g].usd=$c(n[g].usd+x.usd),n[g].events+=x.events,jm(n[g].tokens,x.tokens);for(let[g,x]of Object.entries(m.byProvider))r[g]||={usd:0,events:0},r[g].usd=$c(r[g].usd+x.usd),r[g].events+=x.events;e.push({id:f.id,name:f.name,usd:m.totalUsd,rollup:m})}return e.sort((f,w)=>w.usd-f.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:$c(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+d.length,tracksSilentArchived:u,tracksSilentLive:d},tracks:e}}var qr=t=>`$${t.toFixed(4)}`,tr=(t,e)=>String(t).padEnd(e);function Um(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${qr(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${tr(a,26)} ${tr(qr(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${tr(a.name||a.id,30)} ${qr(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${tr(c.label,40)} ${tr(qr(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((d,f)=>Number(d[0])-Number(f[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${tr(qr(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
250
|
-
`)}import vv from"node:fs/promises";import Tv from"node:path";var hv=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],wv={research:[/\bresearch\b/,/\binvestigat/,/\bverif/,/\bcompar(e|ing|ison)/,/\bcompetit/,/\bdeep[ -]?dive\b/,/\banaly[sz]/,/\bassess/,/\bdiligence\b/,/\bmarket\b/,/\bstrateg/,/\broadmap\b/,/\bnorth star/,/\bwhat('| i)?s the (state|status)\b/,/\bstatus of\b/,/\bexplore\b/,/\bfeasib/,/\bevaluat/,/\bscope out\b/,/\btalk through\b/],"bug-fix":[/\bfix(e[sd]|ing)?\b/,/\bbugs?\b/,/\bbroken\b/,/\bregression\b/,/\bcrash/,/\bincident\b/,/\bdebug/,/\bdoesn'?t (work|render|load|start)/,/\bnot working\b/,/\btroubleshoot/,/\bflaky?\b/,/\brepro(duce|duction)?\b/,/\brace condition\b/,/\bleak\b/],"feature-build":[/\bimplement/,/\bbuild(ing)?\b/,/\badd(ing)?\b/,/\bcreate\b/,/\bwire (it |them )?(up|in)\b/,/\bship(ping)?\b/,/\bintegrat/,/\bsupport for\b/,/\bfeature\b/,/\bendpoint\b/,/\badapter\b/,/\bprototype\b/],design:[/\bdesign/,/\bui\b/,/\bux\b/,/\bstyl(e|es|ing)\b/,/\bcss\b/,/\blayout\b/,/\bmock-?up/,/\brender(ing|s)?\b/,/\bpolish/,/\btheme\b/,/\bfonts?\b/,/\bresponsive\b/,/\banimation/,/\bvisual/,/\bpopover\b/],orchestration:[/\bhand[ -]?(off|over)/,/\bhand (it|the lead|this) (over|off)\b/,/\bdelegat/,/\bsubtask/,/\bmerge[ -]?back\b/,/\borchestrat/,/\b(switch|change) (the )?model\b/,/\bmodel switch/],"docs-writing":[/\bdocs?\b/,/\bdocument/,/\bmemo\b/,/\breadme\b/,/\bwrite-?up\b/,/\bchangelog\b/,/\bclaude\.md\b/,/\bblog/,/\bproofread/,/\bcopy(edit|writ)/],"ops-infra":[/\bdeploy/,/\bsystemd\b/,/\bdaemon\b/,/\btmux\b/,/\bdns\b/,/\bwireguard\b/,/\brestart/,/\bnginx\b/,/\bprovision/,/\binstall/,/\bupgrade/,/\bcron\b/,/\bbackups?\b/,/\bcertificat/,/\bssh\b/,/\bsecrets?\b/]};function ks(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of hv){let o=0;for(let s of wv[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var gv=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),yv=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Fm(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))gv.has(r)&&(e+=o||0),yv.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Oc(t){return String(t||"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[-_/]+/g," ")}var xs=t=>t.payload?.ts??t.ts,bv=12e4,Ce=t=>Math.round(t*1e4)/1e4,kv=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function xv(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>xs(S)>=e,a=ks([Oc(n?.name),Oc(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let S of r)if(S.type==="subtask.delegated"&&S.boxId&&S.payload?.task){let T=ks(S.payload.task);T&&i.set(S.boxId,T)}let c=new Map,l=S=>{let T=S||"unattributed";return c.has(T)||c.set(T,{segments:[u(null)],lastTs:null,provider:null}),c.get(T)},u=S=>({textCat:S,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),d=[...r].sort((S,T)=>xs(S)-xs(T)),f=0,w=0,m=0,g={turns:0};for(let S of d){let T=l(S.boxId),E=T.segments[T.segments.length-1],J=xs(S);switch(T.lastTs!=null&&J>T.lastTs&&s(S)&&(E.activeMs+=Math.min(J-T.lastTs,bv)),T.lastTs=J,S.provider&&(T.provider=S.provider),S.type){case"turn.user":{let M=S.payload?.text;if(S.source==="hook"||!String(M||"").trim())break;let I=u(ks(M));T.segments.push(I),s(S)&&(I.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let M=S.payload?.tool;M&&(E.tools[M]=(E.tools[M]||0)+1),s(S)&&(E.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let M=S.payload||{},I={input:M.tokens?.input||0,output:M.tokens?.output||0,cacheRead:M.tokens?.cacheRead||0,cacheWrite:M.tokens?.cacheWrite??M.tokens?.cacheCreation??0},h=Number(M.usd)||0;!h&&M.tokens&&(h=Kt(M.model,I)),!h&&M.tokens&&!jo(M.model)&&(g.turns+=1),E.usd+=h,E.tokens.input+=I.input,E.tokens.output+=I.output,E.tokens.cacheRead+=I.cacheRead,E.tokens.cacheWrite+=I.cacheWrite;let p=M.model||"unknown";E.models[p]=(E.models[p]||0)+h,S.provider&&(E.provider=S.provider);break}case"handoff.done":s(S)&&(f+=1);break;case"subtask.merged":s(S)&&(w+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let x=new Map,A=!1;for(let[S,T]of c)for(let E of T.segments){if(!(E.usd>0||E.activeMs>0||E.turns>0||E.toolCalls>0))continue;let M=E.textCat?.category??i.get(S)?.category??Fm(E.tools)?.category??a?.category??"uncategorized";x.has(M)||x.set(M,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let I=x.get(M);I.usd+=E.usd,I.activeMs+=E.activeMs,I.turns+=E.turns,I.toolCalls+=E.toolCalls;for(let[p,b]of Object.entries(E.models))I.models[p]=(I.models[p]||0)+b;let h=E.provider||T.provider;h&&(I.providers[h]=(I.providers[h]||0)+E.usd),h==="cursor"&&E.usd===0&&(A=!0)}return{perCategory:x,handoffs:f,merged:w,failed:m,sawCursorActivity:A,unpricedTurns:g.turns}}function Wm(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let g of t||[]){let{track:x,events:A}=g;if(!A?.length){a+=1;continue}let{perCategory:S,handoffs:T,merged:E,failed:J,sawCursorActivity:M,unpricedTurns:I}=xv(g,{since:r});M&&(i=!0),c+=I;let h={trackId:x?.id,name:x?.name||x?.id,archived:!!x?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:T,subtasksMerged:E,subtasksFailed:J};for(let[p,b]of S){h.usd+=b.usd,h.activeMs+=b.activeMs,h.turns+=b.turns,h.categories[p]={usd:Ce(b.usd),activeMs:b.activeMs};for(let[$,z]of Object.entries(b.models))h.models[$]=Ce((h.models[$]||0)+z);for(let[$,z]of Object.entries(b.providers))h.providers[$]=Ce((h.providers[$]||0)+z);o.has(p)||o.set(p,kv());let v=o.get(p);v.usd+=b.usd,v.activeMs+=b.activeMs,v.turns+=b.turns,v.toolCalls+=b.toolCalls;for(let[$,z]of Object.entries(b.models))v.models[$]=(v.models[$]||0)+z;for(let[$,z]of Object.entries(b.providers))v.providers[$]=(v.providers[$]||0)+z;let ot=v.byTrack.get(x?.id)||{usd:0,activeMs:0};ot.usd+=b.usd,ot.activeMs+=b.activeMs,v.byTrack.set(x?.id,ot)}(h.usd>0||h.activeMs>0||h.turns>0)&&(h.usd=Ce(h.usd),h.dominantCategory=Sv(h.categories),s.push(h))}let l=[...o.values()].reduce((g,x)=>g+x.usd,0),u=[...o.values()].reduce((g,x)=>g+x.activeMs,0),d=l>0?"usd":"time",f=d==="usd"?l:u,w=new Map((t||[]).map(g=>[g.track?.id,g.track?.name||g.track?.id])),m=[...o.entries()].map(([g,x])=>({category:g,sharePct:f>0?Math.round((d==="usd"?x.usd:x.activeMs)/f*100):0,shareBasis:d,usd:Ce(x.usd),activeMs:x.activeMs,turns:x.turns,toolCalls:x.toolCalls,models:Hm(x.models),providers:Hm(x.providers),topWorkstreams:[...x.byTrack.entries()].sort((A,S)=>S[1].usd-A[1].usd||S[1].activeMs-A[1].activeMs).slice(0,2).map(([A,S])=>({trackId:A,name:w.get(A),usd:Ce(S.usd),activeMs:S.activeMs}))})).sort((g,x)=>x.usd-g.usd||x.activeMs-g.activeMs);return s.sort((g,x)=>x.usd-g.usd||x.activeMs-g.activeMs),{since:r,capturedAt:n,totalUsd:Ce(l),activeMs:u,turns:[...o.values()].reduce((g,x)=>g+x.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var Sv=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},Hm=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ce(n)]));var Cc={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function Av(t){let e=String(t||"7d");return e in Cc?{key:e,ms:Cc[e]}:{key:"7d",ms:Cc["7d"]}}function _v(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=To(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function Ev(t){if(!t?.cwd)return null;try{let n=(await vv.readFile(Tv.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function qm(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=Av(e),s=[];for(let a of t||[]){let i=await B(a);s.push({track:a,events:_v(a,i),brief:await Ev(a)})}return{window:r,...Wm(s,{windowMs:o,now:n})}}import er from"node:fs/promises";import Iv from"node:fs";import ft from"node:path";import Rv from"node:os";import{execFile as $v}from"node:child_process";import{promisify as Ov}from"node:util";var Cv=Ov($v),Gm=process.env.BOSTRAT_STATE_DIR||ft.join(process.env.HOME||Rv.tmpdir(),".bostrat"),Mc=!1;async function Gr(t,e){let n=await er.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await er.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await er.mkdir(ft.dirname(e),{recursive:!0}),await er.copyFile(t,e),!0)}var Mv=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Kr(t,e){let n=0,r=await er.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(Mv.has(o.name))continue;let s=ft.join(t,o.name);o.isDirectory()?n+=await Kr(s,ft.join(e,o.name)):o.isFile()&&(n+=await Gr(s,ft.join(e,o.name))?1:0)}return n}async function Km({stateDir:t=Gm,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Gm}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await er.mkdir(e,{recursive:!0});let o=0;o+=await Gr(ft.join(t,"tracks.json"),ft.join(e,"tracks.json"))?1:0,o+=await Gr(ft.join(t,"mocks.json"),ft.join(e,"mocks.json"))?1:0,o+=await Gr(ft.join(t,"runbooks.json"),ft.join(e,"runbooks.json"))?1:0,o+=await Kr(ft.join(t,"runbooks"),ft.join(e,"runbooks")),o+=await Kr(ft.join(t,"mock-archive"),ft.join(e,"mock-archive")),o+=await Gr(ft.join(t,"events.jsonl"),ft.join(e,"events.jsonl"))?1:0,o+=await Kr(ft.join(t,"events"),ft.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Kr(ft.join(l.cwd,".bostrat"),ft.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>Cv("git",["-C",e,...l]);Iv.existsSync(ft.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await da(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Mc))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Mc=!1}catch{Mc=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as Pv}from"node:child_process";import{promisify as Nv}from"node:util";import{randomUUID as Bv}from"node:crypto";import Dv from"node:fs";import jv from"node:os";import Lv from"node:path";var Pe=Nv(Pv),Ss=t=>new Promise(e=>setTimeout(e,t)),Ne=Ds,Pc=process.env.BOSTRAT_USAGE_PROBE_CWD||Lv.join(jv.homedir(),".bostrat","usage-probe"),Uv=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,Fv=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,Hv=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Vm=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Me=null,Vr=null,Jr=null;async function Wv(){try{return await Pe(O,C("has-session","-t",Ne)),!0}catch{return!1}}function qv(){Me&&clearTimeout(Me),Me=setTimeout(()=>Gv().catch(()=>{}),Uv),Me.unref&&Me.unref()}async function Gv(){Me&&(clearTimeout(Me),Me=null);try{await Pe(O,C("kill-session","-t",Ne))}catch{}}async function Kv(){let t=Date.now()+Hv;for(;;){let e=await Nc().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ss(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ss(500)}}async function Vv(){if(await Wv())return;if(!await pe())throw new Error("host tmux server not running \u2014 start globo-tmux.service");Dv.mkdirSync(Pc,{recursive:!0}),await fe(Pc);let t=Bv();await Pe(O,C("new-session","-d","-s",Ne,"-c",Pc,"-x","200","-y","60"));let e=`${Bs} --session-id ${t}`;await Pe(O,C("send-keys","-t",Ne,e,"Enter")),await Kv()}async function Nc(){let{stdout:t}=await Pe(O,C("capture-pane","-t",Ne,"-p"));return t}async function Jv(){await Vv(),await Pe(O,C("send-keys","-t",Ne,"-l","/usage")),await Pe(O,C("send-keys","-t",Ne,"Enter"));let t=Date.now()+Fv,e="";for(;await Ss(400),e=await Nc(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ss(900),e=await Nc(),await Pe(O,C("send-keys","-t",Ne,"Escape")).catch(()=>{}),qv(),e}function zv(t){let e=String(t||"").split(`
|
|
251
|
-
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function
|
|
252
|
-
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function
|
|
253
|
-
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:
|
|
272
|
+
_${o} archived_`:"")}async function CA(t,e){let n=await ln();if(!n)return at("Slack integration not configured.");let r=t.deps,o=(e.text||"").trim().split(/\s+/).filter(Boolean),s=(o[0]||"help").toLowerCase(),a=o.slice(1);switch(s){case"start":{if(!a.length)return at("Usage: `/bostrat start <name> [repo]`");let i=await r.listRepos(),c=a[a.length-1]?.toLowerCase(),l=a.length>1?i.find(d=>d.name.toLowerCase()===c):null,u=(l?a.slice(0,-1):a).join(" ");return l?(Tn(ew(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),at(`Starting *${K(u)}* on ${K(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:$A(u,i)}}case"archive":{if(!a.length)return at("Usage: `/bostrat archive <track>`");let{match:i,live:c}=hl(await r.listTracks(),a.join(" "));return i?(Tn(EA(t,n,{track:i,responseUrl:e.response_url})),at(`Archiving *${K(i.name)}*\u2026`)):wl(c,a.join(" "))}case"handoff":{let i=r.knownAgents??_A,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return at(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:d}=hl(await r.listTracks(),l);return u?(Tn(IA(t,n,{track:u,agent:c,responseUrl:e.response_url})),at(`Handing *${K(u.name)}* to ${K(c)}\u2026`)):wl(d,l)}case"stop":{if(!a.length)return at("Usage: `/bostrat stop <track>`");let{match:i,live:c}=hl(await r.listTracks(),a.join(" "));return i?(Tn(RA(t,n,{track:i,responseUrl:e.response_url})),at(`Stopping *${K(i.name)}*\u2026`)):wl(c,a.join(" "))}case"list":return at(await OA(r));default:return at(TA)}}async function MA(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await kl(o);try{if(await t.deps.answerPrompt(o,{promptId:s,answers:[a]}),i?.prompt?.promptId===s){let{ts:c,question:l}=i.prompt;delete i.prompt,await ye(),await yr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${K(l||"question")}~
|
|
273
|
+
\u2713 *${K(a)}*`}}]}).catch(()=>{})}}catch(c){await $t(n.response_url,at(`Couldn't answer: ${c?.message||c}`))}}async function PA(t,e,n,r){let{trackId:o,offerId:s,action:a}=r,i=t.deps;try{let c=await i.resolveTrack(o);if(!c)throw new Error("track not found");let l=await i.readEvents(c),u=l.find(f=>f.type==="pr.offered"&&f.payload?.offerId===s);if(!u)throw new Error("unknown offer");if(l.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===s))throw new Error("offer already answered");let d=a==="pr"?["commit","push","pr"]:a==="push"?["commit","push"]:null;d&&await i.sayToLead(c.id,Es(d,{branch:u.payload.branch,base:u.payload.base}),{kind:d.includes("pr")?"pr-go":"push-go"}),await i.appendEvent({id:c.id,cwd:c.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:u.boxId,payload:{ts:Date.now(),offerId:s,action:d?a:"dismiss",actions:d}})}catch(c){await $t(n.response_url,at(`Couldn't act on the offer: ${c?.message||c}`))}}async function NA(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await ln();if(!r)return;if(n.action_id==="bostrat-start-repo"){let s;try{({name:s}=JSON.parse(n.block_id))}catch{return}let a=n.selected_option?.value;await $t(e.response_url,{...at(`Starting *${K(s)}*\u2026`),replace_original:!0}),Tn(ew(t,r,{name:s,repo:a==="__fresh__"?null:a,responseUrl:e.response_url}));return}let o;try{o=JSON.parse(n.value)}catch{return}n.action_id==="bostrat-prompt"?Tn(MA(t,r,e,o)):n.action_id==="bostrat-ship"&&Tn(PA(t,r,e,o))}async function BA(t,e){if(!e||e.type!=="message"||e.bot_id||e.subtype||!e.thread_ts||e.thread_ts===e.ts||!e.text?.trim())return;let n=await vA(e.channel,e.thread_ts);if(!n)return;let r=await ln();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await _n(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function DA(t){return async e=>{if(e.type==="slash_commands")return CA(t,e.payload);if(e.type==="interactive"){await NA(t,e.payload);return}if(e.type==="events_api"){await BA(t,e.payload?.event);return}}}var jA=new Set(["turn.user","turn.agent","prompt.raised","prompt.answered","pr.offered","pr.offer.answered","pr.state.changed","handoff.done","view.state.changed","mock.state.changed"]);function LA(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${K(e.question||"The lead needs an answer")}`}}],r=(e.options||[]).slice(0,12);return r.length?n.push({type:"actions",elements:r.map(o=>({type:"button",action_id:"bostrat-prompt",text:{type:"plain_text",text:ho(String(o),75)},value:JSON.stringify({trackId:t,promptId:e.promptId,label:String(o)})}))}):n.push({type:"context",elements:[{type:"mrkdwn",text:"_reply in this thread to answer_"}]}),n}async function FA(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await yr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${K(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await _n(t,e,r.question||"The lead needs an answer",LA(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await ye()}async function UA(t,e,n){let r=n.payload||{};if(!e.prompt||r.promptId&&e.prompt.promptId!==r.promptId)return;let{ts:o,question:s}=e.prompt;delete e.prompt,await ye(),await yr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${K(s||"question")}~
|
|
274
|
+
\u2713 _answered_`}}]})}async function HA(t,e,n){let r=n.payload||{};if(!r.offerId||e.offers?.[r.offerId])return;let o=(i,c,l)=>({type:"button",action_id:"bostrat-ship",text:{type:"plain_text",text:c},value:JSON.stringify({trackId:n.trackId,offerId:r.offerId,action:i}),...l?{style:l}:{}}),s=r.toPr?[o("push",`Commit & push${r.toPr.number?` to #${r.toPr.number}`:""}`,"primary"),o("dismiss","Not now")]:[o("pr","Open PR","primary"),o("push","Just push"),o("dismiss","Not now")],a=await _n(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${K(ho(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await ye()}async function WA(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await ye();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await yr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function qA(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??GA)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await zh(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${K(o.name)}*${a}${o.group?` (group \`${K(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${K(a.name)}*`).join(", ");await _n(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${K(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function GA(t,e){let n=await _e(t);if(!n)return null;let r=rn(n,e);if(!r)return null;let o=await mo.readFile(jt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function KA(t,e){if(!jA.has(e.type))return;let n=await kl(e.trackId);if(!n)return;let r=await ln();if(!r)return;let o=e.payload||{};switch(e.type){case"turn.user":if(n.kickoff||e.actor!=="user"||!o.text)return;n.kickoff=o.text,await ye(),await yl(r,n);return;case"turn.agent":{if(!o.text)return;let s=AA(o.text);if(!s)return;await _n(r,n,s,[{type:"section",text:{type:"mrkdwn",text:K(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${ss(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return FA(r,n,e);case"prompt.answered":return UA(r,n,e);case"pr.offered":return HA(r,n,e);case"pr.offer.answered":return WA(r,n,e);case"pr.state.changed":{if(o.change!=="opened"&&o.change!=="merged")return;let s=o.change==="opened"?"PR opened":"PR merged";await _n(r,n,`${s}${o.url?`: ${o.url}`:o.number?` (#${o.number})`:""}`);return}case"handoff.done":if(!o.toAgent||o.toAgent===n.lead)return;n.lead=o.toAgent,await ye(),await yl(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await _n(r,n,`Live preview ${o.name}: ${cs(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${K(o.name)}*: ${cs(o.slug)}
|
|
275
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return qA(r,n,e,t.deps)}}function nw(t){let e={deps:t};Dt.on("event",s=>{KA(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await ln()||(r&&clearInterval(r),r=null,n=Xh({getToken:async()=>(await ln())?.appToken,onEnvelope:DA(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import VA from"node:fs/promises";function JA(t){let e=new Map;for(let n of String(t||"").split(`
|
|
276
|
+
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function zA(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function XA(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ie(e.cwd,e.sessionId):e?.cwd?Oa(e.cwd):null}async function rw(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await C(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await YA(r,o,t,i);break;default:break}return a}async function YA(t,e,n,r){let o=[],s=XA(n,e);if(s)try{o=JA(await VA.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=zA(await F(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let d of c){let f=d.model||"unknown",p=u.get(f)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(p.tokens))p.tokens[h]+=d.tokens[h];p.messageIds.push(d.messageId),u.set(f,p)}for(let[d,f]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:d,usd:Jt(d,f.tokens),tokens:f.tokens,messageId:f.messageIds[f.messageIds.length-1],messageIds:f.messageIds,apiCalls:f.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import tT from"node:fs";import xl from"node:fs/promises";import eT from"node:http";import br from"node:path";import ra from"node:os";import{createRequire as nT}from"node:module";import Sl from"ws";import ZA from"node:path";import QA from"node:os";var _M=process.env.BOSTRAT_STATE_DIR||ZA.join(process.env.HOME||QA.tmpdir(),".bostrat");function ow(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function sw(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var na=(()=>{try{return nT(import.meta.url)("../package.json").version||null}catch{return null}})(),rT=process.env.BOSTRAT_STATE_DIR||br.join(process.env.HOME||ra.tmpdir(),".bostrat");function oT(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function sT({home:t=process.env.HOME||ra.homedir(),env:e=process.env}={}){let n=r=>{try{return tT.statSync(r),!0}catch{return!1}};return{claude:n(br.join(t,".claude",".credentials.json")),codex:n(br.join(e.CODEX_HOME||br.join(t,".codex"),"auth.json"))}}var vt=1,aw=1e3,aT=3e4,iw=100,iT=2e3,cT=25e3;function cw({url:t,token:e,nodeId:n=ra.hostname(),name:r=ra.hostname(),listTracks:o,selfUrl:s=`http://127.0.0.1:${process.env.PORT||3362}`,log:a=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=sw(t);let i=s.replace(/^http/,"ws"),c=new Map,l=new Map,u=br.join(rT,"plane-link","cursor.json"),d={},f=new Map,p=null,h=!1,m=!1,b=aw,k=null,v=null,E=null,M=!1;async function lt(){try{d=JSON.parse(await xl.readFile(u,"utf8"))}catch{d={}}}function I(){M=!0,!v&&(v=setTimeout(async()=>{if(v=null,!!M){M=!1;try{await xl.mkdir(br.dirname(u),{recursive:!0}),await xl.writeFile(u,JSON.stringify(d))}catch(R){a.error("[plane-link] cursor flush failed:",R?.message||R)}}},iT),v.unref?.())}function $(R){return!p||p.readyState!==Sl.OPEN?!1:(p.send(JSON.stringify(R)),!0)}function _(R){for(let{trackId:D,event:T}of R)f.set(T.id,{trackId:D,seq:T.seq});return $({v:vt,ch:"events",type:"append",events:R})}async function U(){let R;try{R=await o()}catch(D){a.error("[plane-link] backfill listTracks failed:",D?.message||D);return}$({v:vt,ch:"ctl",type:"tracks",ids:R.map(D=>D.id).filter(D=>typeof D=="string")});for(let D of R){if(!h)return;let T;try{T=await F(D)}catch{continue}let O=d[D.id]??0,W=T.filter(tt=>tt.seq>O&&typeof tt.id=="string");for(let tt=0;tt<W.length;tt+=iw){let it=W.slice(tt,tt+iw).map(yt=>({trackId:D.id,event:yt}));if(!_(it))return}}}function V(R){!h||!R?.trackId||typeof R.id!="string"||_([{trackId:R.trackId,event:R}])}let g=new Map;async function w(R){let{reqId:D,method:T,path:O,headers:W={},body:tt}=R,it=Object.fromEntries(Object.entries(W).filter(([,Bt])=>Bt!=null)),yt=new AbortController;g.set(D,yt);try{if(R.host){await y(R,it,yt);return}let Bt=await fetch(s+O,{method:T,headers:it,body:tt?Buffer.from(tt,"base64"):void 0,signal:yt.signal});if($({v:vt,ch:"control",type:"http_head",reqId:D,status:Bt.status,headers:{"content-type":Bt.headers.get("content-type")||"application/octet-stream"}}),Bt.body){for await(let Ye of Bt.body)if(!$({v:vt,ch:"control",type:"http_data",reqId:D,data:Buffer.from(Ye).toString("base64")}))break}$({v:vt,ch:"control",type:"http_end",reqId:D,error:null})}finally{g.delete(D)}}function y(R,D,T){let{reqId:O,method:W,path:tt,body:it}=R,yt=new URL(s);return new Promise(Bt=>{let Ye=eT.request({host:yt.hostname,port:yt.port,method:W,path:tt,headers:{...D,host:R.host},signal:T.signal},Ut=>{let{connection:x,"keep-alive":q,"transfer-encoding":En,upgrade:Io,...Ze}=Ut.headers;$({v:vt,ch:"control",type:"http_head",reqId:O,status:Ut.statusCode||502,headers:Ze}),Ut.on("data",ga=>{$({v:vt,ch:"control",type:"http_data",reqId:O,data:Buffer.from(ga).toString("base64")})||Ut.destroy()}),Ut.on("end",()=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:null}),Bt()}),Ut.on("error",()=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:"view upstream error"}),Bt()})});Ye.on("error",Ut=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:Ut?.message||"view request failed"}),Bt()}),it&&Ye.write(Buffer.from(it,"base64")),Ye.end()})}function A(){if(m)return;p=new Sl(t,{headers:{authorization:`Bearer ${e}`}}),p.on("open",()=>{$({v:vt,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:na,providers:sT()})}),p.on("message",D=>{let T;try{T=JSON.parse(D)}catch{return}if(T.ch==="ctl"&&T.type==="hello_ok"){h=!0,b=aw,a.log(`[plane-link] connected to plane as ${n}`),T.minNodeVersion&&na&&oT(na,T.minNodeVersion)&&a.error(`[plane-link] this node is bostrat-node ${na}; the plane asks for \u2265${T.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),E&&clearInterval(E),E=setInterval(()=>$({v:vt,ch:"ctl",type:"ping"}),cT),E.unref?.(),U().catch(O=>a.error("[plane-link] backfill failed:",O?.message||O));return}if(T.ch==="events"&&T.type==="ack"&&Array.isArray(T.ids)){for(let O of T.ids){let W=f.get(O);W&&(f.delete(O),(d[W.trackId]??0)<W.seq&&(d[W.trackId]=W.seq))}I();return}if(T.ch==="control"&&T.type==="http_open"){w(T).catch(O=>$({v:vt,ch:"control",type:"http_end",reqId:T.reqId,error:O?.message||"tunnel error"}));return}if(T.ch==="control"&&T.type==="http_cancel"){g.get(T.reqId)?.abort();return}if(T.ch==="attach"&&T.type==="open"){let O=new Sl(i+T.path,Array.isArray(T.protocols)?T.protocols:[],T.host?{headers:{host:T.host}}:void 0);c.set(T.streamId,O),l.set(T.streamId,[]),O.on("open",()=>{let tt=l.get(T.streamId)||[];l.delete(T.streamId);for(let{buf:it,bin:yt}of tt)O.send(it,{binary:yt})}),O.on("message",(tt,it)=>{let yt=it?tt:Buffer.from(String(tt));$({v:vt,ch:"attach",type:"data",streamId:T.streamId,data:yt.toString("base64"),...it?{bin:!0}:{}})});let W=()=>{l.delete(T.streamId),c.delete(T.streamId)&&$({v:vt,ch:"attach",type:"close",streamId:T.streamId})};O.on("close",W),O.on("error",W);return}if(T.ch==="attach"&&T.type==="data"){let O=c.get(T.streamId);if(!O)return;let W=Buffer.from(T.data,"base64"),tt=T.bin===!0,it=l.get(T.streamId);it?it.push({buf:W,bin:tt}):O.send(W,{binary:tt});return}if(T.ch==="attach"&&T.type==="close"){let O=c.get(T.streamId);c.delete(T.streamId),l.delete(T.streamId),O?.close();return}});let R=()=>{h&&a.log("[plane-link] disconnected from plane"),h=!1,E&&(clearInterval(E),E=null),f.clear();for(let D of c.values())D.close();c.clear(),l.clear();for(let D of g.values())D.abort();g.clear(),!(m||k)&&(k=setTimeout(()=>{k=null,A()},b),k.unref?.(),b=Math.min(b*2,aT))};p.on("close",R),p.on("error",R)}return Dt.on("event",V),lt().then(A),{stop:()=>{m=!0,Dt.off("event",V),k&&clearTimeout(k),v&&clearTimeout(v),E&&clearInterval(E),p?.close()},status:()=>({connected:h,nodeId:n,cursor:{...d},inflight:f.size})}}function lT(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var lw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),uw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},vl=t=>Math.round(t*1e4)/1e4;async function dw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=lw(),u=0,d=[];for(let f of t||[]){let p=await F(f),h=Te(p,{boxLabels:lT(f)});if(h.turns===0){f.archivedAt?u++:d.push(f.name||f.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let m of h.unpricedModels||[])c.add(m);uw(l,h.totalTokens);for(let[m,b]of Object.entries(h.byModel))n[m]||={usd:0,events:0,tokens:lw()},n[m].usd=vl(n[m].usd+b.usd),n[m].events+=b.events,uw(n[m].tokens,b.tokens);for(let[m,b]of Object.entries(h.byProvider))r[m]||={usd:0,events:0},r[m].usd=vl(r[m].usd+b.usd),r[m].events+=b.events;e.push({id:f.id,name:f.name,usd:h.totalUsd,rollup:h})}return e.sort((f,p)=>p.usd-f.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:vl(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+d.length,tracksSilentArchived:u,tracksSilentLive:d},tracks:e}}var wo=t=>`$${t.toFixed(4)}`,kr=(t,e)=>String(t).padEnd(e);function fw(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${wo(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${kr(a,26)} ${kr(wo(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${kr(a.name||a.id,30)} ${wo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${kr(c.label,40)} ${kr(wo(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((d,f)=>Number(d[0])-Number(f[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${kr(wo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
277
|
+
`)}import yT from"node:fs/promises";import bT from"node:path";var uT=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],dT={research:[/\bresearch\b/,/\binvestigat/,/\bverif/,/\bcompar(e|ing|ison)/,/\bcompetit/,/\bdeep[ -]?dive\b/,/\banaly[sz]/,/\bassess/,/\bdiligence\b/,/\bmarket\b/,/\bstrateg/,/\broadmap\b/,/\bnorth star/,/\bwhat('| i)?s the (state|status)\b/,/\bstatus of\b/,/\bexplore\b/,/\bfeasib/,/\bevaluat/,/\bscope out\b/,/\btalk through\b/],"bug-fix":[/\bfix(e[sd]|ing)?\b/,/\bbugs?\b/,/\bbroken\b/,/\bregression\b/,/\bcrash/,/\bincident\b/,/\bdebug/,/\bdoesn'?t (work|render|load|start)/,/\bnot working\b/,/\btroubleshoot/,/\bflaky?\b/,/\brepro(duce|duction)?\b/,/\brace condition\b/,/\bleak\b/],"feature-build":[/\bimplement/,/\bbuild(ing)?\b/,/\badd(ing)?\b/,/\bcreate\b/,/\bwire (it |them )?(up|in)\b/,/\bship(ping)?\b/,/\bintegrat/,/\bsupport for\b/,/\bfeature\b/,/\bendpoint\b/,/\badapter\b/,/\bprototype\b/],design:[/\bdesign/,/\bui\b/,/\bux\b/,/\bstyl(e|es|ing)\b/,/\bcss\b/,/\blayout\b/,/\bmock-?up/,/\brender(ing|s)?\b/,/\bpolish/,/\btheme\b/,/\bfonts?\b/,/\bresponsive\b/,/\banimation/,/\bvisual/,/\bpopover\b/],orchestration:[/\bhand[ -]?(off|over)/,/\bhand (it|the lead|this) (over|off)\b/,/\bdelegat/,/\bsubtask/,/\bmerge[ -]?back\b/,/\borchestrat/,/\b(switch|change) (the )?model\b/,/\bmodel switch/],"docs-writing":[/\bdocs?\b/,/\bdocument/,/\bmemo\b/,/\breadme\b/,/\bwrite-?up\b/,/\bchangelog\b/,/\bclaude\.md\b/,/\bblog/,/\bproofread/,/\bcopy(edit|writ)/],"ops-infra":[/\bdeploy/,/\bsystemd\b/,/\bdaemon\b/,/\btmux\b/,/\bdns\b/,/\bwireguard\b/,/\brestart/,/\bnginx\b/,/\bprovision/,/\binstall/,/\bupgrade/,/\bcron\b/,/\bbackups?\b/,/\bcertificat/,/\bssh\b/,/\bsecrets?\b/]};function oa(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of uT){let o=0;for(let s of dT[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var fT=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),pT=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function pw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))fT.has(r)&&(e+=o||0),pT.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Al(t){return String(t||"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[-_/]+/g," ")}var sa=t=>t.payload?.ts??t.ts,mT=12e4,Ke=t=>Math.round(t*1e4)/1e4,hT=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function wT(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>sa(v)>=e,a=oa([Al(n?.name),Al(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let v of r)if(v.type==="subtask.delegated"&&v.boxId&&v.payload?.task){let E=oa(v.payload.task);E&&i.set(v.boxId,E)}let c=new Map,l=v=>{let E=v||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},u=v=>({textCat:v,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),d=[...r].sort((v,E)=>sa(v)-sa(E)),f=0,p=0,h=0,m={turns:0};for(let v of d){let E=l(v.boxId),M=E.segments[E.segments.length-1],lt=sa(v);switch(E.lastTs!=null&<>E.lastTs&&s(v)&&(M.activeMs+=Math.min(lt-E.lastTs,mT)),E.lastTs=lt,v.provider&&(E.provider=v.provider),v.type){case"turn.user":{let I=v.payload?.text;if(v.source==="hook"||!String(I||"").trim())break;let $=u(oa(I));E.segments.push($),s(v)&&($.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let I=v.payload?.tool;I&&(M.tools[I]=(M.tools[I]||0)+1),s(v)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let I=v.payload||{},$={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},_=Number(I.usd)||0;!_&&I.tokens&&(_=Jt(I.model,$)),!_&&I.tokens&&!Go(I.model)&&(m.turns+=1),M.usd+=_,M.tokens.input+=$.input,M.tokens.output+=$.output,M.tokens.cacheRead+=$.cacheRead,M.tokens.cacheWrite+=$.cacheWrite;let U=I.model||"unknown";M.models[U]=(M.models[U]||0)+_,v.provider&&(M.provider=v.provider);break}case"handoff.done":s(v)&&(f+=1);break;case"subtask.merged":s(v)&&(p+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let b=new Map,k=!1;for(let[v,E]of c)for(let M of E.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let I=M.textCat?.category??i.get(v)?.category??pw(M.tools)?.category??a?.category??"uncategorized";b.has(I)||b.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let $=b.get(I);$.usd+=M.usd,$.activeMs+=M.activeMs,$.turns+=M.turns,$.toolCalls+=M.toolCalls;for(let[U,V]of Object.entries(M.models))$.models[U]=($.models[U]||0)+V;let _=M.provider||E.provider;_&&($.providers[_]=($.providers[_]||0)+M.usd),_==="cursor"&&M.usd===0&&(k=!0)}return{perCategory:b,handoffs:f,merged:p,failed:h,sawCursorActivity:k,unpricedTurns:m.turns}}function hw(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let m of t||[]){let{track:b,events:k}=m;if(!k?.length){a+=1;continue}let{perCategory:v,handoffs:E,merged:M,failed:lt,sawCursorActivity:I,unpricedTurns:$}=wT(m,{since:r});I&&(i=!0),c+=$;let _={trackId:b?.id,name:b?.name||b?.id,archived:!!b?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:M,subtasksFailed:lt};for(let[U,V]of v){_.usd+=V.usd,_.activeMs+=V.activeMs,_.turns+=V.turns,_.categories[U]={usd:Ke(V.usd),activeMs:V.activeMs};for(let[y,A]of Object.entries(V.models))_.models[y]=Ke((_.models[y]||0)+A);for(let[y,A]of Object.entries(V.providers))_.providers[y]=Ke((_.providers[y]||0)+A);o.has(U)||o.set(U,hT());let g=o.get(U);g.usd+=V.usd,g.activeMs+=V.activeMs,g.turns+=V.turns,g.toolCalls+=V.toolCalls;for(let[y,A]of Object.entries(V.models))g.models[y]=(g.models[y]||0)+A;for(let[y,A]of Object.entries(V.providers))g.providers[y]=(g.providers[y]||0)+A;let w=g.byTrack.get(b?.id)||{usd:0,activeMs:0};w.usd+=V.usd,w.activeMs+=V.activeMs,g.byTrack.set(b?.id,w)}(_.usd>0||_.activeMs>0||_.turns>0)&&(_.usd=Ke(_.usd),_.dominantCategory=gT(_.categories),s.push(_))}let l=[...o.values()].reduce((m,b)=>m+b.usd,0),u=[...o.values()].reduce((m,b)=>m+b.activeMs,0),d=l>0?"usd":"time",f=d==="usd"?l:u,p=new Map((t||[]).map(m=>[m.track?.id,m.track?.name||m.track?.id])),h=[...o.entries()].map(([m,b])=>({category:m,sharePct:f>0?Math.round((d==="usd"?b.usd:b.activeMs)/f*100):0,shareBasis:d,usd:Ke(b.usd),activeMs:b.activeMs,turns:b.turns,toolCalls:b.toolCalls,models:mw(b.models),providers:mw(b.providers),topWorkstreams:[...b.byTrack.entries()].sort((k,v)=>v[1].usd-k[1].usd||v[1].activeMs-k[1].activeMs).slice(0,2).map(([k,v])=>({trackId:k,name:p.get(k),usd:Ke(v.usd),activeMs:v.activeMs}))})).sort((m,b)=>b.usd-m.usd||b.activeMs-m.activeMs);return s.sort((m,b)=>b.usd-m.usd||b.activeMs-m.activeMs),{since:r,capturedAt:n,totalUsd:Ke(l),activeMs:u,turns:[...o.values()].reduce((m,b)=>m+b.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var gT=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},mw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ke(n)]));var Tl={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function kT(t){let e=String(t||"7d");return e in Tl?{key:e,ms:Tl[e]}:{key:"7d",ms:Tl["7d"]}}function xT(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=_s(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function ST(t){if(!t?.cwd)return null;try{let n=(await yT.readFile(bT.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function ww(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=kT(e),s=[];for(let a of t||[]){let i=await F(a);s.push({track:a,events:xT(a,i),brief:await ST(a)})}return{window:r,...hw(s,{windowMs:o,now:n})}}import xr from"node:fs/promises";import vT from"node:fs";import mt from"node:path";import AT from"node:os";import{execFile as TT}from"node:child_process";import{promisify as _T}from"node:util";var ET=_T(TT),gw=process.env.BOSTRAT_STATE_DIR||mt.join(process.env.HOME||AT.tmpdir(),".bostrat"),_l=!1;async function go(t,e){let n=await xr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await xr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await xr.mkdir(mt.dirname(e),{recursive:!0}),await xr.copyFile(t,e),!0)}var IT=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function yo(t,e){let n=0,r=await xr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(IT.has(o.name))continue;let s=mt.join(t,o.name);o.isDirectory()?n+=await yo(s,mt.join(e,o.name)):o.isFile()&&(n+=await go(s,mt.join(e,o.name))?1:0)}return n}async function yw({stateDir:t=gw,dest:e=process.env.BOSTRAT_SYNC_DIR||`${gw}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await xr.mkdir(e,{recursive:!0});let o=0;o+=await go(mt.join(t,"tracks.json"),mt.join(e,"tracks.json"))?1:0,o+=await go(mt.join(t,"mocks.json"),mt.join(e,"mocks.json"))?1:0,o+=await go(mt.join(t,"runbooks.json"),mt.join(e,"runbooks.json"))?1:0,o+=await yo(mt.join(t,"runbooks"),mt.join(e,"runbooks")),o+=await yo(mt.join(t,"mock-archive"),mt.join(e,"mock-archive")),o+=await go(mt.join(t,"events.jsonl"),mt.join(e,"events.jsonl"))?1:0,o+=await yo(mt.join(t,"events"),mt.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await yo(mt.join(l.cwd,".bostrat"),mt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>ET("git",["-C",e,...l]);vT.existsSync(mt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Ni(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||_l))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,_l=!1}catch{_l=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as RT}from"node:child_process";import{promisify as $T}from"node:util";import{randomUUID as OT}from"node:crypto";import CT from"node:fs";import MT from"node:os";import PT from"node:path";var Je=$T(RT),aa=t=>new Promise(e=>setTimeout(e,t)),ze=va,El=process.env.BOSTRAT_USAGE_PROBE_CWD||PT.join(MT.homedir(),".bostrat","usage-probe"),NT=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,BT=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,DT=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,bw=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Ve=null,bo=null,ko=null;async function jT(){try{return await Je(P,N("has-session","-t",ze)),!0}catch{return!1}}function LT(){Ve&&clearTimeout(Ve),Ve=setTimeout(()=>FT().catch(()=>{}),NT),Ve.unref&&Ve.unref()}async function FT(){Ve&&(clearTimeout(Ve),Ve=null);try{await Je(P,N("kill-session","-t",ze))}catch{}}async function UT(){let t=Date.now()+DT;for(;;){let e=await Il().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await aa(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await aa(500)}}async function HT(){if(await jT())return;if(!await xe())throw new Error("host tmux server not running \u2014 start globo-tmux.service");CT.mkdirSync(El,{recursive:!0}),await ke(El);let t=OT();await Je(P,N("new-session","-d","-s",ze,"-c",El,"-x","200","-y","60"));let e=`${Sa} --session-id ${t}`;await Je(P,N("send-keys","-t",ze,e,"Enter")),await UT()}async function Il(){let{stdout:t}=await Je(P,N("capture-pane","-t",ze,"-p"));return t}async function WT(){await HT(),await Je(P,N("send-keys","-t",ze,"-l","/usage")),await Je(P,N("send-keys","-t",ze,"Enter"));let t=Date.now()+BT,e="";for(;await aa(400),e=await Il(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await aa(900),e=await Il(),await Je(P,N("send-keys","-t",ze,"Escape")).catch(()=>{}),LT(),e}function qT(t){let e=String(t||"").split(`
|
|
278
|
+
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function GT(t){let e=String(t||"").split(`
|
|
279
|
+
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function kw({fresh:t=!1}={}){return!t&&bo&&Date.now()-bo.at<bw?bo.data:ko||(ko=(async()=>{try{let e=await WT(),n=qT(e),r={...n,contributors:GT(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return bo={at:Date.now(),data:r},r}catch(e){let n={session:null,week:null,contributors:null,subscription:!1,error:e?.message||String(e),capturedAt:Date.now()};return bo={at:Date.now()-(bw-3e3),data:n},n}finally{ko=null}})(),ko)}var KT=262144;function VT(t){let e=Number(t);if(!Number.isFinite(e)||e<0)return null;let n=Math.floor(e/3600),r=Math.round(e%3600/60);return n>=48?`in ${Math.round(n/24)}d`:n>0?`in ${n}h ${r}m`:`in ${Math.max(1,r)}m`}function xw(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:VT(t.resets_in_seconds)}:null}function JT(t){let e=String(t||"").split(`
|
|
280
|
+
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:xw(i.primary),week:xw(i.secondary),sawTokenCount:n};if(i.primary_used_percent!=null||i.secondary_used_percent!=null){let c=l=>Number.isFinite(Number(l))?{pct:Math.round(Number(l)),resets:null}:null;return{session:c(i.primary_used_percent),week:c(i.secondary_used_percent),sawTokenCount:n}}return{session:null,week:null,sawTokenCount:n}}return{session:null,week:null,sawTokenCount:n}}async function zT(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await hn(t?.cwd,t?.sessionId);if(!n)return{...e,session:null,week:null,subscription:!1,error:"no codex rollout found for this box yet",capturedAt:Date.now()};let r=await Tt(n.file,KT),o=JT(r);return{...e,session:o.session,week:o.week,subscription:!!(o.session||o.week),capturedAt:n.mtimeMs||Date.now()}}catch(n){return{...e,session:null,week:null,subscription:!1,error:n?.message||String(n),capturedAt:Date.now()}}}async function Sw(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?zT(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await kw({fresh:e})}}function vw(t){let e=t.split(".");if(e.length!==4)return null;let n=0;for(let r of e){let o=Number(r);if(!Number.isInteger(o)||o<0||o>255)return null;n=n*256+o}return n>>>0}function XT(t){let[e,n]=t.split("/"),r=vw(e);if(r==null)return null;let o=n==null?32:Number(n);if(!Number.isInteger(o)||o<0||o>32)return null;let s=o===0?0:4294967295<<32-o>>>0;return a=>(a&s)===(r&s)}function YT(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(XT).filter(Boolean)}function ZT(t){let e=t.headers["fly-client-ip"];if(typeof e=="string"&&e.trim())return e.trim();let n=t.headers["x-forwarded-for"];return typeof n=="string"&&n.trim()?n.split(",")[0].trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Aw(t){let e=YT(t),n=e.length===0;return{open:n,clientIp:ZT,allowed(r){if(n)return!0;let o=vw((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import QT from"node:http";import t_ from"node:net";function Tw(t){if(!t)return null;let e=String(t).replace(/:\d+$/,"").toLowerCase();if(/^[\d.]+$/.test(e)||e.includes(":"))return null;let n=e.split(".");if(n.length<3)return null;let r=n[0];return/^[a-z0-9-]+$/.test(r)?r:null}function _w(t){let e=Tw(t.headers.host);return e?{slug:e,view:ni(e)}:null}var e_=t=>`<!doctype html><meta charset="utf-8">
|
|
254
281
|
<meta http-equiv="refresh" content="3">
|
|
255
282
|
<title>${t.name} \u2014 starting</title>
|
|
256
283
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
257
284
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
258
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
285
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function Ew(t,e,n){let r=_w(t);if(!r)return n();let{view:o,slug:s}=r;if(!o)return e.statusCode=404,e.setHeader("content-type","text/plain"),e.end(`bostrat: no view named "${s}"`);if(o.state==="starting")return e.statusCode=503,e.setHeader("content-type","text/html"),e.end(e_(o));if(o.state!=="live"&&o.state!=="unhealthy")return e.statusCode=404,e.setHeader("content-type","text/plain"),e.end(`bostrat: view "${s}" is ${o.state} \u2014 restart it from the Views tab`);let a={...t.headers,host:`127.0.0.1:${o.port}`,"x-forwarded-host":t.headers.host,"x-forwarded-proto":t.headers["x-forwarded-proto"]||"http"},i=QT.request({host:"127.0.0.1",port:o.port,method:t.method,path:t.url,headers:a},c=>{e.writeHead(c.statusCode||502,c.headers),c.pipe(e)});i.on("error",()=>{e.headersSent?e.destroy():(e.statusCode=502,e.setHeader("content-type","text/plain"),e.end(`bostrat: view "${s}" did not answer on :${o.port}`))}),t.pipe(i),t.on("aborted",()=>i.destroy())}function Iw(t,e,n){let r=_w(t);if(!r)return!1;let{view:o,slug:s}=r;if(!o||o.state!=="live"&&o.state!=="unhealthy")return e.write(`HTTP/1.1 404 Not Found\r
|
|
259
286
|
\r
|
|
260
287
|
bostrat: no live view "${s}"\r
|
|
261
|
-
`),e.destroy(),!0;let a=
|
|
288
|
+
`),e.destroy(),!0;let a=t_.connect(o.port,"127.0.0.1",()=>{let c=[`${t.method} ${t.url} HTTP/1.1`];for(let[l,u]of Object.entries(t.headers))l!=="host"&&c.push(`${l}: ${Array.isArray(u)?u.join(", "):u}`);c.push(`host: 127.0.0.1:${o.port}`),a.write(c.join(`\r
|
|
262
289
|
`)+`\r
|
|
263
290
|
\r
|
|
264
|
-
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function rh(t,e){let n=typeof e=="string"?{host:e}:e||{},r=n["x-bostrat-view-base"];return typeof r=="string"&&r?`${n["x-forwarded-proto"]==="https"?"https":"http"}://${t.slug}.${r.replace(/:\d+$/,"")}/`:`http://${t.slug}.${iT(n.host)}/`}function iT(t){if(process.env.BOSTRAT_PUBLIC_HOST)return String(process.env.BOSTRAT_PUBLIC_HOST).replace(/:\d+$/,"");let e=String(t||"").replace(/:\d+$/,"").toLowerCase();if(!e||!e.includes(".")||/^[\d.]+$/.test(e))return"bostrat.globopi";let n=e.split(".");return Qm(e)&&gi(n[0])?n.slice(1).join("."):e}import{execFile as cT}from"node:child_process";import{promisify as lT}from"node:util";var Uc=lT(cT),ah=3e4,uT=30*6e4,oh=ah,dT=5*6e4,fT=10080*6e4,pT=60*6e4,mT=3,ih="number,url,title,headRefName,headRefOid,state",Lc=60*6e4,sh=new Map;function hT(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Lc)}function wT(t,e){return t.armedAt&&e-t.armedAt<uT||hT(t,e)?oh:e-(t.firstSeen||0)>fT?pT:dT}async function gT(t,e){let{stdout:n}=await Uc("gh",["pr","view",String(e),"--repo",t,"--json",ih],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await Gt({slug:t})}});return JSON.parse(n||"null")}async function yT(t){let{stdout:e}=await Uc("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",ih],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await Gt({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}async function bT(){let t=new Map;for(let e of await dt().catch(()=>[]))t.set(e.id,e);return t}async function kT(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function xT(t,e){let n=await Ti(t);return n?Er(n,e):null}async function ST(t,e,n){let r=await Ti(t);if(!r)throw new Error(`no local checkout for ${t}`);return Ir(r,e,{method:n})}async function vT(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function vs(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var TT=10*6e4;async function AT(t,e,n){let r=await n.detail(t.repo,t.number).catch(()=>null);if(!r)return;if(r.checks.failed>0){let s=r.checks.failing.map(a=>a.name).join(", ");await vs(t,r,e,n,`check${r.checks.failed===1?"":"s"} failed: ${s}`);return}if(r.mergeable==="CONFLICTING"){await vs(t,r,e,n,`conflicts with ${r.baseRefName}`);return}if(!r.checks.total){let s=Math.max(t.autoMerge?.armedAt||0,Date.parse(r.updatedAt||"")||0);if((n.now??Date.now())-s<TT)return;await vs(t,r,e,n,r.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(r.checks.pending>0||r.mergeable==="UNKNOWN"||r.mergeStateStatus==="UNKNOWN"||r.isDraft)return;let o=await n.lookup(t.repo,t.number).catch(()=>null);if(o?.autoMerge){try{await n.merge(t.repo,t.number,o.autoMerge.method||"merge")}catch(s){if(/already merged|not open|is closed/i.test(String(s?.message||s)))return;await vs(t,r,e,n,String(s?.message||s).trim()||"merge failed");return}await ch(t,{...r,state:"MERGED"},e,n)}}async function Bc(t,e,n){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function _T(t,e){try{return await Uc("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Dc(t,e,n,r=()=>!0){return(await B(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var jc=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function ET(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await Io(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await $o(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait);await Td(t.repo,t.number,s).catch(()=>{}),!i&&!c&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Dc(a,"pushed",t.number,u=>u.payload?.sha===s)&&await Bc(a,"pushed",{...jc(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Lc)await Ro(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await Ro(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=t.ciFixAwait.since||0;a&&!await Dc(a,"ci-fixed",t.number,l=>(l.payload?.ts||0)>=c)&&await Bc(a,"ci-fixed",{...jc(t,e),passed:i.checks.passed}).catch(()=>{}),await Ro(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Lc)await Oo(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Oo(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){let c=t.reconcileAwait.since||0;a&&!await Dc(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Bc(a,"reconciled",{...jc(t,e),base:i.baseRefName||null}).catch(()=>{}),await Oo(t.repo,t.number).catch(()=>{})}}}}async function ch(t,e,n,r){let o=n.get(t.trackId);o&&((await B(o).catch(()=>[])).some(i=>i.type==="pr.state.changed"&&i.payload?.change==="merged"&&i.payload?.number===Number(t.number))||await r.emitMerged(o,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo}).catch(()=>{})),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function IT(t={}){let{now:e=Date.now(),auth:n=xi,view:r=gT,list:o=yT,links:s=Ad,trackMap:a=bT,emitMerged:i=kT,mark:c=Qe,archiveIfArmed:l=tn,batchThreshold:u=mT,detail:d=xT,merge:f=ST,lookup:w=Tt,disarm:m=Mo,emitDisarmed:g=vT,localHasCommit:x=_T}=t;if(!(await n().catch(()=>null))?.authenticated)return 0;let T=(await s().catch(()=>[])).filter(I=>e-(sh.get(bt(I.repo,I.number))||0)>=wT(I,e));if(!T.length)return 0;let E=new Map;for(let I of T)sh.set(bt(I.repo,I.number),e),E.has(I.repo)||E.set(I.repo,[]),E.get(I.repo).push(I);let J=await a().catch(()=>new Map),M={now:e,emitMerged:i,mark:c,archiveIfArmed:l,detail:d,merge:f,lookup:w,disarm:m,emitDisarmed:g,localHasCommit:x};for(let[I,h]of E){let p;if(h.length>u)p=await o(I).catch(()=>null);else{p=new Map;for(let b of h){let v=await r(I,b.number).catch(()=>null);v?.number!=null&&p.set(Number(v.number),v)}}if(p)for(let b of h){let v=p.get(Number(b.number));v&&(v.state==="MERGED"?await ch(b,v,J,M):v.state==="CLOSED"?await c(b.repo,b.number,"closed").catch(()=>{}):(await ET(b,v,J,M).catch(()=>{}),b.autoMerge&&await AT(b,J,M).catch(()=>{})))}}return T.length}function lh(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await IT()}catch{}finally{t=!1}}},ah)}import ue from"node:fs/promises";import nr from"node:path";import wh from"node:os";import uh from"node:fs/promises";import Ts from"node:path";import{execFile as RT}from"node:child_process";import{promisify as $T}from"node:util";import{fileURLToPath as OT}from"node:url";var CT=$T(RT),fh=Ts.dirname(OT(import.meta.url)),MT=Ts.resolve(fh,"..","..","client","dist"),PT="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",NT=new Map([...PT].map((t,e)=>[t,e]));function BT(t){let e=[],n=0,r=0;for(let o of t){let s=NT.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function DT(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=BT(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function jT(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var dh=new Map;async function LT(t){let e;try{e=await uh.stat(t)}catch{return null}let n=dh.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await uh.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:DT(r.mappings||"")};return dh.set(t,o),o}catch{return null}}var Fc=null;function UT(){return Fc||(Fc=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):CT("git",["rev-parse","--short","HEAD"],{cwd:Ts.resolve(fh,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Fc}var FT=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function HT(t,{distDir:e=MT}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(FT)){let[a,i,c,l]=s,u=await LT(Ts.join(e,"assets",`${i}.map`)),d=u?jT(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),d&&u.sources[d[1]]){let f=u.sources[d[1]].replace(/^(\.\.\/)+/,"");n.push(`${f}:${d[2]+1}:${d[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function ph(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await UT();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await HT(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var WT=()=>process.env.BOSTRAT_STATE_DIR||nr.join(process.env.HOME||wh.tmpdir(),".bostrat"),Be=()=>nr.join(WT(),"telemetry"),gh=()=>nr.join(Be(),".relay-offsets.json"),qT=50,GT=14,KT=3e4,mh=500,Mt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function VT(t,e){if(!t||typeof t!="object")return null;let n=Mt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Mt(t.anonId,64),sessionId:Mt(t.sessionId,64),userId:Mt(t.userId,64),release:Mt(t.release,64),appVersion:Mt(t.appVersion,32),route:Mt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Mt(t.error.kind,256),message:Mt(t.error.message,8192),stack:Mt(t.error.stack,32768),fingerprint:Mt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Mt(s?.n,64),r:Mt(s?.r,128)}))),o}var yh=t=>new Date(t).toISOString().slice(0,10),hh=0;async function bh(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,qT):[]).map(s=>VT(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await ph(s);await ue.mkdir(Be(),{recursive:!0});let o=nr.join(Be(),`${yh(e)}.ndjson`);return await ue.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
265
|
-
`).join(""),"utf8"),e-
|
|
266
|
-
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=mh){let w=d.slice(f,f+mh).map(g=>zT(g)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:w})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=w.length}r[s]=i+l+1,await Sh(r)}return{sent:o,disabled:!1}}var Hc=null,Wc=!1;function vh(){Hc||(Hc=setInterval(async()=>{if(!Wc){Wc=!0;try{await XT()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Wc=!1}}},KT),Hc.unref?.())}import Es from"node:fs/promises";import Ih from"node:path";import YT from"node:os";var Rh=()=>process.env.BOSTRAT_STATE_DIR||Ih.join(process.env.HOME||YT.tmpdir(),".bostrat"),$h=()=>Ih.join(Rh(),"dictation-glossary.json"),ZT=100,QT=500,tA=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Is=null,qc=null,Gc=Promise.resolve();function Th(){let t={},e=Date.now();for(let n of tA)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Oh(){return Is||(qc??=Es.readFile($h(),"utf8").then(t=>JSON.parse(t)?.terms??Th()).catch(()=>Th()).then(t=>Is=t).finally(()=>qc=null),qc)}async function Ah(){let t=JSON.stringify({terms:Is},null,2),e=$h(),n=`${e}.${process.pid}.tmp`;await Es.mkdir(Rh(),{recursive:!0}),await Es.writeFile(n,t),await Es.rename(n,e)}function eA(){return Gc=Gc.then(Ah,Ah),Gc.catch(()=>{})}async function Kc(){return Object.values(await Oh()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var nA=t=>Math.max(1,Math.ceil(t.length/4));async function rA(){let t=[],e=0;for(let{term:n}of await Kc()){let r=nA(n);if(!(e+r>QT)&&(t.push(n),e+=r,t.length>=ZT))break}return t}async function oA(t,e){let n=await Oh(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var _h=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),_s=t=>t.toLowerCase();function Eh(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Ch(t,e){let n=_h(t),r=_h(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let d=s-1;d>=0;d--)a[u][d]=_s(n[u])===_s(r[d])?a[u+1][d+1]+1:Math.max(a[u+1][d],a[u][d+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)_s(n[c])===_s(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Eh(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Eh(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await oA(u,"learned");return await eA(),[...i]}var zr=null;function sA(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function Mh(){if(!zr)return null;let t=await Dr();if(!t||t.apiKey.slice(-6)!==zr.keyTail)return null;let{keyTail:e,...n}=zr;return n}async function Ph(t,e){let n=await Dr();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await rA())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=sA(o.status,i);c.persistent&&(zr={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return zr=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Lh=ct.dirname(iA(import.meta.url)),Nh=Number(process.env.PORT)||3362,Rs=Cm(process.env),Uh=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,rr=Zm(process.env.BOSTRAT_ALLOW_IPS),k=de();k.disable("x-powered-by");k.use((t,e,n)=>{if(rr.allowed(rr.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});k.use(eh);k.use("/api/mocks",de.json({limit:"1mb"}));k.use("/api/mocks",de.raw({type:"image/png",limit:"24mb"}));k.use("/api/runbooks",de.json({limit:"1mb"}));k.use("/api/telemetry",de.json({limit:"256kb"}));k.use("/api/dictation",de.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));k.use("/api/tracks/:id/upload",de.raw({type:()=>!0,limit:"25mb"}));k.use(de.json({limit:"64kb"}));k.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Rt,hostTmuxAlive:await pe(),autoTriggerPct:Uh})});k.get("/api/sessions",async(t,e)=>{e.json({sessions:await vn()})});k.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await Ue(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});k.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!ut(n))return e.status(400).json({error:"invalid session name"});if(!await G(n))return e.status(404).json({error:"session not found"});try{await Wt(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});k.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!ut(n))return e.status(400).json({error:"invalid session name"});let r=await G(n);if(!r)return e.status(404).json({error:"session not found"});let o=await lr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});k.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!ut(n))return e.status(400).json({error:"invalid session name"});try{let r=await Pl(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});k.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=fo(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});k.get("/api/tracks",async(t,e)=>{e.json({tracks:await dt(),agents:Wi,tickets:await um()})});k.get("/api/cost",async(t,e)=>{try{let n=await Lm(await dt());if(n.cursorUsage=Vd(),t.query.format==="text")return e.type("text/plain").send(Um(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/usage/roundup",async(t,e)=>{try{e.json(await qm(await dt(),{window:t.query.window}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.post("/api/hook",async(t,e)=>{try{let n=await Om(t.body||{},bp);e.json({ok:!0,events:n.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});k.post("/api/telemetry",async(t,e)=>{try{if(!kh(rr.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await bh(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});k.get("/api/repos",async(t,e)=>{try{let n=await re(),r=new Set((await Promise.all(n.map(a=>Y(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await pr()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/github/token",async(t,e)=>{try{e.json(await ho(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});k.get("/api/github/status",(t,e)=>{e.json({configured:mo()})});k.post("/api/github/refresh",(t,e)=>{Jl(),e.json({ok:!0})});function Fh(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function Xc({repo:t=null,number:e=null,branch:n=null}){let r=t?await Y(t):null;if(r&&e!=null){let s=await Tt(r,e),a=s&&await rt(s.trackId);if(a)return a}return n?(await dt()).find(s=>s.workspace?.branch===n&&Fh(s,t,r))??null:null}async function hn(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Xc({repo:s,number:e,branch:o}).catch(()=>null);c&&await R({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...i}}).catch(()=>{})}async function dA(t){let e=(await dt()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Y(n.path);if(r)for(let o of n.prs){if(await Tt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Fh(a,n.path,r));s&&await Bt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}k.get("/api/git/prs",async(t,e)=>{try{let n=await kf();await dA(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,archive:a,headRefName:i,title:c,url:l}=t.body||{};try{if(s){let w=o||"merge";if(!Jo.has(w))throw new Error(`invalid merge method: ${w}`);let m=Number(r);if(!Number.isInteger(m)||m<=0)throw new Error("invalid PR number");let g=await Y((await Te(n)).path);if(!g)throw new Error("repo has no GitHub remote");let x=await Xc({repo:n,number:m,branch:i}).catch(()=>null);if(!x)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(await Bt({repo:g,number:m,trackId:x.id,url:l,headRef:i,title:c,source:"matched"}).catch(()=>{}),!await Co(g,m,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await za(g,m,a===!0),en(),await hn("auto-armed",{number:m,url:l,title:c,branch:i,repo:n,track:x}),e.json({ok:!0,number:m,auto:!0})}let u=await Ir(n,r,{method:o||"squash"});await hn("merged",{number:u.number,url:l,title:c,branch:i,repo:n});let d=await Y(n).catch(()=>null),f=!1;if(d&&(a===!0&&await za(d,u.number,!0).catch(()=>{}),f=await tn(d,u.number).catch(()=>!1),await Qe(d,u.number,"merged").catch(()=>{})),a===!0&&!f){let w=await Xc({repo:n,number:u.number,branch:i}).catch(()=>null);w&&!w.archivedAt&&await Vn(w.id).catch(()=>{})}e.json(u)}catch(u){let d=u?.message||String(u),f=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(d)?400:502;e.status(f).json({error:d})}});k.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await Y((await Te(n)).path).catch(()=>null),l=c?await Mo(c,i).catch(()=>null):null;try{await xf(n,i)}catch(u){if(!l)throw u}en(),await hn("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});k.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await Y(n).catch(()=>null),s=await Tf(n,{method:r||"merge",onMerged:a=>{hn("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&tn(o,a.number).catch(()=>{}).then(()=>Qe(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});k.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:vf()})});k.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s}=t.body||{};try{let a=n?await rt(n):null;if(!a)return e.status(400).json({error:"trackId required"});if(a.workspace?.kind==="multi")return e.status(400).json({error:"this Track tags multiple repos \u2014 ask the lead in chat to push and open the PR(s); the Git-tab door becomes per-repo in the next phase"});let i=await Af(a.cwd,{title:r,body:o,base:s}),c=await Y(a.cwd).catch(()=>null);c&&i.number&&await Bt({repo:c,number:i.number,trackId:a.id,url:i.url,headRef:a.workspace?.branch??null,title:r,source:"api"}),await hn("opened",{number:i.number,url:i.url,title:r,track:a,branch:a.workspace?.branch}),e.json(i)}catch(a){let i=a?.message||String(a),c=/required|invalid/i.test(i)?400:502;e.status(c).json({error:i})}});k.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Er(n,r),a=(await dt()).find(w=>!w.archivedAt&&w.workspace?.branch===o.headRefName);if(!a)return e.status(404).json({error:`no active Track owns branch "${o.headRefName}"`});let i=await _f(n,o.number);if(!i.length)return e.status(409).json({error:"no failing checks on this PR"});let c=ct.join(a.cwd,".bostrat","ci"),l=ct.join(".bostrat","ci",`pr-${o.number}-fail.log`);await It.promises.mkdir(c,{recursive:!0}),await It.promises.writeFile(ct.join(a.cwd,l),i.map(w=>`\u2550\u2550\u2550\u2550 ${w.name}${w.link?` \xB7 ${w.link}`:""} \u2550\u2550\u2550\u2550
|
|
267
|
-
${
|
|
291
|
+
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function Rw(t,e){let n=typeof e=="string"?{host:e}:e||{},r=n["x-bostrat-view-base"];return typeof r=="string"&&r?`${n["x-forwarded-proto"]==="https"?"https":"http"}://${t.slug}.${r.replace(/:\d+$/,"")}/`:`http://${t.slug}.${n_(n.host)}/`}function n_(t){if(process.env.BOSTRAT_PUBLIC_HOST)return String(process.env.BOSTRAT_PUBLIC_HOST).replace(/:\d+$/,"");let e=String(t||"").replace(/:\d+$/,"").toLowerCase();if(!e||!e.includes(".")||/^[\d.]+$/.test(e))return"bostrat.globopi";let n=e.split(".");return Tw(e)&&ni(n[0])?n.slice(1).join("."):e}import{execFile as r_}from"node:child_process";import{promisify as o_}from"node:util";var $l=o_(r_),$w=3e4,s_=30*6e4,a_=$w,i_=10*6e4,c_=10080*6e4,l_=60*6e4,u_=8,d_=1440*6e4,f_=3,Ow="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Rl=60*6e4,p_=15*6e4,ia=new Map,ca=new Map,m_=/could not resolve|not found|no pull requests?/i,h_=t=>m_.test(String(t?.message||t||""));function w_(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Rl)}var Cw={fast:a_,slow:i_,decay:l_};function Mw(t,e){return t.armedAt&&e-t.armedAt<s_||w_(t,e)?"fast":e-(t.firstSeen||0)>c_?"decay":"slow"}function g_(t,e){return Cw[Mw(t,e)]}function Ao(t,e,n,r){let o=t.lanes?.get(st(e.repo,e.number))||{};return js({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function y_(t,e){he("watch");try{let{stdout:n}=await $l("gh",["pr","view",String(e),"--repo",t,"--json",Ow],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ne({slug:t})}});return JSON.parse(n||"null")}catch(n){throw De(n)&&je(n),n}}async function b_(t){he("watch");try{let{stdout:e}=await $l("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Ow],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ne({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw De(e)&&je(e),e}}async function k_(){let t=new Map;for(let e of await ft().catch(()=>[]))t.set(e.id,e);return t}async function x_(t,e){await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function S_(t,e){let n=await vn(t);return n?lr(n,e):null}async function v_(t,e,n){let r=await vn(t);if(!r)throw new Error(`no local checkout for ${t}`);return ur(r,e,{method:n})}async function A_(t,e){let n=await vn(t);if(!n)throw new Error(`no local checkout for ${t}`);return oo(n,e)}async function T_(t,e){await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Sr(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var __=10*6e4;async function E_(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>d_){await Sr(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await Sr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Sr(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<__)return;await Sr(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await Sr(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await Sr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Pw(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function xo(t,e,n){await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function I_(t,e){try{return await $l("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function So(t,e,n,r=()=>!0){return(await F(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var vo=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function R_(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await ds(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ps(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<p_;await pf(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await ff(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await So(a,"pushed",t.number,d=>d.payload?.sha===s)&&await xo(a,"pushed",{...vo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Rl)await fs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await fs(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Ao(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await So(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await xo(a,"ci-fixed",{...vo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await fs(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Rl)await ms(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await ms(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Ao(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await So(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await xo(a,"reconciled",{...vo(t,e),base:i.baseRefName||null}).catch(()=>{}),await ms(t.repo,t.number).catch(()=>{})}}}}async function $_(t,e,n,r){let o=Ao(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await So(s,"closed",t.number)&&await xo(s,"closed",{...vo(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function O_(t,e,n){Ao(n,t,"lost",null);let r=e.get(t.trackId);r&&!await So(r,"lost",t.number)&&await xo(r,"lost",vo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Pw(t,e,n,r,o={}){let s=Ao(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await F(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function C_(t={}){let{now:e=Date.now(),auth:n=Bc,view:r=y_,list:o=b_,links:s=hf,trackMap:a=k_,emitMerged:i=x_,mark:c=Oe,archiveIfArmed:l=Fe,notifyIfArmed:u=Ue,notifyIfResolved:d=sl,batchThreshold:f=f_,detail:p=S_,merge:h=v_,updateBranch:m=A_,lookup:b=St,disarm:k=Gr,emitDisarmed:v=T_,localHasCommit:E=I_}=t;if(!(await n().catch(()=>null))?.authenticated||$s()>0)return 0;let I=(await s().catch(()=>[])).filter(y=>e-(ia.get(st(y.repo,y.number))||0)>=g_(y,e));if(!I.length)return 0;let $=Date.now(),_=new Map,U=new Map;for(let y of I){let A=Mw(y,e);_.set(st(y.repo,y.number),{lane:A,intervalMs:Cw[A]}),ia.set(st(y.repo,y.number),e),U.has(y.repo)||U.set(y.repo,[]),U.get(y.repo).push(y)}let V=await a().catch(()=>new Map),g={now:e,lanes:_,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:d,detail:p,merge:h,updateBranch:m,lookup:b,disarm:k,emitDisarmed:v,localHasCommit:E};for(let[y,A]of U){let R,D=new Set;if(A.length>f)R=await o(y).catch(()=>null);else{R=new Map;for(let T of A)try{let O=await r(y,T.number);O?.number!=null&&R.set(Number(O.number),O)}catch(O){h_(O)&&D.add(st(T.repo,T.number))}}if(R)for(let T of A){let O=st(T.repo,T.number),W=R.get(Number(T.number));if(!W){if(D.has(O)){let tt=(ca.get(O)||0)+1;ca.set(O,tt),tt>=u_&&(await O_(T,V,g),ca.delete(O),ia.delete(O))}continue}ca.delete(O),W.state==="MERGED"||W.state==="CLOSED"?(W.state==="MERGED"?await Pw(T,W,V,g):await $_(T,W,V,g),ia.delete(O)):(await R_(T,W,V,g).catch(()=>{}),T.autoMerge&&await E_(T,V,g).catch(()=>{}))}}let w={};for(let{lane:y}of _.values())w[y]=(w[y]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(w).map(([y,A])=>`${y} ${A}`).join(", ")}) in ${Date.now()-$}ms`),I.length}function Nw(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await C_()}catch{}finally{t=!1}}},$w)}import be from"node:fs/promises";import vr from"node:path";import Hw from"node:os";import Bw from"node:fs/promises";import la from"node:path";import{execFile as M_}from"node:child_process";import{promisify as P_}from"node:util";import{fileURLToPath as N_}from"node:url";var B_=P_(M_),jw=la.dirname(N_(import.meta.url)),D_=la.resolve(jw,"..","..","client","dist"),j_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",L_=new Map([...j_].map((t,e)=>[t,e]));function F_(t){let e=[],n=0,r=0;for(let o of t){let s=L_.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function U_(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=F_(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function H_(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var Dw=new Map;async function W_(t){let e;try{e=await Bw.stat(t)}catch{return null}let n=Dw.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Bw.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:U_(r.mappings||"")};return Dw.set(t,o),o}catch{return null}}var Ol=null;function q_(){return Ol||(Ol=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):B_("git",["rev-parse","--short","HEAD"],{cwd:la.resolve(jw,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Ol}var G_=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function K_(t,{distDir:e=D_}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(G_)){let[a,i,c,l]=s,u=await W_(la.join(e,"assets",`${i}.map`)),d=u?H_(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),d&&u.sources[d[1]]){let f=u.sources[d[1]].replace(/^(\.\.\/)+/,"");n.push(`${f}:${d[2]+1}:${d[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function Lw(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await q_();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await K_(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var V_=()=>process.env.BOSTRAT_STATE_DIR||vr.join(process.env.HOME||Hw.tmpdir(),".bostrat"),Xe=()=>vr.join(V_(),"telemetry"),Ww=()=>vr.join(Xe(),".relay-offsets.json"),J_=50,z_=14,X_=3e4,Fw=500,Nt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function Y_(t,e){if(!t||typeof t!="object")return null;let n=Nt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Nt(t.anonId,64),sessionId:Nt(t.sessionId,64),userId:Nt(t.userId,64),release:Nt(t.release,64),appVersion:Nt(t.appVersion,32),route:Nt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Nt(t.error.kind,256),message:Nt(t.error.message,8192),stack:Nt(t.error.stack,32768),fingerprint:Nt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Nt(s?.n,64),r:Nt(s?.r,128)}))),o}var qw=t=>new Date(t).toISOString().slice(0,10),Uw=0;async function Gw(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,J_):[]).map(s=>Y_(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Lw(s);await be.mkdir(Xe(),{recursive:!0});let o=vr.join(Xe(),`${qw(e)}.ndjson`);return await be.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
292
|
+
`).join(""),"utf8"),e-Uw>6*3600*1e3&&(Uw=e,Z_(e).catch(()=>{})),r.length}async function Z_(t=Date.now()){let e=qw(t-z_*24*3600*1e3),n=[];try{n=await be.readdir(Xe())}catch{return}let r=n.filter(s=>s.endsWith(".ndjson")&&s.slice(0,10)<e);if(!r.length)return;for(let s of r)await be.rm(vr.join(Xe(),s),{force:!0});let o=await Vw();for(let s of r)delete o[s];await Jw(o)}var ua=new Map;function Kw(t,e=Date.now()){let n=t||"unknown",r=ua.get(n);return r||(ua.size>512&&ua.clear(),r={tokens:40,at:e},ua.set(n,r)),r.tokens=Math.min(40,r.tokens+(e-r.at)/6e4*20),r.at=e,r.tokens<1?!1:(r.tokens-=1,!0)}async function Vw(){try{return JSON.parse(await be.readFile(Ww(),"utf8"))||{}}catch{return{}}}async function Jw(t){await be.mkdir(Xe(),{recursive:!0}),await be.writeFile(Ww(),JSON.stringify(t),"utf8")}function Q_(t,{env:e,host:n}={}){let r=t.name==="app.error"&&t.error,o={};for(let[a,i]of[["event",t.name],["route",t.route],["session",t.sessionId],["anon",t.anonId],["user",t.userId],["release",t.release],["appVersion",t.appVersion],["device",t.deviceClass]])i&&(o[a]=i);let s={timestamp:new Date(t.ts).toISOString(),service:"bostrat-client",source:"treetop-js",env:e||process.env.BOSTRAT_ENV||"pi",host:n||Hw.hostname(),level:r?"error":"info",message:r?`${t.error.kind||"Error"}: ${t.error.message||"(no message)"}`.slice(0,8192):t.name,tags:o,attributes:{...t.props?{props:t.props}:{},receivedAt:t.receivedAt}};return r&&(s.error={kind:t.error.kind,message:t.error.message,stack:t.error.stack},t.error.fingerprint&&(s.fingerprint=t.error.fingerprint),t.error.symbolicated&&(s.attributes.symbolicated=!0),t.breadcrumbs&&(s.attributes.breadcrumbs=t.breadcrumbs)),s}async function tE({fetchImpl:t=fetch}={}){let e=await Pd();if(!e)return{sent:0,disabled:!0};let n;try{n=(await be.readdir(Xe())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Vw(),o=0;for(let s of n){let a=vr.join(Xe(),s),i=r[s]||0,c;try{let f=await be.open(a,"r");try{let p=(await f.stat()).size;if(p<=i)continue;c=Buffer.alloc(p-i),await f.read(c,0,c.length,i)}finally{await f.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
293
|
+
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=Fw){let p=d.slice(f,f+Fw).map(m=>Q_(m)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await Jw(r)}return{sent:o,disabled:!1}}var Cl=null,Ml=!1;function zw(){Cl||(Cl=setInterval(async()=>{if(!Ml){Ml=!0;try{await tE()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Ml=!1}}},X_),Cl.unref?.())}import fa from"node:fs/promises";import tg from"node:path";import eE from"node:os";var eg=()=>process.env.BOSTRAT_STATE_DIR||tg.join(process.env.HOME||eE.tmpdir(),".bostrat"),ng=()=>tg.join(eg(),"dictation-glossary.json"),nE=100,rE=500,oE=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],pa=null,Pl=null,Nl=Promise.resolve();function Xw(){let t={},e=Date.now();for(let n of oE)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function rg(){return pa||(Pl??=fa.readFile(ng(),"utf8").then(t=>JSON.parse(t)?.terms??Xw()).catch(()=>Xw()).then(t=>pa=t).finally(()=>Pl=null),Pl)}async function Yw(){let t=JSON.stringify({terms:pa},null,2),e=ng(),n=`${e}.${process.pid}.tmp`;await fa.mkdir(eg(),{recursive:!0}),await fa.writeFile(n,t),await fa.rename(n,e)}function sE(){return Nl=Nl.then(Yw,Yw),Nl.catch(()=>{})}async function Bl(){return Object.values(await rg()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var aE=t=>Math.max(1,Math.ceil(t.length/4));async function iE(){let t=[],e=0;for(let{term:n}of await Bl()){let r=aE(n);if(!(e+r>rE)&&(t.push(n),e+=r,t.length>=nE))break}return t}async function cE(t,e){let n=await rg(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Zw=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),da=t=>t.toLowerCase();function Qw(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function og(t,e){let n=Zw(t),r=Zw(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let d=s-1;d>=0;d--)a[u][d]=da(n[u])===da(r[d])?a[u+1][d+1]+1:Math.max(a[u+1][d],a[u][d+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)da(n[c])===da(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Qw(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Qw(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await cE(u,"learned");return await sE(),[...i]}var To=null;function lE(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function sg(){if(!To)return null;let t=await Lr();if(!t||t.apiKey.slice(-6)!==To.keyTail)return null;let{keyTail:e,...n}=To;return n}async function ag(t,e){let n=await Lr();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await iE())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=lE(o.status,i);c.persistent&&(To={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return To=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var dg=ct.dirname(dE(import.meta.url)),ig=Number(process.env.PORT)||3362,ma=ow(process.env),fg=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Ar=Aw(process.env.BOSTRAT_ALLOW_IPS),S=ae();S.disable("x-powered-by");S.use((t,e,n)=>{if(Ar.allowed(Ar.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});S.use(Ew);S.use("/api/mocks",ae.json({limit:"1mb"}));S.use("/api/mocks",ae.raw({type:"image/png",limit:"24mb"}));S.use("/api/runbooks",ae.json({limit:"1mb"}));S.use("/api/telemetry",ae.json({limit:"256kb"}));S.use("/api/hook",ae.json({limit:"2mb"}));S.use("/api/dictation",ae.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));S.use("/api/tracks/:id/upload",ae.raw({type:()=>!0,limit:"25mb"}));S.use(ae.json({limit:"64kb"}));S.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ct,hostTmuxAlive:await xe(),autoTriggerPct:fg})});S.get("/api/sessions",async(t,e)=>{e.json({sessions:await Pn()})});S.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await tn(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});S.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});if(!await z(n))return e.status(404).json({error:"session not found"});try{await Vt(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});S.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});let r=await z(n);if(!r)return e.status(404).json({error:"session not found"});let o=await $r(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});S.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Ru(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});S.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Ho(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});S.get("/api/tracks",async(t,e)=>{e.json({tracks:await ft(),agents:Qc,tickets:await is()})});S.get("/api/cost",async(t,e)=>{try{let n=await dw(await ft());if(n.cursorUsage=Mm(),t.query.format==="text")return e.type("text/plain").send(fw(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/usage/roundup",async(t,e)=>{try{e.json(await ww(await ft(),{window:t.query.window}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs",async(t,e)=>{try{e.json(await xf(await ft()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs/:key",async(t,e)=>{try{let n=await Sf(await ft(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Nh()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/hook",async(t,e)=>{try{let n=await rw(t.body||{},hh);e.json({ok:!0,events:n.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.post("/api/telemetry",async(t,e)=>{try{if(!Kw(Ar.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Gw(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.get("/api/repos",async(t,e)=>{try{let n=await pe(),r=new Set((await Promise.all(n.map(a=>Y(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Vr()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/github/token",async(t,e)=>{try{e.json(await ys(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});S.get("/api/github/status",(t,e)=>{e.json({configured:gs(),calls:dm(),latency:jm()})});S.post("/api/github/refresh",(t,e)=>{Mf(),e.json({ok:!0})});function Fl(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function ha({repo:t=null,number:e=null,branch:n=null}){let r=t?await Y(t):null;if(r&&e!=null){let s=await St(r,e),a=s&&await nt(s.trackId);if(a)return a}return n?(await ft()).find(s=>s.workspace?.branch===n&&Fl(s,t,r))??null:null}async function pg({repo:t=null,number:e=null,branch:n=null}){let r=await ha({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await Y(t).catch(()=>null):null;return(await ft()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Fl(a,t,o))??null}async function Kt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ha({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Y(s).catch(()=>null):null);await C({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function hE(t){let e=(await ft()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Y(n.path);if(r)for(let o of n.prs){if(await St(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Fl(a,n.path,r));s&&await Mt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}S.get("/api/git/prs",async(t,e)=>{try{let n=await Gm();await hE(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:d}=t.body||{};try{if(s){let m=o||"merge";if(!ro.has(m))throw new Error(`invalid merge method: ${m}`);let b=Number(r);if(!Number.isInteger(b)||b<=0)throw new Error("invalid PR number");let k=await Y((await we(n)).path);if(!k)throw new Error("repo has no GitHub remote");let v=await ha({repo:n,number:b,branch:l}).catch(()=>null);if(!v)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await oo(n,b),await Mt({repo:k,number:b,trackId:v.id,url:d,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await qr(k,b,{merge:{method:m}}))throw new Error("couldn't record the auto-merge arm");return await zn(k,b,i===!0),await hs(k,b,c===!0&&i!==!0),oe(),await Kt("auto-armed",{number:b,url:d,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:b,auto:!0})}let f=await ur(n,r,{method:o||"squash"});await Kt("merged",{number:f.number,url:d,title:u,branch:l,repo:n});let p=await Y(n).catch(()=>null),h=!1;if(p&&(i===!0&&await zn(p,f.number,!0).catch(()=>{}),c===!0&&i!==!0&&await hs(p,f.number,!0).catch(()=>{}),await Ue(p,f.number).catch(()=>{}),h=await Fe(p,f.number).catch(()=>!1),await Oe(p,f.number,"merged").catch(()=>{})),i===!0&&!h){let m=await ha({repo:n,number:f.number,branch:l}).catch(()=>null);m&&!m.archivedAt&&await wr(m.id).catch(()=>{})}e.json(f)}catch(f){let p=f?.message||String(f),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(h).json({error:p})}});S.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await Y((await we(n)).path).catch(()=>null),l=c?await Gr(c,i).catch(()=>null):null;try{await Lc(n,i)}catch(u){if(!l)throw u}oe(),await Kt("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});S.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await Y(n).catch(()=>null),s=await zm(n,{method:r||"merge",onMerged:a=>{Kt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&Ue(o,a.number).catch(()=>{}).then(()=>Fe(o,a.number)).catch(()=>{}).then(()=>Oe(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});S.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Jm()})});S.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!ro.has(i))throw new Error(`invalid merge method: ${i}`);let c=n?await nt(n):null;if(!c)return e.status(400).json({error:"trackId required"});let l=(await Ce()).filter(f=>f.trackId===c.id&&f.state!=="merged"&&f.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let f of l)await zn(f.repo,f.number,o===!0).catch(()=>{}),await hs(f.repo,f.number,s===!0&&o!==!0).catch(()=>{});let u=[],d=[];for(let f of l){let p={repo:f.repo,number:f.number,ok:!0,action:"armed"};u.push(p);try{let h=await vn(f.repo);if(!h)throw new Error(`no local checkout for ${f.repo}`);let m=await lr(h,f.number);d.push({r:p,l:f,repoPath:h,pr:m,plan:Km(m,{update:r!==!1})})}catch(h){p.ok=!1,p.action="failed",p.reason=h?.message||String(h)}}for(let{r:f,l:p,repoPath:h,pr:m,plan:b}of Vm(d))try{if(b.action==="skip")f.action="skipped",f.reason=b.reason;else if(b.action==="merge")await ur(h,p.number,{method:i}),await Kt("merged",{number:p.number,url:m.url,title:m.title,branch:m.headRefName,repo:h,track:c}),await Ue(p.repo,p.number).catch(()=>{}),await Fe(p.repo,p.number).catch(()=>{}),await Oe(p.repo,p.number,"merged").catch(()=>{}),f.action="merged";else{if(b.action==="update-arm"&&(await oo(h,p.number).catch(()=>{}),f.action="updated-armed"),!await qr(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await Kt("auto-armed",{number:p.number,url:m.url,title:m.title,branch:m.headRefName,repo:h,track:c,...f.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(k){f.ok=!1,f.action="failed",f.reason=k?.message||String(k)}if(oe(),!u.some(f=>f.ok))return e.status(502).json({error:u.map(f=>`${f.repo}#${f.number}: ${f.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});S.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{let r=n?await nt(n):null;if(!r)return e.status(400).json({error:"trackId required"});let o=(await Ce()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Gr(a.repo,a.number).catch(()=>null),c=await vn(a.repo).catch(()=>null);c&&await Lc(c,a.number).catch(()=>{}),i&&(await Kt("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}oe(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});S.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{let i=n?await nt(n):null;if(!i)return e.status(400).json({error:"trackId required"});if(i.workspace?.kind==="multi"){let u=Rt(i.workspace),d,f=Array.isArray(a)?a.filter(Boolean):[];if(f.length)d=f.map(m=>{let b=u.find(k=>k.name===m||k.repo===m||k.base===m||k.dir===m);if(!b)throw new Error(`invalid repo "${m}" \u2014 not tagged on this Track`);return b});else if(d=await to(i),!d.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let m of d)try{let b=await Uc(m.dir,{title:r,body:o,base:s}),k=m.repo||await Y(m.dir).catch(()=>null);k&&b.number&&await Mt({repo:k,number:b.number,trackId:i.id,url:b.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Kt("opened",{number:b.number,url:b.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:k??m.name}}),p.push({repo:m.name,slug:k??null,ok:!0,url:b.url,number:b.number})}catch(b){p.push({repo:m.name,slug:m.repo??null,ok:!1,error:b?.message||String(b)})}let h=p.filter(m=>m.ok);return h.length?e.json({ok:!0,multi:!0,results:p,url:h[0].url,number:h[0].number}):e.status(502).json({error:p.map(m=>`${m.repo}: ${m.error}`).join("; "),results:p})}let c=await Uc(i.cwd,{title:r,body:o,base:s}),l=await Y(i.cwd).catch(()=>null);l&&c.number&&await Mt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Kt("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});S.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await to(n),o=new Map(r.map(a=>[a.base,a])),s=Rt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await Eh(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});S.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await Ih(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});S.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await lr(n,r),s=await Y(n).catch(()=>null),a=await pg({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Rt(a.workspace).find(k=>k.base===n||s&&k.repo===s)??null,c=a.workspace?.kind==="multi",l=await Xm(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ct.join("ci",i.name):"ci",d=ct.join(a.cwd,".bostrat",u),f=ct.join(".bostrat",u,`pr-${o.number}-fail.log`);await Ot.promises.mkdir(d,{recursive:!0}),await Ot.promises.writeFile(ct.join(a.cwd,f),l.map(k=>`\u2550\u2550\u2550\u2550 ${k.name}${k.link?` \xB7 ${k.link}`:""} \u2550\u2550\u2550\u2550
|
|
294
|
+
${k.log??"(no fetchable log \u2014 see link)"}
|
|
268
295
|
`).join(`
|
|
269
|
-
`));let u=i.map(w=>w.name).join(", "),d=await $e(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}. Failing check${i.length===1?"":"s"}: ${u}. The failure log is at ${l} in this worktree \u2014 read it, fix the root cause, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),f=await Y(n).catch(()=>null);f&&(await Bt({repo:f,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Sd(f,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await hn("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:i.map(w=>w.name)}}),e.json({ok:!0,leadBox:d.leadBox,queued:!!d.queued,checks:i.map(w=>w.name),log:l})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});k.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Er(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let a=(await dt()).find(l=>!l.archivedAt&&l.workspace?.branch===o.headRefName);if(!a)return e.status(404).json({error:`no active Track owns branch "${o.headRefName}"`});let i=await $e(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}. Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),c=await Y(n).catch(()=>null);c&&(await Bt({repo:c,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await vd(c,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await hn("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:i.leadBox,queued:!!i.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});k.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=wd.filter(u=>t.body.actions.includes(u)),!o.length)return e.status(400).json({error:"actions must include commit, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=await rt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});let a=await B(s),i=a.find(u=>u.type==="pr.offered"&&u.payload?.offerId===n);if(!i)return e.status(404).json({error:"unknown offer"});if(a.some(u=>u.type==="pr.offer.answered"&&u.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let c=null;o&&(c=await $e(s.id,Ao(o,{branch:i.payload.branch,base:i.payload.base,toPr:i.payload.toPr||null}),{kind:o.includes("pr")?"pr-go":"push-go"}));let l=o?o.includes("pr")?"pr":o.includes("push")?"push":"commit":"dismiss";await R({id:s.id,cwd:s.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:i.boxId,payload:{ts:Date.now(),offerId:n,action:l,actions:o}}),e.json({ok:!0,offerId:n,action:l,actions:o,queued:!!c?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var Xr=t=>e=>({...e,url:rh(e,t.headers)});k.get("/api/views",async(t,e)=>{try{let n=await af({trackId:t.query.track||void 0});e.json({views:n.map(Xr(t)),maxLive:ve})}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/views/:id",async(t,e)=>{let n=await cf(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Xr(t)(n),session:Nn(n.slug)})});k.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await rt(n):null;if(!i&&s&&(i=(await dt()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await yi({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:Xr(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});k.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Xr(t)(await lf(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});k.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Xr(t)(await uf(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});k.delete("/api/views/:id",async(t,e)=>{try{e.json(await df(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var fA=[["linear",ln,ac],["jira",cs,oc]],Vc=new Map;k.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await rc(),r={};for(let[o,s,a]of fA){let i=await s();if(!i){Vc.delete(o),r[o]={configured:!1};continue}let c=Vc.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},Vc.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await rc()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Vp(o),i=n?await rt(n):null;!i&&r&&(i=(await dt()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ct.sep)))||null),i&&await R({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});k.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await rt(n):null;if(!s&&r&&(s=(await dt()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Yn(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});k.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await rt(r):null;if(!o&&n&&(o=(await dt()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ct.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await dm(),ticket:await te(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});k.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await te(t.params.id)})});k.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Yn(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});k.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await gc(t.params.id)})});var $s=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;k.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await hm(t.params.id))}catch(n){let r=n?.message||String(n);e.status($s(r)).json({error:r})}});k.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await yc(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status($s(r)).json({error:r})}});k.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await wm(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status($s(r)).json({error:r})}});k.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await rt(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await gm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status($s(s)).json({error:s})}});k.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await rt(n):null;if(!s&&r&&(s=(await dt()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await bm(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});k.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await ym()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});k.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await rt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await km(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});k.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Df({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/mocks/:id",async(t,e)=>{let n=await Ae(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});k.get("/api/mocks/:id/png",async(t,e)=>{let n=await Ae(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Dt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});k.get("/api/mocks/:id/html",async(t,e)=>{let n=await Ae(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Dt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});k.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await rt(o):null;if(!l&&a&&(l=(await dt()).find(w=>w.cwd&&(a===w.cwd||a.startsWith(w.cwd+ct.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(f=>f.name===l.leadBox),d=await jf({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:d})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});k.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Lf(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});k.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Ff(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await $e(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});k.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Hf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});k.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await dt(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ct.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Xf({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});k.get("/api/runbooks/:id",async(t,e)=>{let n=await Oi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});k.get("/api/runbooks/:id/md",async(t,e)=>{let n=await Oi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Vf(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(Un(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});k.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await rt(n):null;if(!a&&s&&(a=(await dt()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Yf({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});k.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Zf(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});k.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await Qf(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});k.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await tp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});k.post("/api/dictation",async(t,e)=>{if(!Buffer.isBuffer(t.body)||t.body.length===0)return e.status(400).json({error:"expected a raw audio body"});try{e.json(await Ph(t.body,t.headers["content-type"]))}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});k.get("/api/dictation/glossary",async(t,e)=>{e.json({configured:!!await Dr(),terms:await Kc(),problem:await Mh()})});k.post("/api/dictation/learn",async(t,e)=>{let{transcript:n,finalText:r}=t.body||{};if(typeof n!="string"||typeof r!="string")return e.status(400).json({error:"transcript and finalText are required"});e.json({learned:await Ch(n,r)})});k.post("/api/tracks",async(t,e)=>{let{name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i,ticket:c}=t.body||{};try{let l=await Ji({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Yn(l,c,{source:"create"})}catch(d){u=d?.message||String(d)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});k.get("/api/tracks/:id",async(t,e)=>{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Br(t.params.id),subtasks:Jn(t.params.id).map(Yr)})});k.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Cp(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await gc(t.params.id).catch(()=>{}),e.json(n)}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});k.post("/api/tracks/:id/archive",async(t,e)=>{try{e.json({ok:!0,track:await Vn(t.params.id)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/already archived/i.test(r)?409:500).json({error:r})}});k.post("/api/tracks/:id/unarchive",async(t,e)=>{try{e.json({ok:!0,track:await Mp(t.params.id)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/not archived/i.test(r)?409:500).json({error:r})}});k.get("/api/tracks/:id/messages",async(t,e)=>{let n=await Gi(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});k.get("/api/tracks/:id/events",async(t,e)=>{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
296
|
+
`));let p=l.map(k=>k.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",m=await ge(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${f} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),b=s;b&&(await Mt({repo:b,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await lf(b,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Kt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(k=>k.name)}}),e.json({ok:!0,leadBox:m.leadBox,queued:!!m.queued,checks:l.map(k=>k.name),log:f})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});S.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await lr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Y(n).catch(()=>null),a=await pg({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Rt(a.workspace).find(d=>d.base===n||s&&d.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await ge(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Mt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await uf(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Kt("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});S.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Zp.filter(d=>t.body.actions.includes(d)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await nt(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await F(a),c=i.find(d=>d.type==="pr.offered"&&d.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(d=>d.type==="pr.offer.answered"&&d.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await ge(a.id,Es(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await C({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&kh(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var _o=t=>e=>({...e,url:Rw(e,t.headers)});S.get("/api/views",async(t,e)=>{try{let n=await Qo({trackId:t.query.track||void 0});e.json({views:n.map(_o(t)),maxLive:Ee})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/views/:id",async(t,e)=>{let n=await pd(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:_o(t)(n),session:Un(n.slug)})});S.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await nt(n):null;if(!i&&s&&(i=(await ft()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ri({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:_o(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});S.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:_o(t)(await md(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:_o(t)(await hd(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});S.delete("/api/views/:id",async(t,e)=>{try{e.json(await wd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var wE=[["linear",cn,di],["jira",rs,li]],Dl=new Map;S.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await ci(),r={};for(let[o,s,a]of wE){let i=await s();if(!i){Dl.delete(o),r[o]={configured:!1};continue}let c=Dl.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},Dl.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await ci()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Md(o),i=n?await nt(n):null;!i&&r&&(i=(await ft()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ct.sep)))||null),i&&await C({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});S.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await nt(n):null;if(!s&&r&&(s=(await ft()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Jn(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});S.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await nt(r):null;if(!o&&n&&(o=(await ft()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ct.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await zd(),ticket:await ee(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});S.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await ee(t.params.id)})});S.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Jn(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});S.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Si(t.params.id)})});var wa=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;S.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Qd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(wa(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await vi(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(wa(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await tf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(wa(r)).json({error:r})}});S.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await nt(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await ef(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(wa(s)).json({error:s})}});S.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await nt(n):null;if(!s&&r&&(s=(await ft()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await rf(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});S.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await nf()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});S.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await nt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await of(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});S.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Jo({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/mocks/:id",async(t,e)=>{let n=await _e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});S.get("/api/mocks/:id/png",async(t,e)=>{let n=await _e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(jt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});S.get("/api/mocks/:id/html",async(t,e)=>{let n=await _e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(jt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});S.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await nt(o):null;if(!l&&a&&(l=(await ft()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+ct.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(f=>f.name===l.leadBox),d=await zu({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:d})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});S.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Xu(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});S.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Zu(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ge(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});S.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Qu(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ft(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ct.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await ts({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/runbooks/:id",async(t,e)=>{let n=await ai(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});S.get("/api/runbooks/:id/md",async(t,e)=>{let n=await ai(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=xd(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(Hn(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});S.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await nt(n):null;if(!a&&s&&(a=(await ft()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Ad({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});S.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Td(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});S.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await _d(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});S.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await Ed(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.post("/api/dictation",async(t,e)=>{if(!Buffer.isBuffer(t.body)||t.body.length===0)return e.status(400).json({error:"expected a raw audio body"});try{e.json(await ag(t.body,t.headers["content-type"]))}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});S.get("/api/dictation/glossary",async(t,e)=>{e.json({configured:!!await Lr(),terms:await Bl(),problem:await sg()})});S.post("/api/dictation/learn",async(t,e)=>{let{transcript:n,finalText:r}=t.body||{};if(typeof n!="string"||typeof r!="string")return e.status(400).json({error:"transcript and finalText are required"});e.json({learned:await og(n,r)})});S.post("/api/tracks",async(t,e)=>{let{name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i,ticket:c}=t.body||{};try{let l=await ol({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Jn(l,c,{source:"create"})}catch(d){u=d?.message||String(d)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});S.get("/api/tracks/:id",async(t,e)=>{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:mg(po(t.params.id)),subtasks:gr(t.params.id).map(Eo)})});S.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Mh(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Si(t.params.id).catch(()=>{}),e.json(n)}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.post("/api/tracks/:id/archive",async(t,e)=>{try{e.json({ok:!0,track:await wr(t.params.id)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/already archived/i.test(r)?409:500).json({error:r})}});S.post("/api/tracks/:id/unarchive",async(t,e)=>{try{e.json({ok:!0,track:await Dh(t.params.id)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/not archived/i.test(r)?409:500).json({error:r})}});S.get("/api/tracks/:id/messages",async(t,e)=>{let n=await tl(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});S.get("/api/tracks/:id/events",async(t,e)=>{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
270
297
|
data: ${JSON.stringify(i)}
|
|
271
298
|
|
|
272
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
299
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await F(n))i.seq>o&&r(i);let s=Nu(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
273
300
|
|
|
274
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});
|
|
301
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});S.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await Lh(t.params.id)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/mid-turn|terminal|archived/i.test(r)?409:500;e.status(o).json({error:r})}});S.post("/api/tracks/:id/say",async(t,e)=>{let{text:n,attachments:r}=t.body||{};try{e.json(await ge(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});S.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(n.archivedAt)return e.status(409).json({error:"track is archived"});if(!n.cwd)return e.status(409).json({error:"track has no workspace yet"});let r=Buffer.isBuffer(t.body)?t.body:null,o=await eh(n.cwd,{filename:t.query.filename,buffer:r});e.json({ok:!0,attachment:o})}catch(n){let r=n?.message||String(n);e.status(/too large|unsupported|empty|required/i.test(r)?400:500).json({error:r})}});S.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(!n.cwd)return e.status(404).json({error:"track has no workspace"});let r=ct.basename(String(t.params.file||""));if(!r||!qc(ct.extname(r).slice(1)))return e.status(400).json({error:"not an upload path"});e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.sendFile(ct.join(Wc(n.cwd),r),o=>{o&&!e.headersSent&&e.status(404).json({error:"upload not found"})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});var gE=/^[0-9a-fA-F-]{8,64}$/;S.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});let{box:r,uuid:o}=t.params,s=Number(t.params.idx);if(!gE.test(o)||!Number.isInteger(s)||s<0)return e.status(400).json({error:"bad image ref"});let a=(n.boxes||[]).find(f=>f.name===r);if(!a||a.agent!=="claude"||!a.cwd||!a.sessionId)return e.status(404).json({error:"image source not available"});let i=ie(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=uE.createInterface({input:Ot.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let f of u){if(!f.includes(c))continue;let p;try{p=JSON.parse(f)}catch{continue}if(p.uuid===o){l=p,u.close();break}}let d=l?Ca(l)[s]:null;if(!d||!/^image\//.test(d.mediaType))return e.status(404).json({error:"image not found"});e.set("content-type",d.mediaType),e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.send(Buffer.from(d.data,"base64"))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tracks/:id/interrupt",async(t,e)=>{try{e.json(await rl(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:500).json({error:r})}});S.delete("/api/tracks/:id/queue",(t,e)=>{e.json({ok:!0,removed:Xs(t.params.id).filter(n=>n.kind!=="ship-notify")})});S.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await el(t.params.id,{promptId:n,answers:r,text:o}))}catch(s){let a=s?.message||String(s),i=/not found/i.test(a)?404:/no pending prompt|changed since|not running|not supported|needs a code|look like a sign-in code/i.test(a)?409:500;e.status(i).json({error:a})}});S.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await Oh({provider:t.params.provider,fresh:t.body?.fresh===!0});e.json({ok:!0,track:n})}catch(n){let r=n?.message||String(n);e.status(/unknown setup provider|isn't available/i.test(r)?400:500).json({error:r})}});S.post("/api/tracks/:id/navigate-prompt",async(t,e)=>{let{promptId:n,questionIndex:r}=t.body||{};try{e.json(await gh(t.params.id,{promptId:n,questionIndex:r}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/no pending prompt|changed since|not running|not supported|out of range|review screen/i.test(s)?409:500;e.status(a).json({error:s})}});S.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n}=t.body||{};try{e.json(await Sh(t.params.id,typeof n=="string"?n:""))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required/i.test(o)?400:500;e.status(s).json({error:o})}});S.get("/api/tracks/:id/usage",async(t,e)=>{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=n.boxes?.find(s=>s.name===n.leadBox)||null,o=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Sw(r,{fresh:o}))});S.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await vh(t.params.id,n))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|unknown model|not running/i.test(o)?400:500;e.status(s).json({error:o})}});S.post("/api/tracks/:id/subtask",async(t,e)=>{let{agent:n,task:r,skipPermissions:o,reason:s}=t.body||{};try{let a=await qh(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Eo(a)})}catch(a){let i=a?.message||String(a),c=/not found/i.test(i)?404:/required|unknown agent|git repo|not running|can't resolve/i.test(i)?400:500;e.status(c).json({error:i})}});S.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:gr(t.params.id).map(Eo)})});S.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await wh(t.params.id,t.params.sid);if(!n)return e.status(404).json({error:"subtask not found"});e.json(n)});S.post("/api/tracks/:id/subtask/:sid/cancel",(t,e)=>{try{e.json({ok:!0,subtask:Eo(ul(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});S.post("/api/tracks/:id/subtask/:sid/merge-now",(t,e)=>{try{e.json({ok:!0,subtask:Eo(Kh(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Eo(t){return t?{id:t.id,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,step:t.step,running:!t.finishedAt,box:t.box,branch:t.branch,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt}:null}S.post("/api/tracks/:id/handoff-lead",async(t,e)=>{let{toAgent:n,skipPermissions:r,reason:o}=t.body||{};try{let s=await il(t.params.id,n,r!==!1,o);e.json({ok:!0,step:s.step,toAgent:s.toAgent,fromBox:s.fromBox,skipPermissions:s.skipPermissions})}catch(s){let a=s?.message||String(s);e.status(/already running/i.test(a)?409:/not found/i.test(a)?404:400).json({error:a})}});S.post("/api/tracks/:id/spin-up-view",async(t,e)=>{try{e.json(await xh(t.params.id,t.body||{}))}catch(n){let r=n?.message||String(n);e.status(/already being spun up/i.test(r)?409:/not found/i.test(r)?404:400).json({error:r})}});S.get("/api/tracks/:id/handoff-lead",(t,e)=>{e.json(mg(po(t.params.id))??{running:!1})});function mg(t){return t?{running:!t.finishedAt,step:t.step,error:t.error,fromBox:t.fromBox,toBox:t.toBox,toAgent:t.toAgent,skipPermissions:t.skipPermissions,startedAt:t.startedAt,finishedAt:t.finishedAt}:null}S.get("/api/debug/handoff",async(t,e)=>{let n=Math.min(Number(t.query.limit)||80,500);e.json({verbose:Oo(),paths:Jl,trace:await Vl({limit:n})})});var jl=ct.resolve(dg,"..","..","client","dist");Ot.existsSync(jl)&&(S.use(ae.static(jl)),S.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ct.join(jl,"index.html"))));var hg=fE.createServer(S),yE=new mE({noServer:!0}),wg=(t,e,n)=>{if(!Ar.allowed(Ar.clientIp(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
275
302
|
\r
|
|
276
|
-
`),e.destroy();return}if(
|
|
303
|
+
`),e.destroy();return}if(Iw(t,e,n))return;let r=ru(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
277
304
|
\r
|
|
278
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
305
|
+
`),e.destroy();return}let o=r.sessionName;if(!pt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
279
306
|
\r
|
|
280
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
307
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;yE.handleUpgrade(t,e,n,i=>tu(i,o,{cols:s,rows:a}))};hg.on("upgrade",wg);var gg=process.env.BOSTRAT_CERT_DIR||ct.join(process.env.HOME||Ll.tmpdir(),".bostrat","certs"),cg=Number(process.env.BOSTRAT_TLS_PORT)||3443,lg=ct.join(gg,"bostrat.globopi.key"),ug=ct.join(gg,"bostrat.globopi.crt");if(Ot.existsSync(lg)&&Ot.existsSync(ug)){let t=pE.createServer({key:Ot.readFileSync(lg),cert:Ot.readFileSync(ug)},S);t.on("upgrade",wg),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(cg,ma,()=>{console.log(`[bostrat-agent] https listening on ${ma||"0.0.0.0"}:${cg}`)})}hg.listen(ig,ma,()=>{console.log(`[bostrat-agent] listening on ${ma||"0.0.0.0"}:${ig}`),console.log(`[bostrat-agent] tmux socket: ${Ct}`),console.log(`[bostrat-agent] auto-trigger default: ${fg}%`),console.log(`[bostrat-agent] ip allowlist: ${Ar.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),Hh().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),Wh().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),jh().then(t=>t&&console.log(`[bostrat-agent] reaped ${t} orphaned box(es) on archived track(s)`)).catch(t=>console.error("[bostrat-agent] archived-orphan sweep failed:",t?.message||t)),$h().then(t=>console.log(`[bostrat-agent] standing instructions refreshed (${t.refreshed} updated, ${t.removed} legacy CLAUDE.md removed${t.codexGlobal?`, codex global ${t.codexGlobal}`:""})`)).catch(t=>console.error("[bostrat-agent] standing-instructions refresh failed:",t?.message||t)),SE(),xE(),Nw(),sf({resolveTrack:nt}),nw({listTracks:ft,createTrack:ol,archiveTrack:wr,listRepos:pe,resolveTrack:nt,sayToLead:ge,answerPrompt:el,startLeadHandoff:il,interruptLead:rl,listSubtaskJobs:gr,cancelSubtask:ul,readEvents:F,appendEvent:C,knownAgents:Qc}),bE(),Ir().then(t=>{t||console.warn(`[boot] no host tmux server on ${Ct} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),zw(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(cw({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Ll.hostname(),name:process.env.BOSTRAT_NODE_NAME||Ll.hostname(),listTracks:ft}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function bE(){kE(),Zo().catch(()=>{}),setInterval(()=>Zo().catch(()=>{}),1e4)}function kE(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","gh"])try{let e=ct.resolve(dg,"..","bin",t),n=ct.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Ot.existsSync(e))continue;Ot.mkdirSync(n,{recursive:!0});let r=ct.join(n,t);(Ot.existsSync(r)?Ot.readlinkSync(r):null)!==e&&(Ot.rmSync(r,{force:!0}),Ot.symlinkSync(e,r)),console.log(`[bostrat-agent] ${t} CLI linked at ${r}`)}catch(e){console.error(`[bostrat-agent] ${t} CLI link failed:`,e?.message||e)}}function xE(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ft(),r=await yw({tracks:n});r.committed&&console.log(`[state-sync] committed (${r.staged} file(s) staged${r.pushed?", pushed":""})`)}catch{}finally{t=!1}}};setInterval(e,6e4),e()}function SE(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ft();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await tl(n.id).catch(()=>{}),await Ym(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|