bostrat-node 0.0.1 → 0.2.0
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/LICENSE.md +18 -0
- package/README.md +37 -0
- package/bin/bostrat-git-credential +46 -0
- package/bin/bostrat-mock +118 -0
- package/bin/bostrat-node.js +12 -0
- package/bin/bostrat-runbook +126 -0
- package/bin/bostrat-ticket +137 -0
- package/bin/bostrat-view +96 -0
- package/bin/gh +51 -0
- package/dist/cli.js +162 -0
- package/dist/index.js +265 -0
- package/node-entrypoint.sh +21 -0
- package/package.json +19 -5
- package/cli.js +0 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
var Bh=Object.defineProperty;var Hc=(t,e)=>{for(var n in e)Bh(t,n,{get:e[n],enumerable:!0})};import ct from"node:path";import Et from"node:fs";import qT from"node:readline";import{fileURLToPath as GT}from"node:url";import fe from"express";import KT from"node:http";import VT from"node:https";import Fc from"node:os";import{WebSocketServer as JT}from"ws";import{execFile as Kh}from"node:child_process";import{promisify as Vh}from"node:util";import Xc from"node:fs";import{randomUUID as Jh}from"node:crypto";import fn from"node:fs/promises";import Wc from"node:os";import Le from"node:path";function Xr(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function Dh(){return process.env.CLAUDE_CONFIG_DIR?Le.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Le.join(Wc.homedir(),".claude.json")}function jh(){return process.env.CLAUDE_CONFIG_DIR?Le.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Le.join(Wc.homedir(),".claude","settings.json")}async function pe(t){if(!t||typeof t!="string")return;let e=Le.resolve(t),n=Dh(),r=jh(),o={};try{o=JSON.parse(await fn.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 fn.mkdir(Le.dirname(n),{recursive:!0}),await fn.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await fn.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 fn.mkdir(Le.dirname(r),{recursive:!0}),await fn.writeFile(r,JSON.stringify(i,null,2))}import Rs from"node:fs/promises";import Lh from"node:os";import Yr from"node:path";function Uh(){let t=process.env.CODEX_HOME||Yr.join(Lh.homedir(),".codex");return Yr.join(t,"config.toml")}function qc(t){return`[projects."${t}"]`}function Fh(t,e){let n=qc(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 pn(t){if(!t||typeof t!="string")return;let e=Yr.resolve(t),n=Uh(),r="";try{r=await Rs.readFile(n,"utf8")}catch{}if(Fh(r,e))return;let o=qc(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
|
+
trust_level = "trusted"
|
|
3
|
+
`;r=r.slice(0,a)+d+r.slice(l)}else{let s=`
|
|
4
|
+
${o}
|
|
5
|
+
trust_level = "trusted"
|
|
6
|
+
`;r=r.trimEnd()+s+(r.length&&!r.endsWith(`
|
|
7
|
+
`)?`
|
|
8
|
+
`:"")}try{await Rs.mkdir(Yr.dirname(n),{recursive:!0}),await Rs.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Gc from"node:fs/promises";import Hh from"node:os";import er from"node:path";function Wh(t){let n=er.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||er.join(Hh.homedir(),".cursor");return er.join(r,"projects",n)}async function mn(t){if(!t||typeof t!="string")return;let e=er.resolve(t),n=Wh(e),r=er.join(n,".workspace-trusted");await Gc.mkdir(n,{recursive:!0}),await Gc.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
9
|
+
`,"utf8")}import nr from"node:fs/promises";import qh from"node:os";import Vc from"node:path";var $s=process.env.BOSTRAT_DEBUG_DIR||Vc.join(qh.homedir(),".bostrat","debug"),hn=Vc.join($s,"handoff.jsonl"),Kc=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Zr(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function Gh(t){try{await nr.mkdir($s,{recursive:!0}),await nr.appendFile(hn,`${JSON.stringify(t)}
|
|
10
|
+
`,"utf8");let n=(await nr.readFile(hn,"utf8")).split(`
|
|
11
|
+
`).filter(Boolean);n.length>Kc&&await nr.writeFile(hn,`${n.slice(-Kc).join(`
|
|
12
|
+
`)}
|
|
13
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${hn}: ${e?.message||e}`)}}async function j(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&&Zr())try{e.paneTail=(await pt(e.sessionName,{lines:35})).slice(-4e3)}catch{e.paneTail=null}(Zr()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await Gh(e)}async function Jc({limit:t=80}={}){try{return(await nr.readFile(hn,"utf8")).split(`
|
|
14
|
+
`).filter(Boolean).slice(-t).map(r=>{try{return JSON.parse(r)}catch{return{raw:r}}})}catch{return[]}}var zc={DEBUG_DIR:$s,LOG_FILE:hn};var et=Vh(Kh);function rr(t){return new Promise(e=>setTimeout(e,t))}function Os(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 O=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",Ht=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",zh=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",Cs=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",Ms="bostrat-usage-probe",Xh=new Set([Ms]);function Ns(t,e){let n=wn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function wn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function gn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function Ps(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 et(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function Bs(t){return Ps(String(t||"").trim().split(/\s+/)[0])}function C(...t){return["-S",Ht,...t]}async function ee(){if(!Xc.existsSync(Ht))return!1;try{return await et(O,C("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}var Yh=/^[A-Za-z0-9_.-]{1,64}$/;function ut(t){return typeof t=="string"&&Yh.test(t)}async function yn(){if(!Xc.existsSync(Ht))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 et(O,C("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("|"),w=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(w)?n-w*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:d||null,agent:f||(l==="1"?"claude":null)}}).filter(r=>!Xh.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function q(t){return(await yn()).find(n=>n.name===t)||null}var Zh=/^[A-Za-z0-9 ._-]+$/,Qh=64;function tw(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=Qh&&Zh.test(e)}function Ds(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"")}async function ew(t){try{let{stdout:e}=await et(O,C("list-sessions","-F","#{session_name}"));return e.split(`
|
|
16
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function Ue(t,e=zh,n={}){if(!tw(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Ds(r);if(!ut(o))throw new Error("resulting tmux session name is invalid");if(!await ee())throw new Error(`host tmux server not running on ${Ht} \u2014 start it with: systemctl --user start globo-tmux.service`);if(await ew(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||Cs,a=n.agent||gn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||Jh(),l=i?s:Ns(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),d=a==="claude"&&wn(s),f=n.firstRun===!0;f||(d&&await pe(e),a==="codex"&&await pn(e),a==="cursor"&&await mn(e));let w=d&&!f?Xr():null,h=w?["-e",`ANTHROPIC_API_KEY=${w}`]:[];try{await et(O,C("new-session","-d","-s",o,"-c",e,...h))}catch(g){throw w?Os(g,w):g}return await Fe(o),await et(O,C("set-option","-t",o,"@bostrat-session","1")),await et(O,C("set-option","-t",o,"@bostrat-title",r)),await et(O,C("set-option","-t",o,"@bostrat-sid",c)),await et(O,C("set-option","-t",o,"@bostrat-agent",a)),await et(O,C("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u}}async function Wt(t){if(!ut(t))throw new Error("invalid session name");await et(O,C("kill-session","-t",t))}async function Yc(t,e){if(ut(t))try{let n=e?C("set-option","-t",t,"@bostrat-sid",e):C("set-option","-u","-t",t,"@bostrat-sid");await et(O,n)}catch{}}async function pt(t,{lines:e=40}={}){if(!ut(t))return"";try{let n=C("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await et(O,n);return String(r||"")}catch{return""}}var nw=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,rw=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,It=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,ow=Number(process.env.BOSTRAT_BOX_COLS)||120,sw=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function aw(t){if(!ut(t))return 0;try{let{stdout:e}=await et(O,C("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
17
|
+
`).filter(Boolean).length}catch{return 0}}async function Fe(t,{cols:e=ow,rows:n=sw}={}){if(ut(t)&&!await aw(t))try{let r=await Qc(t);if(r.cols===e&&r.rows===n)return;await et(O,C("resize-window","-t",t,"-x",String(e),"-y",String(n))),await et(O,C("set-window-option","-t",t,"window-size","latest")),await rr(80)}catch{}}async function Zc(t){if(!ut(t))return 0;try{let{stdout:e}=await et(O,C("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function Qc(t){if(!ut(t))return{cols:0,rows:0};try{let{stdout:e}=await et(O,C("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 Qr(t,{cols:e=nw,rows:n=rw}={}){if(ut(t))try{let r=await Qc(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await et(O,C("resize-window","-t",t,"-x",String(o),"-y",String(s))),await rr(80))}catch{}}async function me(t){return await Qr(t),pt(t,{lines:It})}var iw=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function gt(t,e,{settleMs:n=40,kind:r}={}){if(!ut(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>iw.has(s));for(let s=0;s<o.length;s++)await et(O,C("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await rr(n);return j({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function to(t,e,{kind:n}={}){if(!ut(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await et(O,C("send-keys","-t",t,"-N",String(r),"BSpace")),j({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function eo(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!ut(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await et(O,C("send-keys","-t",t,"-l",s)),n>0&&await rr(n);j({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function yt(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!ut(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await et(O,C("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await rr(s),await et(O,C("send-keys","-t",t,"Enter"))}j({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 cw from"node-pty";async function el(t,e,n={}){if(!ut(e)){no(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await ee()){no(t,`host tmux server not running on ${Ht}`),t.close(1011,"tmux server unavailable");return}if(!await q(e)){no(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=tl(n.cols,120,nl),o=tl(n.rows,32,rl),s;try{s=cw.spawn(O,["-S",Ht,"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&&js(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(js(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"&&lw(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{}Fe(e).catch(()=>{})})}var nl=20,rl=6;function lw(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=nl&&Number(e)>=rl}function tl(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function js(t,e){try{t.send(JSON.stringify(e))}catch{}}function no(t,e){js(t,{type:"error",message:e})}function ol(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 or from"node:fs";import uw from"node:fs/promises";import bn from"node:path";import dw from"node:os";var sl={"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},fw=2e5,Ls=process.env.BOSTRAT_CLAUDE_PROJECTS||bn.join(dw.homedir(),".claude","projects");function Us(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Fs(t){let e=bn.join(Ls,Us(t)),n;try{n=or.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=bn.join(e,o);try{let{mtimeMs:a}=or.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function ne(t,e){if(!e)return null;let n=bn.join(Ls,Us(t),`${e}.jsonl`);return or.existsSync(n)?n:null}function al(t){if(!t)return[];let e=bn.join(Ls,Us(t)),n;try{n=or.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=or.statSync(bn.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function St(t,e=131072){let n=await uw.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 pw(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 sr(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=ne(t,e),o=!0,!r)return null}else if(n){if(r=Fs(t),o=!1,!r)return null}else return null;let s;try{s=pw(await St(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(sl,s.model),i=sl[s.model]||fw;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 mw(t,e){return!t||!e?null:ne(t,e)}function hw(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 ww(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 gw(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Hs(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 il(t,e){if(!t.uuid)return null;let n=Hs(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 yw=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Ws(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await St(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 g=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof g=="string"&&yw.test(g)&&s.set(h.id,g.split("/").pop())}if(gw(l.content)){let h=il(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let d=hw(l.content),f=u==="assistant"?ww(l.content):null,w=u==="user"?il(c,s):null;!d&&!f&&!w||o.push({role:u,text:d,tools:f||void 0,images:w||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var bw=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function cl(t,e,{turns:n=16}={}){let r=mw(t,e);if(!r)return"";let o=await Ws(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}:** ${bw(s.text||c,700)} ${i}`.trim()}).join(`
|
|
21
|
+
|
|
22
|
+
`):""}import Dw from"node:fs";import Zs from"node:fs/promises";import io from"node:path";import kw from"node:fs/promises";import{execFile as xw}from"node:child_process";import{promisify as Sw}from"node:util";var qs=Sw(xw),vw=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,ll=new Set,ul=Promise.resolve(),Gs=null;function fl(t){Gs=typeof t=="function"?t:null}function Tw(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function pl({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||ll.has(r)||(ll.add(r),ul=ul.then(()=>Aw({sessionName:t,from:e,to:r})).catch(()=>{}))}async function Aw({sessionName:t,from:e,to:n}){await j({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await qs("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await kw.realpath(r),s=Tw(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await qs("npm",["install","-g","@openai/codex"],{env:a,timeout:vw});let i=(await qs(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await j({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await dl({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await j({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await dl({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function dl(t){if(Gs)try{await Gs(t)}catch{}}var _w=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Ks=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,ar=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,Rt={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,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/}},Ew=/Trust this workspace|\[a\]\s*Trust/,Iw=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function Rw(t){let e=String(t||"");if(!Rt.codex.block.test(e))return null;let n=e.match(Iw);return{from:n?.[1]||null,to:n?.[2]||null}}function he(t){return new Promise(e=>setTimeout(e,t))}var $w=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,ro=new Map;function ml(t,e){let n=Date.now(),r=$w();for(let[s,a]of ro)n-a>=r&&ro.delete(s);let o=`${t}::${e}`;return ro.has(o)?!1:(ro.set(o,n),!0)}var Ow=68,Cw=/^\s*[─━]{10,}\s*$/;function Mw(t){let e=0;for(let n of String(t).split(`
|
|
23
|
+
`))Cw.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Vs(t,e){let n=Rt[t];if(!n?.busy)return"idle";if(Js(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e))return"busy";let r=Mw(e);return t==="claude"&&r>0&&r<Ow?"unknown":"idle"}var Nw=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function wl(t,e){return t==="claude"&&Nw.test(String(e||""))}function ir(t,e){return Vs(t,e)==="busy"}function oo(t,e){return Vs(t,e)!=="idle"}function so(t,e){let n=Rt[t];return n?.up?n.up.test(e):!0}function Pw(t,e){let n=Rt[t];return n?Js(t,e)?!1:n.up.test(e)&&Vs(t,e)==="idle":!0}function Js(t,e){let n=Rt[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 hl(t,e,n){let r=Date.now()+n,o=0;for(await Fe(t).catch(()=>{});;){let s=await pt(t);if(Pw(e,s)){await he(ar);return}if(e==="cursor"&&Ew.test(s)&&!Rt.cursor.up.test(s)&&o<3){if(!ml(t,"cursor-trust")){await he(Ks);continue}o+=1,await j({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(ar);continue}if(e==="codex"&&o<3){let a=Rw(s);if(a){if(!ml(t,`codex-update:${a.from}->${a.to}`)){await he(Ks);continue}o+=1,await j({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"}),pl({sessionName:t,from:a.from,to:a.to}),await he(ar);continue}}if(Date.now()>r){if(Js(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=Rt.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"&&Rt.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"),j({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.`),j({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!Rt[e]?.block?.test(s),paneUp:!!Rt[e]?.up?.test(s),paneBusy:!!Rt[e]?.busy?.test(s)}).catch(()=>{}),await he(ar);return}await he(Ks)}}function gl({agent:t,agentCmd:e}={}){let n=e?gn(e):gn(t||"");return{label:n,realClaude:e?wn(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 ao({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=gl({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=_w()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=gl({agent:r,agentCmd:o});if(i&&t&&await pe(t),c&&t&&await pn(t),l&&t&&await mn(t),(i||c||l)&&n){await hl(n,a,s);return}let u=Rt[r]?r:Rt[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await hl(n,u,s);return}await he(ar)}var Bw=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function re(t){return t==="codex"?{enter:!0,submitDelayMs:Bw}:{enter:!0}}var yl=".bostrat/handoff",bl="latest.md",kl="Checkpoint: write a work-in-progress handoff",xl="HANDOFF WRITTEN",Sl="Resume from .bostrat/handoff/latest.md",jw="/clear",Lw=t=>`${kl} 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 ${xl} and take no further action.`,Uw=`${Sl}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`;function Fw(t,e){let n=(e||"").trim();return n?t==="assistant"?n===xl:n.startsWith(kl)||n===jw||n.startsWith(Sl):!1}var Hw=6e5;function vl(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-Hw;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 Tl(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 Al(t,{windows:e=[]}={}){if(Fw(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 Ww=3500,qw=3e5,zs=new Map;function co(t){return zs.get(t)||null}async function _l(t){let e=zs.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await q(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:io.join(n.cwd,yl,bl)};return zs.set(t,r),Gw(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function Gw(t,e){t.step="summarize",await El(t.name,e),t.step="clear";let n=await q(t.name);await Ys(t.name,n,"/clear"),await Rl(Ww),t.step="resume",await Ys(t.name,n,Uw),t.step="done",t.finishedAt=Date.now()}async function El(t,e){let n=io.join(e,yl),r=io.join(n,bl);await Zs.mkdir(n,{recursive:!0});let o=await Xs(r),s=await q(t);return await j({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ys(t,s,Lw(r)),await Kw(r,o,qw),await j({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Xs(r)}),await Vw(n,r),r}async function Il(t){let e=await q(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return El(t,e.cwd)}async function Xs(t){try{return(await Zs.stat(t)).mtimeMs}catch{return 0}}async function Kw(t,e,n){let r=Date.now()+n;for(;;){if(await Xs(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Rl(2e3)}}async function Vw(t,e){try{if(!Dw.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Zs.copyFile(e,io.join(t,`${n}.md`))}catch{}}function Rl(t){return new Promise(e=>setTimeout(e,t))}async function Ys(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 q(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await yt(t,n,{kind:"orchestration-drive",...re(r.agent)})}import D from"node:fs/promises";import G from"node:path";import{randomUUID as Xo}from"node:crypto";import{execFile as Ex}from"node:child_process";import{promisify as Ix}from"node:util";import Qs from"node:fs/promises";import Jw from"node:path";var zw=0;async function qt(t,e,{mode:n}={}){await Qs.mkdir(Jw.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++zw}.tmp`;await Qs.writeFile(r,JSON.stringify(e,null,2)+`
|
|
24
|
+
`,n!=null?{mode:n}:void 0),await Qs.rename(r,t)}import{execFile as Xw}from"node:child_process";import{promisify as Yw}from"node:util";import{randomUUID as Zw}from"node:crypto";var He=Yw(Xw),lo=t=>new Promise(e=>setTimeout(e,t)),Qw=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,$l=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,tg=250,eg=400,Ol={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 Ml(t){let e=await Zc(t);if(!e)return!1;try{return await He("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function ta(t,e){let n=Date.now()+e;for(;;){if(await Ml(t))return!0;if(Date.now()>n)return!1;await lo(tg)}}async function ng(t,e){let n=Ol[e]||Ol.default;n.escape&&(await gt(t,["Escape"],{kind:"swap-exit"}),await lo(150)),n.wipe&&await to(t,400,{kind:"swap-exit"}),await eo(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=re(e).submitDelayMs||0;await lo(Math.max(r,250)),await gt(t,["Enter"],{kind:"swap-exit"})}async function rg(t,e){try{let{stdout:n}=await He(O,C("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Cl(t){await He(O,C("respawn-pane","-k","-t",t))}async function ea(t,e={}){if(!ut(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await q(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||gn(n),a=Number(e.exitTimeoutMs)||Qw;await j({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await Ml(t)&&(await ng(t,o),!await ta(t,a)&&(i=!0,await j({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Cl(t),!await ta(t,$l))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&wn(n),l=c?Xr():null,u=await rg(t,"ANTHROPIC_API_KEY"),d=!1;try{l?(await He(O,C("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),d=!0):u&&(await He(O,C("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),d=!0)}catch(x){throw l?Os(x,l):x}if(d&&!i&&(await Cl(t),!await ta(t,$l)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let f=!!e.resumeSessionId,w=e.resumeSessionId||Zw(),h=f?n:Ns(n,w),g=f?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await pe(r.cwd),s==="codex"&&await pn(r.cwd),s==="cursor"&&await mn(r.cwd),await Fe(t),await He(O,C("set-option","-t",t,"@bostrat-sid",w)),await He(O,C("set-option","-t",t,"@bostrat-agent",s)),await lo(eg),await yt(t,h,{kind:"swap-launch"}),await j({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:w,forced:i,envRespawned:d}),{name:t,cwd:r.cwd,sessionId:w,agent:s,pinned:g,forced:i,envRespawned:d}}import mg from"node:fs";import Pl from"node:fs/promises";import cr from"node:path";import hg from"node:os";import{fileURLToPath as wg}from"node:url";import{execFile as gg}from"node:child_process";import{promisify as yg}from"node:util";import ra from"node:fs/promises";import ge from"node:path";import{execFile as og}from"node:child_process";import{promisify as sg}from"node:util";var ag=sg(og),Nl=process.env.HOME||"/home/globo",ig=ge.join(process.env.BOSTRAT_STATE_DIR||ge.join(Nl,".bostrat"),"repos"),cg=[...(process.env.BOSTRAT_REPOS_ROOT||ge.join(Nl,"Documents")).split(","),ig].map(t=>t.trim()).filter(Boolean),lg=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,ug=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function dg(t){try{return(await ra.stat(ge.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function fg(t){let e=i=>ra.readFile(ge.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 oe(){let t=new Map;async function e(n,r){if(t.has(n))return;let o=await dg(n);if(o==="worktree")return;if(o==="repo"){t.set(n,{path:n,name:ge.basename(n),branch:await fg(n)});return}if(r>=lg)return;let s;try{s=await ra.readdir(n,{withFileTypes:!0})}catch{return}for(let a of s)!a.isDirectory()||a.name.startsWith(".")||ug.has(a.name)||await e(ge.join(n,a.name),r+1)}for(let n of cg)await e(ge.resolve(n),0);return[...t.values()].sort((n,r)=>n.name.localeCompare(r.name))}function pg(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var na=new Map;async function Z(t){if(!t)return null;if(na.has(t))return na.get(t);let e=null;try{let{stdout:n}=await ag("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=pg(n)}catch{e=null}return na.set(t,e),e}var Bl=yg(gg),bg=process.env.BOSTRAT_STATE_DIR||cr.join(process.env.HOME||hg.tmpdir(),".bostrat"),kg=cr.join(bg,"repos"),Dl=/^[^/\s]+\/[^/\s]+$/,xg=5*6e4,Sg=6e4,vg=10*6e4,jl=fetch,oa=new Map,We=null;function sa(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 uo(t=process.env){return!!(sa(t)&&t.BOSTRAT_PLANE_TOKEN)}var Ll=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),kn=(t,e)=>Object.assign(new Error(t),{status:e});async function fo(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Dl.test(n))throw kn("repo must be owner/name",400);let r=oa.get(n);if(r&&r.expiresAt-Date.now()>xg)return r;if(!uo(e))throw kn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await jl(`${sa(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Ll(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw kn(`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 kn(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 oa.set(n,a),a}async function lr({env:t=process.env,force:e=!1}={}){if(!uo(t))return{configured:!1,repos:[],username:null};if(!e&&We&&Date.now()-We.at<Sg)return We.data;try{let n=await jl(`${sa(t)}/v1/github/repos`,{headers:Ll(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return We={at:Date.now(),data:r},r}catch(n){return We?We.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Ul(){oa.clear(),We=null}async function Gt({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Z(t):null);if(!r)return{};let{token:o}=await fo(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function aa({env:t=process.env}={}){return(await lr({env:t})).username??null}var Tg=()=>wg(new URL("../bin/bostrat-git-credential",import.meta.url)),Ag=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function _g(t){let[e,n]=String(t).split("/");return cr.join(kg,e,n)}async function ia(t,{env:e=process.env}={}){let n=(...r)=>Bl("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Tg()}" ${Ag(e)}`),await n("config","credential.useHttpPath","true")}async function Fl(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Dl.test(String(t||"")))throw kn(`bad repo slug "${t}"`,400);let r=_g(t);if(mg.existsSync(cr.join(r,".git")))return r;let{token:o}=await fo(t,{env:e});await Pl.mkdir(cr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Bl("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:vg,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await Pl.rm(r,{recursive:!0,force:!0}).catch(()=>{}),kn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await ia(r,{env:e}),r}import sy from"node:fs/promises";function Eg(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===`
|
|
25
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function ur(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 dr(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 Sn=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,bt=t=>t&&!/[$`]/.test(t)?t:null,st=t=>t.replace(/\/+$/,"").split("/").pop()||t,xn=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},ql=t=>t.startsWith("-"),Hl=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function V(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(ql(o)){e.has(o)&&r++;continue}n.push(o)}return n}var Ig={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 ${st(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"},Rg={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 Gl(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 $g(t){let e=Gl(t),n=bt(V(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${st(n)}`:`Read lines ${e.from}\u2013${e.to} of ${st(n)}`:t.includes("-i")&&n?`Edit ${st(n)}`:null}function ca(t){if(t.includes("--files")){let s=bt(V(t,Hl)[0]);return s&&s!=="."?`List files in ${xn(s)}`:"List files"}let e=V(t,Hl),n=e[0];if(!n)return null;let r=bt(e.slice(1).find(s=>s!==".")),o=Sn(n,40);return r?`Search for \u201C${o}\u201D in ${xn(r)}`:`Search for \u201C${o}\u201D`}function la(t){let e=V(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 ua(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=V(t)[0];return n?`Run ${st(n)}`:null}function Og(t){let e=V(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 Wl(t){return t.find(e=>/^https?:\/\//.test(e))||null}var Cg={git:t=>{let e=V(t,new Set(["-C","-c"])),n=Ig[e[0]];return n?n(e,t):null},gh:t=>{let e=V(t),n=Rg[e[0]];return n?n(e):null},sed:$g,rg:ca,grep:ca,egrep:ca,nl:t=>{let e=bt(V(t)[0]);return e?`Read ${st(e)}`:null},cat:t=>{let e=bt(V(t)[0]);return e?`Read ${st(e)}`:null},head:t=>{let e=bt(V(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${st(e)}`:null},tail:t=>{let e=bt(V(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${st(e)}`:null},wc:t=>{let e=bt(V(t)[0]);return e?`Count lines in ${st(e)}`:"Count input lines"},ls:t=>{let e=bt(V(t)[0]);return e&&e!=="."?`List files in ${xn(e)}`:"List files"},find:t=>bt(t[1])&&!ql(t[1])&&t[1]!=="."?`Find files in ${xn(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=bt(V(t)[0]);return e?`Create directory ${xn(e)}`:null},touch:t=>{let e=bt(V(t)[0]);return e?`Create ${st(e)}`:null},rm:t=>{let e=bt(V(t)[0]);return e?`Delete ${xn(e)}`:null},mv:t=>{let e=bt(V(t)[0]);return e?`Move ${st(e)}`:null},cp:t=>{let e=bt(V(t)[0]);return e?`Copy ${st(e)}`:null},chmod:t=>{let e=bt(V(t)[1]);return e?`Change permissions on ${st(e)}`:null},diff:t=>{let e=V(t);return e.length>=2?`Compare ${st(e[0])} and ${st(e[1])}`:null},curl:t=>{let e=Wl(t);return e?`Fetch ${Sn(e,56)}`:"Fetch a URL"},wget:t=>{let e=Wl(t);return e?`Fetch ${Sn(e,56)}`:"Fetch a URL"},npm:la,pnpm:la,yarn:la,npx:t=>{let e=V(t)[0];return e?`Run ${e}`:null},node:t=>ua(t,"Node"),python:t=>ua(t,"Python"),python3:t=>ua(t,"Python"),make:t=>{let e=V(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:Og,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=V(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=V(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=V(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},Mg=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function Ng(t){let e=dr(ur(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(st(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return da(e[2]);let n=st(e[0]);if(Mg.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=Cg[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function da(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
26
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=Eg(e);for(let i=0;i<r.length-1;i++){let c=dr(ur(r[i])),l=dr(ur(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(st(c[0]))&&st(l[0])==="sed"){let u=Gl(l),d=V(c)[0];if(u&&d)return Sn(u.from===u.to?`Read line ${u.from} of ${st(d)}`:`Read lines ${u.from}\u2013${u.to} of ${st(d)}`,88)}}let o=[];for(let i of r){let c=Ng(i);c&&o.push({d:c,head:st(dr(ur(i))[0]||""),sub:V(dr(ur(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":Sn(s?s.d:o[0].d,88)}var pa=new Set(["Bash"]);function vn(t){let e=t.split(`
|
|
27
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function ye(t,e=""){let n=e.trim();return n||da(t)||vn(t)}function fa(t,e){if(t){if(Array.isArray(t)){for(let n of t)fa(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&pa.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))fa(n,e)}}}async function Kl(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await St(t,e)}catch{return[]}let o=[];for(let s of r.split(`
|
|
28
|
+
`)){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(fa(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let d of l)o.push({...d,summary:ye(d.command,d.description),ts:u})}return o.slice(-n)}function Vl(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,g)=>g>=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 w=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:w,ts:Math.max(...w.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 Pg=/^Async (?:agent|task) launched/i,Bg=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 Jl(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await St(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
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?vn(w):null),detail:w?vn(w):null,ts:d,proven:!0})}}else f.type==="tool_result"&&f.tool_use_id&&Pg.test(Bg(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 fr from"node:fs";import jg from"node:fs/promises";import qe from"node:path";import Lg from"node:os";import Ug from"node:crypto";import Fg from"node:zlib";var po=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),jt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Tn=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 ma=t=>`"${String(t).replace(/"/g,'\\"')}"`;function Dg(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 zl(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:jt(`read ${be(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${Tn(r)}`:`Read ${Tn(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?be(e.path,n):null,s=e?.glob?` --glob ${ma(e.glob)}`:"",a=jt(String(r),40);return{command:jt(`grep ${ma(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:jt(`glob ${ma(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${jt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:jt(`edit ${be(e.path,n)}`,140),description:`Edit ${Tn(e.path)}`}:null;case"Write":return e?.path?{command:jt(`write ${be(e.path,n)}`,140),description:`Write ${Tn(e.path)}`}:null;case"Delete":return e?.path?{command:jt(`delete ${be(e.path,n)}`,140),description:`Delete ${Tn(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>be(s,n)).filter(Boolean);return{command:jt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=Dg(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:jt(`apply_patch ${be(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${Tn(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:jt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var Hg=process.env.BOSTRAT_CURSOR_CHATS||qe.join(Lg.homedir(),".cursor","chats");function Zl(t){let e=Ug.createHash("md5").update(String(t)).digest("hex");return qe.join(Hg,e)}function Ql(t){try{let e=JSON.parse(fr.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 Wg(t){let e=Zl(t),n;try{n=fr.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(!fr.existsSync(a))continue;let i=Ql(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 mo(t,e){if(!t)return null;if(e){let n=qe.join(Zl(t),e),r=qe.join(n,"store.db");if(fr.existsSync(r))return{id:e,dir:n,store:r,...Ql(n)||{createdAtMs:null,updatedAtMs:null}}}return Wg(t)}function Xl(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=Fg.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function Gg(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 Kg(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!po.has(r.toolName))continue;let o=zl(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 Vg=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
|
+
`).trim():"",Jg=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Yl=process.getBuiltinModule?.("node:sqlite"),An={turns:[],trailingCommands:[],model:null,requestIds:[]};async function ha(t,e){if(!Yl)return An;let{DatabaseSync:n}=Yl,r;try{r=new n(t,{readOnly:!0})}catch{return An}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 An;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return An}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Xl(r,s.latestRootBlobId):null;if(!i)return{...An,model:a};let c=[],l=[],u=[],d=null,f=[];for(let w of Gg(i)){let h=qg(Xl(r,w));if(!h)continue;let g=h.providerOptions?.cursor;if(g?.requestId&&f.push(g.requestId),g?.modelProviderMessageId&&f.push(g.modelProviderMessageId),h.role!=="assistant")continue;if(Array.isArray(h.content))for(let S of h.content){let v=S?.providerOptions?.cursor?.modelName;v&&(d=v)}l.push(...Kg(h.content,e).map((S,v)=>({...S,id:S.id||`${w}:${v}`}))),u.push(...Jg(h.content));let x=Vg(h.content);if(!x)continue;let A=[...new Set(u)];u=[],c.push({id:w,text:x,tools:A.length?A:void 0,commands:l,model:d}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:f}}catch{return An}finally{try{r.close()}catch{}}}function tu(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(fr.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 eu(t,e,{limit:n=80}={}){let r=mo(t,e);if(!r)return[];let{turns:o,model:s}=await ha(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=tu(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 nu(t,e){let n=mo(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await ha(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 ru(t,e,{limit:n=80}={}){let r=mo(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await ha(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=tu(r,a.length||1),l=[];a.forEach((u,d)=>{for(let f of u.commands)l.push({...f,summary:ye(f.command,f.description),ts:c(d)})});for(let u of s)l.push({...u,summary:ye(u.command,u.description),ts:i+1});return l}async function ou(t,e){let n=mo(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await jg.stat(n.store)).mtimeMs)}catch{return null}}import su from"node:fs";import ga from"node:fs/promises";import wa from"node:path";import zg from"node:os";var Xg=process.env.BOSTRAT_CODEX_SESSIONS||wa.join(process.env.CODEX_HOME||wa.join(zg.homedir(),".codex"),"sessions");async function au(t){try{return await ga.realpath(t)}catch{return t}}function Yg(t=Xg){let e=[],n=r=>{let o;try{o=su.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=wa.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:su.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function Zg(t,e=1048576){let n;try{n=await ga.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(`
|
|
31
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function Qg(t){let e=await Zg(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=Yg();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await au(t);for(let o of n){let s=await Qg(o.file);if(s&&(s===t||await au(s)===r))return o}return null}var ty=new Set(["output_text","text"]),ey=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&ty.has(e.type)).map(e=>e.text).join(`
|
|
32
|
+
`).trim():"";async function iu(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Ge(t,e);if(!o)return[];let s;try{s=await St(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 h;try{h=JSON.parse(w)}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 S=h.payload?.type;S==="task_started"?(l=!0,u=!1,d=h.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&h.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:ba,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 g=h.payload;if(!g)continue;if((g.type==="function_call"||g.type==="custom_tool_call")&&g.name){u=!0,ya.has(g.name)||c.push(g.name);continue}if(g.type!=="message"||g.role!=="assistant")continue;u=!0;let x=ey(g.content),A=c.length?c.slice():null;c=[],!(!x&&!A)&&a.push({role:"assistant",text:x,tools:A||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 pr=new Set(["exec_command","shell","exec"]),ya=new Set(["wait","write_stdin"]),ba="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 ny(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 ry(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 oy(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=ry(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function cu(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Ge(t,e);if(!o)return[];let s;try{s=await St(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"&&pr.has(u.name)){let w;try{w=JSON.parse(u.arguments||"{}")}catch{continue}let h=ny(w);h&&(d=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(d=oy(u.input));if(!d.length)continue;let f=u.call_id||u.id||null;d.forEach((w,h)=>{a.push({id:f?h?`${f}:${h}`: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 lu(t,e){let n=await Ge(t,e);if(!n)return null;try{return Math.round((await ga.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var _n=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}},ka=class extends _n{transcript(e){return!e?.cwd||!e?.sessionId?null:ne(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Ws(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Kl(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Jl(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await sy.stat(n)).mtimeMs)}catch{return null}}},xa=class extends _n{async readMessages(e,n){return eu(e.cwd,e.sessionId,n)}async readCommands(e,n){return ru(e.cwd,e.sessionId,n)}async updatedAt(e){return ou(e.cwd,e.sessionId)}},Sa=class extends _n{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return iu(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return cu(e.cwd,this.sessionId(e),n)}async updatedAt(e){return lu(e.cwd,this.sessionId(e))}},ay=new _n("unknown"),uu=new Map;function va(t){return uu.set(t.agent,t),t}function mr(t){return uu.get(t)||ay}va(new ka("claude",pa));va(new xa("cursor",po));va(new Sa("codex",pr));var Ta=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function du(t,e={}){return(await mr(t.agent).readMessages(t,e)).map(Ta(t))}async function fu(t,e={}){return(await mr(t.agent).readCommands(t,e)).map(Ta(t))}async function pu(t,e={}){return(await mr(t.agent).readJobs(t,e)).map(Ta(t))}var iy=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,mu=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function cy(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
36
|
+
`);if(!mu.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(iy);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"&&mu.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 ho=/(^|\n)\s*›/,hu=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function ly(t,e=""){let n=String(t);if(!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n)||ho.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(hu);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(hu.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 wu=/Enter this one-time code/,uy=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function dy(t,e=""){let n=String(t);if(!wu.test(n)||ho.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
38
|
+
`),o=r.findIndex(i=>wu.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(uy);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 fy(t,e=""){let n=String(t);return!/open the following link to authenticate/.test(n)||ho.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 py(t,e=""){let n=String(t);return!/Paste or type your API key/.test(n)||ho.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 gu(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=dy(e,n)||fy(e,n)||py(e,n)||ly(e,n);if(r)return r}return cy(e,{provider:t,box:n})}function yu(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 xu=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Ea=/Chat about this/i,En=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,bu=/^(type something|chat about this|submit)\b/i,In=/^[─—-]{6,}\s*$/,Ia=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Rn(t){let e=t.search(Ia);return(e>=0?t.slice(0,e):t).trim()}function my(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function hy(t){let e=String(t).trim();return!!(!e||In.test(t)||Ia.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function wy(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=gy(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function gy(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 ku(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Su=/^\s*Switch model\?\s*$/m;function yy(t,e=""){let n=String(t);if(!Su.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(En);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&&!In.test(r[u])&&a.push(f)}}if(s.length<2)return null;let i=s.map(u=>({label:Rn(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}]}}function by(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
40
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(En);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 Aa=/Resume from summary/i,ky=/^\s*This session is\b/;function xy(t,e=""){let n=String(t);if(!Aa.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
41
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(ky.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(En);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&&!In.test(r[u])&&a.push(f)}}if(s.length<2||!s.some(u=>Aa.test(u.label)))return null;let i=s.map(u=>({label:Rn(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 Ke=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function Ra(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(En);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 vu=/Choose the text style that looks best with your terminal/,Tu=/Let's get started\./;function Sy(t,e=""){let n=String(t);if(!vu.test(n)||!Tu.test(n)||Ke.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
42
|
+
`),o=Ra(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:Rn(s.label),description:""}))}]}:null}var Au=/Select login method:/;function vy(t,e=""){let n=String(t);if(!Au.test(n)||Ke.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
43
|
+
`),o=Ra(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Rn(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 $a=/Paste code here if prompted/,Ty=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function Ay(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!Ty.test(o)||$a.test(t[r]))break;n+=o}return n}function _y(t,e=""){let n=String(t);if(!$a.test(n)||Ke.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
44
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=Ay(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 _a=/OAuth error:/,Ey=/Press Enter to retry\./;function Iy(t,e=""){let n=String(t);if(!_a.test(n)||!Ey.test(n)||Ke.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
45
|
+
`)].reverse().find(a=>_a.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 _u=/Quick safety check: Is this a project you created or one you trust/;function Ry(t,e=""){let n=String(t);if(!_u.test(n)||Ke.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
46
|
+
`),o=Ra(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:Rn(s.label),description:""}))}]}:null}var Eu=/^\s*Press Enter to continue\s*$/;function $y(t,e=""){let n=String(t);if(Ke.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
47
|
+
`),o=-1;for(let i=r.length-1;i>=0;i--)if(r[i].trim()!==""){o=i;break}if(o<0||!Eu.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(In.test(r[i])||Ia.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 Oy(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Ea.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 Cy(t,e=""){let n=String(t);if(!xu.test(n)||!Ea.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
48
|
+
`),o=Oy(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let m=i;m<c;m++){let p=r[m].match(En);p&&(u<0&&(u=m),l.push({line:m,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 m of l)for(let p=m.line+1;p<Math.min(r.length,m.line+4)&&!(En.test(r[p])||In.test(r[p]));p++){let b=r[p].trim();if(!(!b||hy(r[p])||bu.test(b))){m.desc=my(b);break}}let d=l.filter(m=>!bu.test(m.label));if(!d.length)return null;let f=d.some(m=>m.hasCheckbox),w=Math.max(0,d.findIndex(m=>m.cursor)),h=s>=0?s+1:0,g=[];for(let m=h;m<u;m++){let p=r[m].trim();p&&!In.test(r[m])&&!/[☐☒]/.test(r[m])&&g.push(p)}let x=g.join(" ")||(s>=0?ku(r[s]):"")||"The agent is asking a question",A=s>=0?wy(r[s],x):null,S=A?.currentTabLabel||(s>=0?ku(r[s]):""),v=d.map(m=>({label:Rn(m.label),description:m.desc})),E=v.map(m=>m.label).join("|"),I=`claude:${A?`q${A.questionIndex+1}/${A.questionTotal}`:"q1/1"}:${S}:${x}:${E}`.slice(0,240),M=A?`claude:ask:${A.tabs.map(m=>m.label).join("|")}`.slice(0,240):I;return{provider:"claude",box:e,multiSelect:f,cursorIndex:w,promptId:I,groupId:M,...A?{questionIndex:A.questionIndex,questionTotal:A.questionTotal,currentTabLabel:A.currentTabLabel,tabs:A.tabs}:{},questions:[{question:x,header:S,multiSelect:f,options:v}]}}function $n(t,e){let n=String(e||"");return n?t==="claude"?xu.test(n)&&Ea.test(n)||Su.test(n)||Aa.test(n)?!0:Ke.test(n)?!1:vu.test(n)&&Tu.test(n)||Au.test(n)||$a.test(n)||_a.test(n)||_u.test(n)||Eu.test(n.trimEnd().split(`
|
|
49
|
+
`).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 Ve(t,e){return!t||!e?null:t.agent==="claude"?by(e,t.name)||Cy(e,t.name)||yy(e,t.name)||xy(e,t.name)||Iy(e,t.name)||_y(e,t.name)||vy(e,t.name)||Sy(e,t.name)||Ry(e,t.name)||$y(e,t.name):t.agent==="cursor"||t.agent==="codex"?gu(t.agent,e,t.name):null}async function Oa(t){if(!t)return null;let e=await me(t.name).catch(()=>"");return Ve(t,e)}function Iu(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 Ru(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 On(t){return typeof t=="string"?t.trim():""}var $u=/^\s*─{8,}\s*$/,My=/^❯[ ]?(.*)$/;function Ca(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
50
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if($u.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if($u.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(My);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 Je(t){return String(t||"").replace(/\s+/g," ").trim()}function Ou(t,e){let n=Je(t),r=Je(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import{execFile as Ny}from"node:child_process";import{promisify as Py}from"node:util";var Na=Py(Ny),Cu=64*1024*1024;async function mt(t,e,{input:n}={}){let r=Na("git",["-C",t,...e],{maxBuffer:Cu});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Pa(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 Mu(t){let{stdout:e}=await mt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function By(t){try{return await mt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Nu(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)=>Na("git",["-C",t,...a],{maxBuffer:Cu,env:o,...i});try{let a=await By(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 Na("rm",["-f",r])}catch{}}}async function Pu(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 Ba(t,e,n,r){return await mt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function Bu(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 Du(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 hr(t,e){try{await mt(t,["worktree","remove","--force",e])}catch{try{await mt(t,["worktree","prune"])}catch{}}}function Dy(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 Da(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function ju(t,e,n,r){let{stdout:o}=await mt(t,Da(["diff","--shortstat",e,n],r));return Dy(o)}async function Lu(t,e,n,r){let{stdout:o}=await mt(t,Da(["diff","--name-only",e,n],r));return o.split(`
|
|
51
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Uu(t,e,n,r){let{stdout:o}=await mt(t,Da(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await Ma(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await Ma(t,o,["--3way","--whitespace=nowarn"]),a=await jy(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await Ma(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function Ma(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 jy(t){try{let{stdout:e}=await mt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
52
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import Ly from"node:fs/promises";import Uy from"node:os";import Fu from"node:path";var wo=t=>new Promise(e=>setTimeout(e,t)),Fy=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,Hy=300,Gu=[{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"}],Wy=new Set([...Gu.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),qy=[{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"}],Ku=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Vu=[{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 Ju(t){return t==="codex"?qy:t==="cursor"?Vu:Gu}function Hu(t){let e=String(t||"").split(`
|
|
53
|
+
`),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 Wu(t){let e=String(t||"").split(`
|
|
54
|
+
`),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 Gy(t){let e=String(t||"").split(`
|
|
55
|
+
`);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 qu(t){let e=String(t||"").split(`
|
|
56
|
+
`),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 zu(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&&!Ku.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Vu.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!Wy.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function wr(t,e,{timeoutMs:n=Fy}={}){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 wo(Hy)}}async function Ky(t,e){return await yt(t,`/model ${e.model}`,re("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function Vy(t,e){await Qr(t),await yt(t,"/model",{enter:!1}),await wo(250),await gt(t,["Enter"],{kind:"model-switch"});let n=await wr(t,Hu);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=Gy(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 wr(t,i=>Wu(i)||!Hu(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?Ku.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 wr(t,i=>!Wu(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 Jy(){let t=Fu.join(process.env.CURSOR_CONFIG_DIR||Fu.join(Uy.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await Ly.readFile(t,"utf8"))?.model||null}catch{return null}}async function zy(t,e){await Qr(t),await eo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await wr(t,qu),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 wr(t,qu,{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 wo(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 wo(800);let a=await Jy();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 Xu(t,e,n){return t==="codex"?Vy(e,n):t==="cursor"?zy(e,n):Ky(e,n)}import ja from"node:fs/promises";import Cn from"node:path";import Xy from"node:os";import{EventEmitter as Yy}from"node:events";import{randomUUID as Zy}from"node:crypto";var Qy=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"]),tb=new Set(["user","agent","orchestrator","system"]),eb=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Mt=new Yy;Mt.setMaxListeners(0);var La=process.env.BOSTRAT_STATE_DIR||Cn.join(process.env.HOME||Xy.tmpdir(),".bostrat");function yo(t){return t?.cwd?Cn.join(t.cwd,".bostrat","events"):Cn.join(La,"events")}var Yu=(t,e)=>Cn.join(t,`${e}.ndjson`),go=new Map;async function nb(t,e){if(!go.has(e)){let o=(await P(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);go.set(e,o)}let n=go.get(e)+1;return go.set(e,n),n}function rb(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||!Qy.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!tb.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!eb.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Cn.join(La,"events"):yo(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=rb(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await nb(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||Zy(),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 ja.mkdir(r,{recursive:!0}),await ja.appendFile(Yu(r,n),JSON.stringify(i)+`
|
|
57
|
+
`),Mt.emit("event",i),Mt.emit(`track:${n}`,i),i}async function P(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?Cn.join(La,"events"):yo(t));let o;try{o=await ja.readFile(Yu(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
58
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function Zu(t,e){let n=`track:${t}`;return Mt.on(n,e),()=>Mt.off(n,e)}var $t=t=>t||null;function ko(t){let e=t||{},n=ob(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function ob(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:$t(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:$t(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:$t(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:$t(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:$t(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:$t(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:$t(t.agent),payload:{...gr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:$t(t.agent||t.toAgent),payload:{kind:"subtask-failed",...gr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:$t(t.agent),payload:{change:"subtask-resumed",...gr(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",...gr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...gr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:$t(t.agent),payload:{change:"rehydrated"}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:$t(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var bo=Promise.resolve(),Fa=t=>(bo=bo.then(t).catch(()=>{}),bo),td=()=>bo;function ed(t,e){let n=ko(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Fa(()=>R(r,n))}var Qu=new Set;async function nd(t){if(!(!t?.id||Qu.has(t.id))){Qu.add(t.id);try{let e=await P(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=ko(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function gr(t){let{type:e,at:n,...r}=t;return r}var Ua=new Map;function rd(t,e,n){!n?.promptId||Ua.get(t.id)===n.promptId||(Ua.set(t.id,n.promptId),Fa(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:$t(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 od(t,e,n,r={}){Ua.delete(t.id),Fa(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:$t(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as Mb}from"node:crypto";var sd=/(?<!\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,ad=/(?<!\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,id=/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,sb=new RegExp(`\\b(?:${sd}|${ad}|${id})\\b`,"i"),ab=/\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,ib=/^(?:commit|push|open|create|raise|ready|ok(?:ay)?\s+to|good\s+to)\b/i,cb=90;function lb(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function cd(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of lb(n).reverse()){if(!sb.test(r))continue;let o=r.endsWith("?")&&r.length<=cb&&ib.test(r);if(ab.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var ld=["commit","push","pr"],ub=new RegExp(`\\b(?:${sd})\\b`,"i"),db=new RegExp(`\\b(?:${ad})\\b`,"i"),fb=new RegExp(`\\b(?:${id})\\b`,"i"),pb=/\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 ud(t){let e=String(t||""),n=[];ub.test(e)&&n.push("commit"),db.test(e)&&n.push("push");let r=pb.test(e);!r&&fb.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 xo(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 mb=3e4;function dd(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<=mb&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function fd(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 gb}from"node:child_process";import{promisify as yb}from"node:util";import So from"node:fs/promises";import pd from"node:path";import hb from"node:os";var md=process.env.BOSTRAT_STATE_DIR||pd.join(process.env.HOME||hb.tmpdir(),".bostrat"),Wa=pd.join(md,"pr-links.json"),kt=(t,e)=>`${t}#${Number(e)}`,vo=null,Ha=null,L=Promise.resolve();async function vt(){return vo||(Ha??=So.readFile(Wa,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>vo=t).finally(()=>Ha=null),Ha)}async function nt(){let t=JSON.stringify({links:vo},null,2),e=`${Wa}.${process.pid}.tmp`;await So.mkdir(md,{recursive:!0}),await So.writeFile(e,t),await So.rename(e,Wa)}var wb=new Set(["api","observed","reconciled"]);async function Nt({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 vt(),c=kt(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:wb.has(a),firstSeen:Date.now()},L=L.then(nt,nt),await L.catch(()=>{}),{link:i[c],created:!0})}async function yr(t,e){if(!t||e==null)return null;let n=(await vt())[kt(t,e)];return!n||n.announced?null:(n.announced=!0,L=L.then(nt,nt),await L.catch(()=>{}),n)}async function Pt(t,e){return!t||e==null?null:(await vt())[kt(t,e)]??null}async function ze(t,e,n){if(!t||e==null)return null;let r=(await vt())[kt(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,L=L.then(nt,nt),await L.catch(()=>{}),r):null}async function hd(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await vt())[kt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},L=L.then(nt,nt),await L.catch(()=>{}),o):null}async function wd(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await vt())[kt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},L=L.then(nt,nt),await L.catch(()=>{}),o):null}async function To(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await vt())[kt(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,L=L.then(nt,nt),await L.catch(()=>{}),r)}async function Ao(t,e){if(!t||e==null)return null;let n=(await vt())[kt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,L=L.then(nt,nt),await L.catch(()=>{}),n)}async function _o(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await vt())[kt(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,L=L.then(nt,nt),await L.catch(()=>{}),r)}async function Eo(t,e){if(!t||e==null)return null;let n=(await vt())[kt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,L=L.then(nt,nt),await L.catch(()=>{}),n)}async function gd(t,e,n){if(!t||e==null||!n)return null;let r=(await vt())[kt(t,e)];return r?(r.lastHeadSha=n,L=L.then(nt,nt),await L.catch(()=>{}),r):null}async function Io(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await vt())[kt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),L=L.then(nt,nt),await L.catch(()=>{}),r):null}async function Ro(t,e){if(!t||e==null)return null;let n=(await vt())[kt(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,L=L.then(nt,nt),await L.catch(()=>{}),n)}async function yd(){return Object.values(await vt()).filter(t=>t.state!=="merged"&&t.state!=="closed")}async function qa(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await pt(t,{lines:It}).catch(()=>"");if(!ir(e,r))return wl(e,r)?{working:!0,prompted:!1}:n;if($n(e,r)){let o=await me(t).catch(()=>r);if(Ve({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function $o(t,e){return(await qa(t,e)).working}var bd=yb(gb),kd=[4e3,15e3,45e3],bb=2500,kb=5*6e4;async function Ga(t,e,{midTurn:n=$o,pollMs:r=bb,capMs:o=kb}={}){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 xb=6e4,Sb=9e4,vb=2e3,Tb=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,Ab=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,_b=/\s--web\b/,xd=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Ka(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function Sd(t){let e=String(t||""),n=e.match(Tb);if(!n)return null;let r=Ka(e,n.index);return _b.test(r)?null:{repo:r.match(xd)?.[1]??null}}var Eb=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function vd(t){let e=String(t||""),n=e.match(Ab);if(!n)return null;let r=Ka(e,n.index),o=Ka(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("-")){Eb.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(xd)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var Ib="number,url,title,headRefName,createdAt";async function Rb(t,e){let n=async s=>{let{stdout:a}=await bd("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",Ib],{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-xb).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function $b(t,e){if(e)return[e];let n=[],r=await Z(t.workspace?.base||t.cwd).catch(()=>null);r&&n.push(r);for(let o of await oe().catch(()=>[])){let s=await Z(o.path).catch(()=>null);s&&!n.includes(s)&&n.push(s)}return n}async function Ob(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await $b(t,e)){let a;try{a=await Rb(s,n)}catch{continue}for(let i of a){let c=await Pt(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 Td(t,e,{delays:n=kd,turnEnd:r}={}){let o=Sd(e.command);if(!o)return null;let s=!!vd(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 Ob(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:w,created:h}=await Nt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!h&&!(w?.trackId===t.id&&await yr(d,f.number))?null:(await Ga(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 Cb="number,url,title,headRefName,state";function Ad(t,e,{delays:n=kd,turnEnd:r,openedWait:o}={}){let s=vd(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 Z(t.workspace?.base||t.cwd).catch(()=>null),l||=t.workspace?.branch,!c||!l)return null;let d=async(w,h)=>{let g=await Pt(c,w.number);return g&&g.trackId!==t.id?null:(g||await Nt({repo:c,number:w.number,trackId:t.id,url:w.url,headRef:w.headRefName,title:w.title,source:"merge-observed"}),await Io(c,w.number),h&&((await P(t).catch(()=>[])).some(S=>S.type==="pr.state.changed"&&(S.payload?.change==="auto-armed"||S.payload?.change==="merged")&&S.payload?.number===w.number)||(await Ga(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(v=>setTimeout(v,w));let h;try{let{stdout:v}=await bd("gh",["pr","view",String(l),"--repo",c,"--json",Cb],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await Gt({slug:c})}});h=JSON.parse(v||"null")}catch{continue}if(!h?.number)continue;if(h.state==="CLOSED")return null;if(h.state!=="MERGED"){if(f=h,s.auto)return d(h,!0);continue}let g=await Pt(c,h.number);if(g&&g.trackId!==t.id)return null;g||await Nt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await Ga(e.box,e.agent,r);let x=await P(t).catch(()=>[]);if(x.some(v=>v.type==="pr.state.changed"&&v.payload?.change==="merged"&&v.payload?.number===h.number))return null;let A=v=>v.some(E=>E.type==="pr.state.changed"&&E.payload?.change==="opened"&&E.payload?.number===h.number),S=0;if(!A(x)){if(Sd(e.command)&&await yr(c,h.number)){let v=Date.now();S=v+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:v,change:"opened",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c,selfMerge:!0}})}else if((await Pt(c,h.number))?.announced){let v=o?.capMs??Sb,E=o?.pollMs??vb,J=Date.now()+v;for(;Date.now()<J&&(await new Promise(I=>setTimeout(I,E)),!A(await P(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:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}),await ze(c,h.number,"merged"),h}return f?d(f,!1):null}finally{ke.delete(a)}})();return i.catch(()=>ke.delete(a)),i}function Ja(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function _d(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Ed(t){return`job|${t.box}|id:${t.id}`}var Va=new Map,Oo=new Map;async function za(t){return Va.has(t.id)?Va.get(t.id):(Oo.has(t.id)||Oo.set(t.id,P(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(Ja({role:o,sourceId:r.payload.sourceId})),n.keys.add(Ja({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(_d({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(Ed({id:r.payload.id,box:r.boxId}));return Va.set(t.id,n),n}).finally(()=>Oo.delete(t.id))),Oo.get(t.id))}async function Id(t,e){try{let n=(await za(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Ja(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=cd(r.text);if(s){let{offered:a,toPr:i}=ud(s);await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:Mb(),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 Rd(t,e){try{let n=await za(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=_d(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}}),Td(t,o),Ad(t,o))}}catch{}}async function $d(t,e){try{let n=await za(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Ed(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 Nb=/^Base directory for this skill:\s*(\S+)/,Pb=/^Run the "([^"]+)" workflow\./,Bb=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,Db=/^<local-command-(?:stdout|caveat)>/i,jb=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,Lb=/^<task-notification>/i,Ub=/<summary>([\s\S]*?)<\/summary>/i,Fb=/<task-id>([\s\S]*?)<\/task-id>/i,Hb=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,Wb=/<status>([\s\S]*?)<\/status>/i,qb=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Gb=new Set(["model","clear"]);function Co(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(Nb);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(Pb);if(r)return{kind:"skill",name:r[1]};let o=e.match(Bb);if(o){let s=o[1].toLowerCase();return Gb.has(s)?{kind:"suppress"}:{kind:"command",command:`/${s}`}}if(Db.test(e))return{kind:"suppress"};if(jb.test(e))return{kind:"suppress"};if(Lb.test(e)){let s=i=>Kb(e.match(i)?.[1].trim()||"")||null,a=s(qb);return{kind:"task",summary:s(Ub)||"",taskId:s(Fb),toolUseId:s(Hb),status:s(Wb)?.toLowerCase()||null,resultHead:a?a.length>600?`${a.slice(0,600).trimEnd()}\u2026`:a:null}}return null}function Kb(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Od(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 Cd(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var at=t=>t.payload?.ts??t.ts;function Mo(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 m of t||[]){if(m.type!=="tool.called"||m.source!=="transcript")continue;let p=String(m.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 T=b[0].split("/").pop().toLowerCase();d.has(T)||d.set(T,[]),d.get(T).push(m.payload?.ts??m.ts)}}let f=3*6e4,w=20*6e4,h=(m,p)=>!!(m?.name&&d.get(String(m.name).toLowerCase()))?.some(T=>T>=p-f&&T<=p+w);for(let m of t||[])if(m.type==="turn.retracted"){r.add(`${m.boxId}|${m.payload?.saidTs}`);let p=Je(m.payload?.text);p&&(o.has(m.boxId)||o.set(m.boxId,[]),o.get(m.boxId).push(p))}else if(m.type==="turn.user"&&m.source==="orchestrator"){let p=Je(m.payload?.text);p&&(s.has(m.boxId)||s.set(m.boxId,new Set),s.get(m.boxId).add(p))}else if(m.type==="job.started"&&m.payload?.id)a.add(m.payload.id),(m.payload.kind||"agent")==="agent"&&m.payload.name&&c.add(m.payload.name);else if(m.type==="turn.user"&&m.payload?.note?.kind==="task"){let p=m.payload.note,b={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:at(m)};p.toolUseId&&i.set(p.toolUseId,b);let T=String(p.summary||"").match(u);T&&l.set(T[1],b)}let g=new Map,x=new Map,A=new Map,S=new Map,v=new Map,E=new Map,J=new Map,I=new Map,M=(m,p)=>{let b=o.get(m);if(!b?.length)return!1;let T=Je(p),ot=s.get(m);return b.some($=>{if(!T.startsWith($))return!1;let z=Je(T.slice($.length));return!!z&&!!ot?.has(z)})};for(let m of t||[]){let p=m.payload||{};switch(m.type){case"turn.user":{if(m.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let b=p.note||Co(p.text);if(b){if(b.kind==="task"){if(b.toolUseId&&a.has(b.toolUseId))break;let T=String(b.summary||"").match(u);if(!b.toolUseId&&T&&c.has(T[1]))break}b.kind!=="suppress"&&e.push({id:m.id,kind:"event",type:Cd(b),name:b.name??null,command:b.command??null,summary:b.summary??null,ts:at(m),box:m.boxId,agent:m.provider});break}}if(r.has(`${m.boxId}|${at(m)}`)||m.source==="transcript"&&M(m.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:m.id,kind:"message",role:"user",text:p.text,ts:at(m),box:m.boxId,agent:m.provider,boxRole:p.boxRole,...m.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=>!h(ot,at(m)));if(!String(p.text||"").trim()&&!b.length)break;if(m.provider==="cursor"){let ot=`${m.boxId}|${p.text}`;if(n.has(ot))break;n.add(ot)}let T=m.provider==="codex"?(p.tools||[]).filter(ot=>!ya.has(ot)&&!pr.has(ot)):p.tools||[];e.push({id:m.id,kind:"message",role:"assistant",text:p.text,ts:at(m),box:m.boxId,agent:m.provider,boxRole:p.boxRole,...T.length?{tools:T}:{},...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(m),T=i.get(p.id);if(!T&&(p.kind||"agent")==="agent"&&p.name){let N=l.get(p.name);N&&N.ts>=b&&(T=N)}let ot={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:T?T.status&&T.status!=="completed"?"failed":"done":"running",startedTs:b,finishedTs:T?.ts??null,summary:T?.summary??null,resultHead:T?.resultHead??null},$=I.get(m.boxId);if($&&$.jobs.some(N=>(N.finishedTs??1/0)>=b))$.jobs.push(ot);else{let N={id:m.id,kind:"job-dock",dockId:p.id,ts:b,box:m.boxId,agent:m.provider,jobs:[ot]};I.set(m.boxId,N),e.push(N)}break}case"handoff.done":e.push(xe(m,"lead-handoff"));break;case"subtask.delegated":e.push({...xe(m,"subtask-delegated"),to:m.boxId,toAgent:m.provider});break;case"subtask.merged":e.push(xe(m,"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(m,`pr-${p.change}`));break;case"pr.offered":{if(!p.offerId)break;for(let T of g.values())T.status="stale";g.clear();let b={id:m.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(m),box:m.boxId,agent:m.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 T=xe(m,b);b==="model-change"&&(T.to=p.model),e.push(T);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:m.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:at(m),box:m.boxId,agent:m.provider});break}case"view.spinup.failed":{e.push({id:m.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:at(m),box:m.boxId,agent:m.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:m.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:at(m),box:m.boxId,agent:m.provider});break}if(p.change!=="created")break;e.push({id:m.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:at(m),box:m.boxId,agent:m.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:m.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:at(m),box:m.boxId,agent:m.provider});break}case"ticket.linked":{e.push({id:m.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:at(m),box:m.boxId,agent:m.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let T of A.values())T.key===p.key&&(T.status="stale",A.delete(T.offerId));let b={id:m.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(m),box:m.boxId,agent:m.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(m,"ticket-state"));break}case"ticket.commented":{e.push({id:m.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:at(m),box:m.boxId,agent:m.provider});break}case"ticket.flow.changed":e.push(xe(m,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let T of v.values())T.status="stale";v.clear();let b={id:m.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(m),box:m.boxId,agent:m.provider};v.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,v.delete(p.proposalId));break}case"ticket.created":{e.push({id:m.id,kind:"ticket-created",items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null,ts:at(m),box:m.boxId,agent:m.provider});break}case"error":{if(!p.kind)break;e.push(xe(m,p.kind));break}case"provider.connected":{e.push({id:m.id,kind:"event",type:"provider-connected",provider:p.provider||m.provider,ts:at(m),at:at(m),box:m.boxId,agent:m.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let b=p.groupId||p.promptId;if(!b)break;let T=J.get(b);T||(T={id:m.id,kind:"prompt",groupId:b,provider:m.provider,box:m.boxId,agent:m.provider,questionTotal:p.questionTotal??1,questions:[],ts:at(m)},J.set(b,T));let ot=p.questionIndex??0,$={index:ot,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},z=T.questions.findIndex(H=>H.index===ot);z>=0?T.questions[z]=$:T.questions.push($),T.questions.sort((H,_)=>H.index-_.index),T.questionTotal=Math.max(T.questionTotal,p.questionTotal??1),new Set(T.questions.map(H=>H.index)).size>=T.questionTotal&&(e.push(T),J.delete(b));break}default:break}}return Jb(e.sort((m,p)=>(m.ts??0)-(p.ts??0)))}var Vb=2*6e4;function Jb(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<=Vb){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 Xa(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:zb(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??Mo(t)).filter(o=>o.kind==="message");return Vl(n,r)}function zb(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==vn(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 Ya={"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}},Md={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 Nd(t){if(!t)return null;let e=String(t).trim();return Ya[e]?e:Md[e]?Md[e]:Object.keys(Ya).find(r=>e.startsWith(r))||null}var No=t=>Nd(t)!==null;function Kt(t,e={}){let n=Ya[Nd(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 Xb(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
59
|
+
`)){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 Za=new Map;async function Yb(t,e){let n=`${t.id}:${e.name}`;if(!Za.has(n)){let r=await P(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);Za.set(n,o)}return Za.get(n)}async function Pd(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 Yb(t,e),o=Xb(await St(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 Po from"node:fs";import ti from"node:path";import Zb from"node:os";var Qb=process.env.BOSTRAT_STATE_DIR||ti.join(process.env.HOME||Zb.tmpdir(),".bostrat"),Dd=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,tk="https://cursor.com/api/dashboard/get-filtered-usage-events",Bd=100;async function ek({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(tk,{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:Bd})});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<Bd)break}return r}function nk(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 rk=5*6e4,Se={at:0,rows:null,error:null},Bo=()=>ti.join(Qb,"cursor-usage.json");function jd(){try{return JSON.parse(Po.readFileSync(Bo(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function ok(t){try{Po.mkdirSync(ti.dirname(Bo()),{recursive:!0});let e=Bo()+".tmp";Po.writeFileSync(e,JSON.stringify(t)),Po.renameSync(e,Bo())}catch{}}var sk=5e3;async function ak({fetchImpl:t,now:e=Date.now()}={}){if(Se.rows&&e-Se.at<rk)return Se.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=jd(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await ek({sinceMs:o,cookie:n,fetchImpl:t});Se={at:e,rows:s.map(nk).filter(Boolean),error:null}}catch(s){Se={at:e,rows:null,error:String(s?.message||s)}}return Se.rows}var Ld=()=>({enabled:Dd(),lastPollAt:Se.at||null,error:Se.error}),Qa=new Map;async function ik(t,e){let n=`${t.id}:${e.name}`;if(!Qa.has(n)){let r=await P(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);Qa.set(n,o)}return Qa.get(n)}async function Ud(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Dd())return;let r=await ak({fetchImpl:n});if(!r||!r.length)return;let o=await nu(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await ik(t,e),a=jd(),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}})}ok({sinceMs:c,seen:[...i].slice(-sk)})}catch{}}import kr from"node:fs/promises";import Tt from"node:path";import ck from"node:os";import lk from"node:net";import{randomUUID as uk}from"node:crypto";import{execFile as dk}from"node:child_process";import{promisify as fk}from"node:util";var Jt=fk(dk),pk=process.env.BOSTRAT_STATE_DIR||Tt.join(process.env.HOME||ck.tmpdir(),".bostrat"),Gd=Tt.join(pk,"views.json"),[Fd,Hd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),ve=Number(process.env.BOSTRAT_VIEW_MAX)||3,mk="bostrat-view-",Mn=t=>`${mk}${t}`,Wd=new Set(["bostrat","www","api","views","app"]),Do=t=>Object.assign(new Error(t),{status:400}),hk=new Set(["npm","pnpm","yarn","bun"]),ri="package.json",wk=/[|;&`]|\$\(/,gk=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),yk=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,Kd=t=>String(t).trim().split(/\s+/).filter(Boolean),bk=/^[A-Za-z_][A-Za-z0-9_]*=/;function kk(t){for(let e of Kd(t))if(!bk.test(e))return e;return null}function Vd(t){let e=Kd(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function xk(t,e){let n=Vd(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Tt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Tt.resolve(t,n[r+1])}return t}var Sk=t=>kr.stat(t).then(e=>e.isDirectory(),()=>!1),Jd=t=>kr.stat(t).then(e=>e.isFile(),()=>!1),jo=t=>kr.realpath(t).catch(()=>Tt.resolve(t));async function vk(t,e){if(Tt.resolve(t)===Tt.resolve(e))return!0;let[n,r]=await Promise.all([jo(t),jo(e)]);return n===r}async function Tk(t){let n=(await kr.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 Jd(Tt.join(t,o.name,ri))?`./${o.name}`:null))).filter(Boolean)}async function Ak(t,e){let[n,r]=await Promise.all([jo(t),jo(e)]),o=Tt.relative(n,r);if(o&&(o.startsWith("..")||Tt.isAbsolute(o)))throw Do(`serve directory escapes the Track workspace: ${e}`)}async function _k(t,e){if(!await Sk(t))throw Do(`serve directory does not exist: ${t}`);let n=kk(e);if(!n)return;if(!n.includes("/")&&!await Ps(n,{loginShell:!0,failOpen:!0}))throw Do(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(wk.test(e)||gk.has(n)||!hk.has(n)||!Vd(e).includes("run")||yk.test(e))return;let r=xk(t,e);if(await Jd(Tt.join(r,ri)))return;let o=await Tk(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Do(`no ${ri} in ${r}${s}`)}var Vt=null,ei=null;async function ie(){return Vt||(ei||=(async()=>{try{Vt=JSON.parse(await kr.readFile(Gd,"utf8"))}catch{Vt={views:[],adoptWatermarks:{}}}finally{ei=null}return Vt.views||=[],Vt.adoptWatermarks||={},Vt})(),ei)}async function zt(){await qt(Gd,Vt)}function ae(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 br=new Map,ai=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,zd=12e3;function ii(t){return br.get(t)||null}function Xd(t,{name:e,box:n}={}){let r=br.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 br.set(t,o),o}function oi(t,e){let n=br.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Lo(t,{error:e=null}={}){let n=br.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function Yd(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 si(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Ek(t,e){let n=si(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!Wd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!Wd.has(s))return s}}function xr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=lk.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 ci(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 Ik(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=Fd;r<=Hd;r++)if(!n.has(r)&&!await xr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${Fd}-${Hd}`)}async function Rk(t){try{let{stdout:e}=await Jt(O,C("list-sessions","-F","#{session_name}"));return e.split(`
|
|
60
|
+
`).some(n=>n.trim()===Mn(t))}catch{return!1}}async function Sr(t){try{await Jt(O,C("kill-session","-t",Mn(t)))}catch{}}async function li(t){if(!await ee())throw new Error("host tmux server not running \u2014 cannot spawn a view server");let e=Mn(t.slug),n=t.serveCwd??t.cwd,r=Tt.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 qd(t,e=12){try{let{stdout:n}=await Jt(O,C("capture-pane","-t",Mn(t),"-p"));return String(n||"").trimEnd().split(`
|
|
61
|
+
`).filter(Boolean).slice(-e).join(`
|
|
62
|
+
`)}catch{return""}}var se=new Map,$k=12e4;async function Ok(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Rk(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await qd(t.slug)||"server exited before listening"),se.delete(t.id),await ae(t,t.state,t.error?{error:t.error}:{}),!0}if(await xr(t.port))return se.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await ae(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>$k?(t.state="error",t.error=await qd(t.slug)||"never started listening",await ae(t,"error",{error:t.error}),!0):!1;let o=(se.get(t.id)||0)+1;return se.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var ni=!1;async function Uo(){if(!ni){ni=!0;try{let t=await ie(),e=!1;for(let n of t.views)await Ok(n)&&(e=!0);e&&await zt()}finally{ni=!1}}}async function Zd({trackId:t}={}){await Uo();let e=await ie();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Qd(t){let n=(await ie()).views.find(r=>r.id===t);return n?{...n}:null}function ui(t){return Vt&&Vt.views.find(e=>e.slug===t)||null}async function di({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=Tt.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Ak(r,u),await _k(u,n)},f=await ie();if(i!=="detected"){let g=si(e),x=String(n).trim(),A=f.views.find(S=>S.trackId===t&&(S.slug===g||si(S.name)===g||S.cmd===x&&Tt.resolve(S.serveCwd??S.cwd)===u));if(A)return Ck(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 h={id:uk().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Ek(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Ik(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await li(h),f.views.push(h),await zt(),await ae(h,"created",{origin:i}),oi(t,h.id),i==="detected"&&await xr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await zt(),await ae(h,"live")),{...h}}async function Ck(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 vk(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await xr(e.port))return await zt(),oi(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 ci(e.port),e.origin="agent"):await Sr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),se.delete(e.id),await li(e),await zt(),await ae(e,"created",{restarted:!0,reused:!0}),oi(e.trackId,e.id),{...e,reused:!0}}async function tf(t){let n=(await ie()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await ci(n.port):await Sr(n.slug),n.state!=="stopped"&&(n.state="stopped",se.delete(n.id),await zt(),await ae(n,"stopped")),{...n}}async function ef(t){let e=await ie(),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 Sr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),se.delete(n.id),await li(n),await zt(),await ae(n,"created",{restarted:!0}),{...n}}async function nf(t){let e=await ie(),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 ci(r.port):await Sr(r.slug),e.views.splice(n,1),se.delete(r.id),await zt(),await ae(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function rf(t){let e=await ie(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Sr(r.slug),se.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await zt()),n.length}var Mk=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function of(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await ie(),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(Mk)){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 xr(c))try{i.push(await di({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 Nk}from"node:child_process";import{promisify as Pk}from"node:util";var Wo=Pk(Nk),Bk={timeout:3e4,maxBuffer:8*1024*1024};async function Lt(t,e,n={}){let r=await Gt({cwd:t}),{stdout:o}=await Wo("gh",e,{cwd:t,...Bk,...n,env:{...process.env,...r,...n.env||{}}});return String(o||"")}var Fo=null;async function mi(){if(Fo&&Date.now()-Fo.at<6e4)return Fo.data;let t;try{let e=await Wo("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 lr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Fo={at:Date.now(),data:t},t}async function Dk(t){try{let{stdout:e}=await Wo("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function af(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 fi(t){let e=(t||[]).map(af);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 hi="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",cf=hi.replace("statusCheckRollup,","");function wi(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function lf(t,e){if(!e)return null;let n=await Z(t);if(!n)return null;try{let r=await Lt(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 uf(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 df(t,e){let n=await lf(t,e);return n===null?{...fi([]),unavailable:!0}:fi(uf(n))}function Ho(t,e=fi(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 jk=9e4,Nn=new Map;function Lk(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Nn.delete(s);continue}r.add(s),Nn.has(s)||Nn.set(s,n),o.mergeabilityPending=n-Nn.get(s)<jk}for(let o of Nn.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Nn.delete(o);return e}async function Tr(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 Lt(n.path,["pr","view",String(r),"--json",hi]);return Ho(JSON.parse(o))}catch(o){if(!wi(o))throw o;let s=await Lt(n.path,["pr","view",String(r),"--json",cf]),a=JSON.parse(s);return Ho(a,await df(n.path,a.headRefOid))}}var sf=(t,e)=>e.number-t.number;async function vr(t){try{let e=await Lt(t,["pr","list","--json",hi,"--limit","50"]);return JSON.parse(e||"[]").map(n=>Ho(n)).sort(sf)}catch(e){if(!wi(e))throw e;let n=await Lt(t,["pr","list","--json",cf,"--limit","50"]);return(await Promise.all(JSON.parse(n||"[]").map(async r=>Ho(r,await df(t,r.headRefOid))))).sort(sf)}}var Uk=2e4,Fk=1500,Pn=null;function Xe(){Pn=null}async function Hk(t,e){if(!t)return e;for(let n of e){let r=await Pt(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 To(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 ff(){let t=await mi();if(!t.authenticated)return{auth:t,repos:[]};let e=Pn?.pending?Fk:Uk;if(Pn&&Date.now()-Pn.at<e)return{auth:t,repos:Pn.data};let n=[];for(let a of await oe())await Dk(a.path)&&n.push(a);let r=Date.now(),o=await Promise.all(n.map(async a=>{try{let i=Lk(a.path,await vr(a.path),r);return await Hk(await Z(a.path).catch(()=>null),i),{name:a.name,path:a.path,prs:i}}catch(i){return{name:a.name,path:a.path,prs:[],error:ce(i)}}})),s=o.some(a=>a.prs.some(i=>i.mergeabilityPending||i.checks.pending>0||i.autoMergeArmed));return Pn={at:Date.now(),data:o,pending:s},{auth:t,repos:o}}async function Te(t){let e=(await oe()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function gi(t){if(!t)return null;for(let e of await oe())if(await Z(e.path).catch(()=>null)===t)return e.path;return null}var qo=new Set(["squash","merge","rebase"]);async function Ar(t,e,{method:n="squash"}={}){let r=await Te(t);if(!qo.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 aa().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Lt(r.path,s)}catch(a){throw new Error(ce(a))}finally{Xe()}return{ok:!0,number:o}}async function pf(t,e){let n=await Te(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Lt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ce(o))}finally{Xe()}return{ok:!0,number:r}}var Wk=2500,qk=9e4,Gk=t=>new Promise(e=>setTimeout(e,t));function mf(t){return t.green&&!t.isDraft}function Kk(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 pi=new Map;function hf(){let t={};for(let[e,n]of pi)t[e]={...n};return t}async function wf(t,{method:e="merge",onMerged:n=null}={}){let r=await Te(t);if(!qo.has(e))throw new Error(`invalid merge method: ${e}`);if(pi.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await vr(r.path)).filter(mf).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 pi.set(r.path,s),Jk(s,o,{listPrs:()=>vr(r.path),merge:a=>Ar(r.path,a,{method:e}),sleep:Gk,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function Vk(t,e){let n=Date.now()+(e.settleMaxMs??qk),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(Wk)}}async function Jk(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 Vk(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: ${ce(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(!mf(a)){t.skipped.push({number:a.number,title:a.title,reason:Kk(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:ce(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),Xe()}}async function gf(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Wo("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ce(l)}`)}let o=n||"";if((await Gt({cwd:t})).GH_TOKEN){let l=await aa().catch(()=>null);l&&(o=`${o?`${o}
|
|
63
|
+
|
|
64
|
+
`:""}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 Lt(t,s)}catch(l){throw new Error(ce(l))}finally{Xe()}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 yf(t,e){let n=await Te(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Lt(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(ce(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!wi(i))throw i;let c=await Lt(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await lf(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(ce(i));o=uf(l).map(af).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=zk(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Lt(n.path,["run","view",c,"--log-failed"]).then(u=>Xk(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function zk(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function Xk(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
65
|
+
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
66
|
+
`+o.slice(-e).join(`
|
|
67
|
+
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
68
|
+
`+r.slice(-n)),r}function ce(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
69
|
+
`).find(n=>n.trim())||"gh failed"}var Yk=6e3,Go=new Map;function yi(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 bf(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await P(t)}catch{return}if(yi(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await $o(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Go.get(t.id)||0)<Yk)return;Go.set(t.id,o);let s;try{s=await vr(t.cwd)}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await Z(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Nt({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 yr(i,a.number))){Go.delete(t.id);return}}await R(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(()=>{}),Go.delete(t.id)}import ht from"node:fs/promises";import At from"node:path";import _r from"node:os";import{randomUUID as Zk}from"node:crypto";import{execFile as Qk}from"node:child_process";import{promisify as tx}from"node:util";import{chromium as ex}from"playwright-core";var xf=tx(Qk),Sf=process.env.BOSTRAT_STATE_DIR||At.join(process.env.HOME||_r.tmpdir(),".bostrat"),vf=At.join(Sf,"mocks.json"),nx=process.env.BOSTRAT_MOCK_ENGINE||"chromium",rx=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",ox=process.env.BOSTRAT_FIREFOX_BIN||"firefox",sx=1300,Bn=2,ax=32,Tf=1500,Dn=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,ix=700*1024*1024;async function cx(){if(process.platform!=="darwin")return _r.freemem();try{let{stdout:t}=await xf("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:_r.freemem()}catch{return _r.freemem()}}var Ut=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
70
|
+
${n.trim().split(`
|
|
71
|
+
`).slice(-6).join(`
|
|
72
|
+
`)}`:e),this.name="MockRenderError"}},Ye=null,bi=null;async function le(){return Ye||(bi||=(async()=>{try{Ye=JSON.parse(await ht.readFile(vf,"utf8"))}catch{Ye={mocks:[]}}finally{bi=null}return Ye.mocks||=[],Ye})(),bi)}async function Xt(){await qt(vf,Ye)}function Ft(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 Ko=t=>At.join(t,".bostrat","mocks");function Bt(t,e){let n=Ko(t.cwd);return{html:At.join(n,`${t.id}.v${e}.html`),png:At.join(n,`${t.id}.v${e}.png`)}}function Ze(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 xi(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var lx=Buffer.from([137,80,78,71,13,10,26,10]);function Af(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(lx)}async function ux(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 dx({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 fx(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,g)=>{r.onload=h,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 h=0;h<s;h++)for(let g=h*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),h<d&&(d=h),w=h);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:w}}},e.toString("base64"))}async function px(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 mx({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await ex.launch({executablePath:rx,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Ut("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Bn})).newPage();a.setDefaultTimeout(Dn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Dn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Dn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await fx(a,i),u=l&&dx({...l,pad:ax*Bn,align:Bn});u&&(i=await px(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Tf)throw new Ut(`capture produced a ${i.length}-byte image`);let c=xi(i);if(!c)throw new Ut("output is not a PNG");return await ht.writeFile(e,i),{width:Math.round(c.width/Bn),height:Math.round(c.height/Bn),dpr:Bn,renderMs:Date.now()-r}}catch(s){throw s instanceof Ut?s:new Ut(/timeout/i.test(s?.message||"")?`render timed out after ${Dn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function kf({htmlPath:t,outPath:e,width:n}){let r=await ht.mkdtemp(At.join(_r.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await xf(ox,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Dn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Ut(c?.killed?`render timed out after ${Dn}ms`:"firefox exited non-zero",s)}let a=await ht.stat(e).catch(()=>null);if(!a||a.size<Tf)throw new Ut(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=xi(await ux(e));if(!i)throw new Ut("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 hx(t){if(nx==="firefox")return kf(t);try{return await mx(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return kf(t);throw e}}var wx=hx;var ki=Promise.resolve();function _f(t){return ki=ki.then(t,t),ki}async function Ef(t,e){let n=await le(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Xt(),await Ft(r,"rendering",{version:e});try{let o=await cx();if(o<ix)throw new Ut(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Bt(r,e),a=await wx({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}If(r,{v:e,source:"html",htmlRel:At.relative(r.cwd,s.html),pngRel:At.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Xt(),await Ft(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 Ft(r,"error",{version:e,error:r.error})}}}function If(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 gx({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?sx:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Rf({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 Ae(t){let n=(await le()).mocks.find(r=>r.id===t);return n?{...n}:null}async function $f({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=gx({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&&!Af(r))throw new Error("image must be a PNG");let f=await le(),w={id:Zk().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(Ko(o),{recursive:!0}),f.mocks.push(w),d?(await ht.writeFile(Bt(w,1).html,n),await Xt(),await Ft(w,"created"),_f(()=>Ef(w.id,1))):(await Cf(w,1,r),await Xt(),await Ft(w,"created"),await Ft(w,"ready",{version:1,width:w.width,height:w.height,dpr:w.dpr})),{...w}}async function Of(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&&!Af(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(Ko(s.cwd),{recursive:!0}),a?(s.state="queued",await ht.writeFile(Bt(s,i).html,e),await Xt(),await Ft(s,"updated",{version:i}),_f(()=>Ef(s.id,i))):(await Cf(s,i,n),await Xt(),await Ft(s,"updated",{version:i}),await Ft(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Cf(t,e,n){let r=xi(n);if(!r)throw new Error("image must be a PNG");let o=Bt(t,e);await ht.writeFile(o.png,n),If(t,{v:e,source:"image",htmlRel:null,pngRel:At.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Mf(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 Ft(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Nf(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=Bt(r,o);await ht.rm(s.html,{force:!0}).catch(()=>{}),await ht.rm(s.png,{force:!0}).catch(()=>{})}return await Xt(),await Ft(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Pf(t){let n=(await le()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=At.join(Sf,"mock-archive",t.id),o=Ko(t.cwd),s=At.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(At.join(o,i),At.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(At.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 Bf(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 tn from"node:fs/promises";import _e from"node:path";import yx from"node:os";import{randomUUID as bx}from"node:crypto";var Ln=process.env.BOSTRAT_STATE_DIR||_e.join(process.env.HOME||yx.tmpdir(),".bostrat"),jf=_e.join(Ln,"runbooks.json"),vi=_e.join(Ln,"runbooks"),Df=256*1024,kx=[{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 xx(t){return kx.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Qe=null,Si=null;async function Ee(){return Qe||(Si||=(async()=>{try{Qe=JSON.parse(await tn.readFile(jf,"utf8"))}catch{Qe={runbooks:[]}}finally{Si=null}return Qe.runbooks||=[],Qe})(),Si)}async function Un(){await qt(jf,Qe)}function Fn(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 jn(t,e){return _e.join(vi,`${t.id}.v${e}.md`)}function Lf(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 Uf(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Df)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Df/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=xx(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 Ff(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Hf({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 Ti(t){let n=(await Ee()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Wf({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=Uf(r),c=await Ee(),l={id:bx().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 tn.mkdir(vi,{recursive:!0}),await tn.writeFile(jn(l,1),r),l.versions.push({v:1,mdRel:_e.relative(Ln,jn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Un(),await Fn(l,"created",{version:1}),{...l}}async function qf(t,{md:e,note:n}={}){let o=(await Ee()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Ff(o,"update");let s=Uf(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await tn.mkdir(vi,{recursive:!0}),await tn.writeFile(jn(o,a),e),o.versions.push({v:a,mdRel:_e.relative(Ln,jn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Un(),await Fn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Gf(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 Ff(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Un(),await Fn(r,e==="done"?"done":"reopened"),{...r})}async function Kf(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 tn.rm(_e.join(Ln,o.mdRel),{force:!0}).catch(()=>{});return await Un(),r.trackDeletedAt||await Fn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Vf(t){let n=(await Ee()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Fn(r,"orphaned"),r.cwd=null;return n.length&&await Un(),n.length}async function Jf(t){let e=await Ee(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Fn(r,"deleted");for(let o of r.versions||[])await tn.rm(_e.join(Ln,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Un()),n.length}import zf from"node:fs/promises";import Vo from"node:path";import{randomUUID as Sx}from"node:crypto";var Yf=".bostrat/uploads",Ai=t=>Vo.join(t,Yf),Xf=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,vx=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),Tx=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 _i(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return vx.has(e)?"image":Tx.has(e)?"doc":null}function Ax(t){let r=Vo.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 Zf(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>Xf)throw new Error(`file too large \u2014 max ${Math.floor(Xf/(1024*1024))} MB`);let r=Ax(e),o=Vo.extname(r).slice(1),s=_i(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=Sx().slice(0,8),i=Ai(t);await zf.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await zf.writeFile(Vo.join(i,c),n),{id:a,name:r,rel:`${Yf}/${c}`,kind:s,size:n.length}}function Qf(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var _x=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Ei(t){return t.role==="user"&&_x.test((t.text||"").trim())}function Hn(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var Rx=Ix(Ex),Ie=process.env.BOSTRAT_STATE_DIR||G.join(process.env.HOME||"/tmp",".bostrat"),ap=G.join(Ie,"tracks.json"),$x=G.join(Ie,"events.jsonl"),Ni=G.join(Ie,"workspaces"),Ox=process.env.BOSTRAT_DEFAULT_REPO||null,Yo={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"}},Bi=Object.keys(Yo);function Cx(t,{skipPermissions:e=!0}={}){let n=Yo[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 ip(t,e,{skipPermissions:n=!0}={}){let r=Yo[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 Zo(t){let e=Yo[t];if(e)return process.env[e.envVar]||e.base}async function Qo(t,e=!0){let n=Cx(t,{skipPermissions:e});if(!await Bs(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 ts=".bostrat/handoff/latest.md";function Mx(t,e=ts){return`${t.trim()}
|
|
73
|
+
|
|
74
|
+
(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 Nx=/\(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 tp(t){return t.role==="user"&&Nx.test((t.text||"").trim())}var Di=".bostrat/handoff/subtask-result.md",Jo=".bostrat/handoff/lead-context.md",Px=t=>`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 ${Jo} \u2014 read that first for context. Your scoped task:
|
|
75
|
+
|
|
76
|
+
${t}
|
|
77
|
+
|
|
78
|
+
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 ${Di} 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.`,Bx=(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."),Yt=null,Ii=null;async function F(){return Yt||(Ii||=(async()=>{try{Yt=JSON.parse(await D.readFile(ap,"utf8"))}catch{Yt={tracks:[]}}finally{Ii=null}return Yt})(),Ii)}async function K(){await qt(ap,Yt)}var Y=(t,e)=>t.tracks.find(n=>n.id===e)||null,Ri=Promise.resolve();function Dx(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
79
|
+
`;return Ri=Ri.then(()=>D.mkdir(Ie,{recursive:!0})).then(()=>D.appendFile($x,n)).catch(()=>{}),Ri}var it=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Dx(t.id,r),ed(t,r),r};fl(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await F()).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 $i=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),jx=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function cp(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=>jx.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 lp(t,e,n,{kind:r}={}){let o=await q(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Zo(o.agent);await we({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await yt(e,n,{...re(o.agent),kind:r||"orchestration"}),await j({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await F(),i=Y(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 en=new Set(["merged","conflict","canceled","failed","handed-off"]),ep=new Map;async function Lx(t){let e=G.join(yo(t),`${t.id}.ndjson`),n;try{n=await D.stat(e)}catch{return null}let r=ep.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await P(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return ep.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Ux(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:qa(n.name,n.agent)}async function Re(t){let e=new Map((await yn()).map(a=>[a.name,a])),n=t.boxes.map(a=>{let i=e.get(a.name),c=en.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([Lx(t),Ux(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var Fx=5e3;function Hx(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-Fx,f=r[u+1]?.createdAt??1/0,w=o.find(h=>!s.has(h.id)&&h.birthMs>=d&&h.birthMs<f);w&&(s.add(w.id),c.push({box:l.name,sessionId:w.id}))}return c}async function up(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=al(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=Hx(u,a,f=>i.has(f));for(let f of d){let w=u.find(h=>h.name===f.box);w&&(w.sessionId=f.sessionId),await Yc(f.box,f.sessionId),n=!0}}return n}async function dt(){let t=await F();return await up(t)&&await K(),Promise.all(t.tracks.map(Re))}async function dp(t,e){let n=await F();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 F(),n=Y(e,t);return n?(await up(e)&&await K(),Re(n)):null}var Wx={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function np(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(Wx[t.agent]||[]).some(n=>n.test(e))}var qx=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function rp(t){let e=(t.text||"").trim();return e?qx.some(n=>n.test(e)):!1}var Gx=10*6e4;async function ji(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=Mr(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=ii(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...Yd(n),...l],f={windows:[...vl(n),...Tl(n,co),...a,...u]},h=(await Promise.all(n.boxes.map(y=>du(y,e)))).flat(),g=h.map(y=>{let B={kind:"message",...y};if(B.role==="assistant"&&(B.text||"").trim()&&B.tools?.length){let dn=mr(B.agent).cliTools,zr=B.tools.filter(je=>!dn.has(je));return{...B,tools:zr.length?zr:void 0}}return B}).map(y=>{if(y.role!=="user")return y;let B=Co(y.text);return B?B.kind==="suppress"?null:{...y,note:B,text:Od(B)}:y}).filter(Boolean).filter(y=>!(y.role==="user"&&r.has((y.text||"").trim()))).filter(y=>!np(y)).filter(y=>!rp(y)).filter(y=>!tp(y)).filter(y=>!Ei(y)).filter(y=>y.role!=="assistant"||!!(y.text||"").trim()||!!y.images?.length).filter(y=>!Al(y,f)),x=g.filter(y=>y.failed&&y.text===ba);if(x.length){let y=new Map;for(let B of x){if(!y.has(B.box)){let je=[...(await pt(B.box,{lines:It}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Is=>Is[1]);y.set(B.box,je.length?je[je.length-1]:null)}let dn=y.get(B.box);dn&&(B.text=`\u25A0 ${dn}`)}}let A=await Promise.all(n.boxes.map(y=>pu(y,e)));await $d(n,A.flat()),await Id(n,g),await nd(n),await of(n,g).catch(()=>{});for(let y of n.boxes)y.agent==="codex"&&y.state==="live"&&await Pd(n,y),y.agent==="cursor"&&y.state==="live"&&await Ud(n,y);let S=await Promise.all(n.boxes.map(y=>fu(y,e)));await Rd(n,S.flat().filter(y=>!$i(y.box,y.ts,u)));let v=await P(n),E=c&&i.viewId?i.viewId:null,J=dd(v),I=Mo(v).filter(y=>!(y.kind==="message"&&(np(y)||rp(y)||tp(y)||Ei(y)))&&!(y.kind==="view-card"&&E&&y.viewId===E)),M=fd(I,J),m=I.filter(y=>!(y.kind==="message"&&y.role==="assistant"&&!M.has(y)&&$i(y.box,y.ts,J))),p=Xa(v,m).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=>!$i(y.anchorBox,y.anchorTs??y.ts,u)),b=[...m,...p].filter(y=>y.ts!=null).sort((y,B)=>y.ts-B.ts),T=Kx(n),ot=T.size?b.filter(y=>y.kind==="event"||!T.has(y.box??y.anchorBox)):b,$=n.boxes.find(y=>y.name===n.leadBox)||null,z=$?await sr($.cwd,$.sessionId,{fallbackNewest:!1}):null;if(!z&&$){let y=[...v].reverse().find(B=>B.type==="context.usage"&&B.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=[...h].reverse().find(y=>y.role==="assistant"&&y.model),H=N?.model||null,_=[...n.events||[]].reverse().find(y=>y.type==="model-change"),W=z?.model||H;_&&(!N?.ts||_.at>=N.ts)&&(W=_.to);let X=$?await pt($.name,{lines:It}).catch(()=>""):"",lt=$&&$n($.agent,X)?await me($.name).catch(()=>X):X,tt=$?Ve($,lt):null;tt&&rd(n,$,tt);let wt=!!$&&!tt&&ir($.agent,X);$&&!$.readyAt&&(wt||tt||X&&so($.agent,X))&&($.readyAt=Date.now(),await K());let Ct=!!$&&!$.readyAt&&Date.now()-(n.createdAt||0)<Gx;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&$&&so($.agent,X)&&!tt){let y=await F(),B=Y(y,n.id);B?.setup&&!B.setup.connectedAt&&(B.setup.connectedAt=Date.now(),n.setup.connectedAt=B.setup.connectedAt,await R(B,{type:"provider.connected",actor:"system",source:"pane",boxId:$.name,provider:$.agent,payload:{provider:$.agent,ts:B.setup.connectedAt}}).catch(()=>{}),await K())}let De=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Dt=null;if(c){let y=Date.now()-i.startedAt;if(i.viewId&&!wt){let B=Lo(n.id);B&&(it(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:B.finishedAt}),await K())}else if(tt||y>zd&&!wt&&!i.viewId||y>ai){let B=tt?"needs-input":y>ai?"timeout":"no-view";Lo(n.id,{error:B})&&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:B}}).catch(()=>{})}else Dt={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:W,options:Ju($?.agent)},pendingPrompt:tt,working:wt,leadStarting:Ct,handoff:De,view:Dt,queued:Jx(n.id)}}function Kx(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 fp(t,e){let n=await rt(t);if(!n)return null;let r=Gn(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 P(n),a=Mo(s).filter(d=>d.box===o),i=Xa(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:It}).catch(()=>"");u=ir(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function Li(t,{promptId:e,answers:n,text:r}={}){let o=Y(await F(),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 q(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Oa(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=Iu(i,n||[]);await gt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=yu(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 j({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),od(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function pp(t,{promptId:e,questionIndex:n}={}){let r=Y(await F(),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 q(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Oa(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=Ru(a,n);return c.length?(await gt(r.leadBox,c,{kind:"navigate-prompt"}),await j({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 Vx=1500,Ir=new Map;function mp(t){let e=Ir.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Ir.set(t,e)),e}function Jx(t){return(Ir.get(t)?.items??[]).map(e=>({...e}))}function es(t){let e=Ir.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 zx(t){let e=mp(t);e.timer||(e.timer=setInterval(()=>Xx(t).catch(()=>{}),Vx))}async function Xx(t){let e=Ir.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=Y(await F(),t);if(!n||n.archivedAt){es(t);return}let r=Mr(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Hn(e,!1);return}let o=await q(n.leadBox);if(!o){Hn(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await pt(n.leadBox,{lines:It}).catch(()=>"");if(oo(s?.agent,a)){Hn(e,!1);return}if($n(s?.agent,a)){let c=await me(n.leadBox).catch(()=>a);if(Ve(s,c)){Hn(e,!1);return}}if(!Hn(e,!0))return;let i=e.items.shift();e.idleSince=null,await hp(n,s,o,i.text,i.kind??null,i.attachments??null),await K()}finally{e.flushing=!1}}}async function hp(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=Mx(r,t.pendingHandoffHydrate.artifact||ts),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(l=>l&&l.rel):[];if(c.length){let l=Qf(c.map(u=>u.rel));a=a.trim()?`${a.trim()}
|
|
80
|
+
|
|
81
|
+
${l}`:l}await we({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:Zo(n.agent)}),await yt(t.leadBox,a,{...re(e?.agent),kind:i?"lead-handoff-first-prompt":"say"}),i&&await j({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length}),it(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}}),delete t.draft}async function $e(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"||!On(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=Y(await F(),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 q(s.leadBox);if(!a&&await Hi(s)&&(await K(),a=await q(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(f=>f.name===s.leadBox),c=On(e),l=mp(t),u=await pt(s.leadBox,{lines:It}).catch(()=>""),d=l.items.length>0||oo(i?.agent,u);if(!d&&$n(i?.agent,u)){let f=await me(s.leadBox).catch(()=>u);d=!!Ve(i,f)}if(d){let f={id:Xo(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(f),zx(t),delete s.draft,await K(),{ok:!0,leadBox:s.leadBox,queued:!0,id:f.id,position:l.items.length}}return await hp(s,i,a,c,n,o),await K(),{ok:!0,leadBox:s.leadBox}}var op="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 wp(t,{hint:e}={}){let n=Y(await F(),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=ii(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";Xd(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${op}
|
|
82
|
+
|
|
83
|
+
What to preview: ${String(e).trim()}`:op;try{await $e(t,a,{kind:"spin-up-view"})}catch(i){throw Lo(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Ui(t){let e=Y(await F(),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(i=>i.name===e.leadBox),r=es(t),o=await q(e.leadBox);o&&await gt(e.leadBox,["Escape"],{kind:"interrupt"});let s=o?await Qx(e,n):null,a=o?await Yx(e).catch(()=>null):null;return await j({flow:"interrupt",phase:"sent",trackId:t,sessionName:e.leadBox,agent:n?.agent,dropped:r.length,unsent:!!s,shipDismissed:!!a}).catch(()=>{}),{ok:!0,interrupted:!!o,dropped:r.length,restored:s}}async function Yx(t){let e=yi(await P(t));return e?.offerId?(await R(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 Zx=[250,400,500,600];async function Qx(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 Zx){await Vi(r);let o=await pt(t.leadBox,{lines:It}).catch(()=>""),s=Ca(e.agent,o);if(s&&Ou(n.text,s))return await t0(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},it(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await K(),n.text}return null}async function t0(t,e,n){for(let r=0;r<3;r++){await to(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Vi(200);let o=await pt(t,{lines:It}).catch(()=>""),s=Ca(e,o);if(!s)return!0;n=s.length+8}return!1}async function gp(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=Y(await F(),t);if(!n)throw new Error(`track "${t}" not found`);let r=On(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await K(),{ok:!0,draft:n.draft||null}}async function yp(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=Y(await F(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await q(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=zu(a,n),l=[...r.events||[]].reverse().find(d=>d.type==="model-change")?.to??null;l||(l=(await sr(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await we({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:Zo(o.agent)});let u=await Xu(a,r.leadBox,i);return it(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await K(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function e0(t){for(let e of t){if(!e||!await Pa(e))continue;let n=await Mu(e).catch(()=>null);if(!n)continue;if((await D.stat(G.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}async function n0(t,e,{repo:n,cwd:r}={}){await D.mkdir(Ni,{recursive:!0});let o=G.join(Ni,t);typeof n=="string"&&!G.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await Fl(n));let s=await e0([n,r,Ox]);if(s){let a=`track/${Op(e)}-${t}`;try{let i=await Bu(s);await Ba(s,o,a,i),await Pi(o);let c=await Z(s).catch(()=>null);return{dir:o,base:s,branch:a,kind:"worktree",baseRef:i,repo:c}}catch{await D.rm(o,{recursive:!0,force:!0}).catch(()=>{})}}return await D.mkdir(o,{recursive:!0}),await Du(o),await Pi(o),{dir:o,base:null,branch:null,kind:"repo"}}var Rr="# bostrat workspace",Er=`${Rr}
|
|
84
|
+
|
|
85
|
+
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
86
|
+
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
87
|
+
another device \u2014 localhost links are dead there). Instead run:
|
|
88
|
+
|
|
89
|
+
bostrat-view up --name "<short name>" -- <serve command>
|
|
90
|
+
|
|
91
|
+
(e.g. \`bostrat-view up --name "Checkout preview" -- npm run dev\`). The server is
|
|
92
|
+
assigned a port via $PORT \u2014 pass it through if the command needs it explicitly
|
|
93
|
+
(e.g. \`-- npm run dev -- --port "\\$PORT"\`; escape it so your own shell doesn't
|
|
94
|
+
eat it). The command is typed verbatim into a shell that already has PORT set \u2014
|
|
95
|
+
do NOT wrap it in \`sh -c '\u2026'\`: the quoting is lost in transit and the wrapper
|
|
96
|
+
runs only the first word. Run it from the directory the serve command belongs in: the view starts in YOUR cwd,
|
|
97
|
+
so in a monorepo cd to the app first (the dir holding its package.json), not the
|
|
98
|
+
workspace root. Share the public URL the command prints; that link works on the
|
|
99
|
+
operator's phone. \`bostrat-view ls\` lists running views (with the dir each runs in),
|
|
100
|
+
\`bostrat-view stop <name-or-id>\` stops one, \`bostrat-view rm <name-or-id>\` deletes
|
|
101
|
+
the record \u2014 needed if a view was created with the wrong directory or command, since
|
|
102
|
+
a re-\`up\` under the same name reuses the existing record.
|
|
103
|
+
|
|
104
|
+
When a view is ALREADY live and you are asked to adjust what it shows: just edit the
|
|
105
|
+
code \u2014 the running dev server hot-reloads, and the operator's existing URL updates
|
|
106
|
+
in place. Do NOT start another server or invent a new view name for the adjustment;
|
|
107
|
+
re-share the same URL (\`bostrat-view ls\` prints it). Re-running \`bostrat-view up\`
|
|
108
|
+
with the same name (or the same serve command) is safe \u2014 it reuses the existing
|
|
109
|
+
view rather than creating a duplicate.
|
|
110
|
+
|
|
111
|
+
When asked to MOCK UP a design ("mock up 3 options", "show me a design for X"),
|
|
112
|
+
you are acting as a DESIGNER delivering images for review. Write each comp as ONE
|
|
113
|
+
self-contained HTML file (inline ALL CSS in a <style> block \u2014 no external
|
|
114
|
+
stylesheets; web fonts with system fallbacks so it renders offline; author at the
|
|
115
|
+
width you were asked for \u2014 default 1300, pass --width for other artboards), then
|
|
116
|
+
publish it with:
|
|
117
|
+
|
|
118
|
+
bostrat-mock add --name "<short name>" [--group <batch-id>] [--width N] <file.html>
|
|
119
|
+
|
|
120
|
+
Do NOT screenshot it by hand and do NOT just leave the file in the repo \u2014 the
|
|
121
|
+
command renders it full-page to an image the operator reviews in chat and the
|
|
122
|
+
gallery. If you already have a finished image, publish it directly:
|
|
123
|
+
\`bostrat-mock add --name "<name>" <file.png>\`.
|
|
124
|
+
|
|
125
|
+
When the operator gives FEEDBACK on a mock, revise the SAME mock \u2014 edit the comp
|
|
126
|
+
and run \`bostrat-mock update <name-or-id> <file>\` \u2014 never add a near-duplicate.
|
|
127
|
+
Each update is a new version; the history stays reviewable. For a batch ("3
|
|
128
|
+
options"), give every comp the same --group so they can be compared side by side.
|
|
129
|
+
\`bostrat-mock ls\` lists mocks, \`bostrat-mock rm <name-or-id>\` deletes one.
|
|
130
|
+
|
|
131
|
+
When asked to link this work to a TICKET (a Linear issue URL or a key like
|
|
132
|
+
ABC-123), run:
|
|
133
|
+
|
|
134
|
+
bostrat-ticket link <issue-url-or-KEY-123>
|
|
135
|
+
|
|
136
|
+
The issue then carries a live Bostrat status card, and PR opened/merged land on
|
|
137
|
+
it as comments automatically \u2014 do NOT hand-write status comments to the tracker.
|
|
138
|
+
\`bostrat-ticket status\` shows the current link, \`bostrat-ticket unlink\` removes it.
|
|
139
|
+
|
|
140
|
+
Ticket STATUS moves are manual-first: at each lifecycle moment (link, PR opened,
|
|
141
|
+
PR merged) Bostrat shows the operator a one-tap offer card in their chat. When
|
|
142
|
+
the operator asks you to AUTOMATE those moves (or to stop the prompts), do NOT
|
|
143
|
+
improvise \u2014 set the prepared config:
|
|
144
|
+
|
|
145
|
+
bostrat-ticket flow set <phase>=<mode>
|
|
146
|
+
|
|
147
|
+
Phases: linked, prOpened, prMerged. Modes: prompt (default, offer card), auto
|
|
148
|
+
(Bostrat moves the issue itself), off (nothing). E.g. "always move tickets when
|
|
149
|
+
PRs merge" \u2192 \`bostrat-ticket flow set prMerged=auto\`; "stop asking about ticket
|
|
150
|
+
status" \u2192 set the relevant phases to off. These three phases are the ONLY
|
|
151
|
+
configurable ticket automations \u2014 if asked to automate or suppress a ticket
|
|
152
|
+
behavior that isn't one of them, say it isn't configurable yet instead of
|
|
153
|
+
working around it. Never move the issue or edit its status by other means.
|
|
154
|
+
|
|
155
|
+
When you FINISH a piece of work and steps remain that ONLY the operator can
|
|
156
|
+
perform (vendor/app registrations, secrets to set, interactive logins, device or
|
|
157
|
+
cert installs, DNS changes, subscriptions, physical steps), write those steps as
|
|
158
|
+
ONE well-formatted markdown RUNBOOK \u2014 a # title naming the deliverable state, a
|
|
159
|
+
short context paragraph, numbered copy-pasteable steps each with a verification
|
|
160
|
+
line ("you should see \u2026"), and a closing line telling the operator to report
|
|
161
|
+
completion in this chat. NEVER put a secret VALUE in a runbook \u2014 name the secret
|
|
162
|
+
and where it goes (\`fly secrets set FOO=<your key>\`), never the value; the
|
|
163
|
+
server rejects token-shaped content. Publish it with:
|
|
164
|
+
|
|
165
|
+
bostrat-runbook add --name "<deliverable state>" <file.md>
|
|
166
|
+
|
|
167
|
+
BEFORE your closing ship-offer line (below). Do NOT just paste the steps into
|
|
168
|
+
chat \u2014 the runbook is durable, versioned, and survives this Track. When the
|
|
169
|
+
operator reports progress ("done", "key installed") or the plan changes, revise
|
|
170
|
+
the SAME runbook \u2014 keep completed steps, struck through (~~like this~~ \u2014 done
|
|
171
|
+
<date>), adjust the rest \u2014 and run
|
|
172
|
+
\`bostrat-runbook update <name-or-id> --note "<what changed>" <file.md>\`; never
|
|
173
|
+
add a near-duplicate. When every step is confirmed, run
|
|
174
|
+
\`bostrat-runbook done <name-or-id>\`. At the START of related work, check
|
|
175
|
+
\`bostrat-runbook ls\` \u2014 if an open runbook covers this ground, update it rather
|
|
176
|
+
than creating another. Runbooks are read-only for the operator: they act and
|
|
177
|
+
report back in chat; you are the only writer.
|
|
178
|
+
|
|
179
|
+
When you FINISH a piece of implementation work (feature built, bug fixed, checks
|
|
180
|
+
green) and the operator has not already told you to ship it: do NOT commit, push,
|
|
181
|
+
or open a PR on your own. Instead END your final message with a short, explicit
|
|
182
|
+
offer \u2014 e.g. "Want me to commit and push this, or open a PR?" \u2014 as its own last
|
|
183
|
+
line. Bostrat detects that closing offer and renders one-tap ship controls in the
|
|
184
|
+
operator's chat; the go-ahead comes back as a normal message telling you exactly
|
|
185
|
+
which steps to run (and which to skip). An offer buried mid-message, phrased as a
|
|
186
|
+
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
187
|
+
implementation turn this way, every time. When the operator has already asked you
|
|
188
|
+
to commit / push / open the PR, just do it; do not re-offer.
|
|
189
|
+
`;async function Pi(t){try{await D.writeFile(G.join(t,"AGENTS.md"),Er,{flag:"wx"})}catch{return}await r0(t,["/AGENTS.md"])}var Oi=t=>typeof t=="string"&&(t===Rr||t.startsWith(`${Rr}
|
|
190
|
+
`)),Ci=Er.replace(`${Rr}
|
|
191
|
+
`,`${Rr}
|
|
192
|
+
|
|
193
|
+
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
194
|
+
SCOPE: these instructions apply ONLY when your working directory is under
|
|
195
|
+
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
196
|
+
When working anywhere else, ignore everything below.
|
|
197
|
+
`);async function bp(t){let{stdout:e}=await Rx("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return G.resolve(t,e.trim())}async function r0(t,e){try{let n=await bp(t);await D.mkdir(G.dirname(n),{recursive:!0});let r=await D.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
198
|
+
`).includes(s));o.length&&await D.appendFile(n,(r.endsWith(`
|
|
199
|
+
`)||!r?"":`
|
|
200
|
+
`)+o.join(`
|
|
201
|
+
`)+`
|
|
202
|
+
`)}catch{}}async function o0(t,e){try{let n=await bp(t),r=await D.readFile(n,"utf8"),o=r.split(`
|
|
203
|
+
`).filter(s=>s!==e);o.length!==r.split(`
|
|
204
|
+
`).length&&await D.writeFile(n,o.join(`
|
|
205
|
+
`))}catch{}}async function kp(){let t={refreshed:0,removed:0,codexGlobal:null};try{await D.mkdir(Ie,{recursive:!0});for(let r of["CLAUDE.md","AGENTS.md"])await D.writeFile(G.join(Ie,r),Er).catch(()=>{});let e=process.env.CODEX_HOME||G.join(process.env.HOME||"/tmp",".codex");if((await D.stat(e).catch(()=>null))?.isDirectory()){let r=G.join(e,"AGENTS.md"),o=await D.readFile(r,"utf8").catch(()=>null);o===null?(await D.writeFile(r,Ci,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Oi(o)&&o!==Ci&&(await D.writeFile(r,Ci).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Ni,qn]){let o=await D.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=G.join(r,s.name),i=G.join(a,"AGENTS.md"),c=await D.readFile(i,"utf8").catch(()=>null);c!==null&&Oi(c)&&c!==Er&&(await D.writeFile(i,Er).catch(()=>{}),t.refreshed++);let l=G.join(a,"CLAUDE.md"),u=await D.readFile(l,"utf8").catch(()=>null);u!==null&&Oi(u)&&(await D.rm(l,{force:!0}).catch(()=>{}),await o0(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function s0(t){let e=t.workspace;if(!(!e||!e.dir))try{e.kind==="worktree"&&e.base?await hr(e.base,e.dir):await D.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Fi({name:t,cwd:e,repo:n,agent:r="claude",skipPermissions:o=!0}){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");let s=await Qo(r,o),a=await F(),i=Xo().slice(0,8),c=await n0(i,t,{repo:n,cwd:e}),l=new Set(a.tracks.flatMap(w=>(w.boxes||[]).map(h=>h.name))),u=`${t.trim()} lead`;l.has(Ds(u))&&(u=`${t.trim().slice(0,50).trim()} ${i} lead`);let d=await Ue(u,c.dir,{agentCmd:s,agent:r});ao({cwd:d.cwd,sessionName:d.name,agent:r,agentCmd:s});let f={id:i,name:t.trim(),cwd:d.cwd,workspace:c,leadBox:d.name,boxes:[Ep(d,"lead","live")],events:[],createdAt:Date.now()};return it(f,"opened",{agent:r,box:d.name,skipPermissions:o,name:f.name,cwd:f.cwd,workspace:{base:c.base,branch:c.branch,kind:c.kind,...c.baseRef?{baseRef:c.baseRef}:{},...c.repo?{repo:c.repo}:{}}}),a.tracks.push(f),await K(),Re(f)}var a0=new Set(["claude","codex","cursor"]);async function xp({provider:t="claude",fresh:e=!1}={}){if(!a0.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await F()).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 ns(r.id).catch(()=>{})}let o=(await Qo(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=Xo().slice(0,8),a=G.join(Ie,"setup",s);await D.mkdir(a,{recursive:!0});let i=await Ue(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&ao({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:[Ep(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 F()).tracks.push(c),await K(),Re(c)}async function Sp(t,e,n){await rf(t).catch(()=>{});let r=$r.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),$r.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of Gn(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await Ki(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 F(),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 vp(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await F(),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 Pf(s),await Sp(t,s,"track deleted"),n?await Jf(t).catch(()=>{}):await Vf(t).catch(()=>{}),await td(),await s0(s),e&&await Bf(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 ns(t){let e=await F(),n=Y(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);await Sp(t,n,"track archived");for(let r of n.boxes)en.has(r.state)||(r.state="stopped");return n.archivedAt=Date.now(),it(n,"archived",{boxes:n.boxes.length}),await K(),Re(n)}async function Hi(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||en.has(e.state)||await q(e.name))return!1;let n=ip(e.agent,e.sessionId);if(!await Bs(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}),ao({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 Tp(t){let e=await F(),n=Y(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 Hi(n),await K(),Re(n)}async function Ap(){let t=await F(),e=new Set((await yn()).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),!en.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await K(),r}async function _p(t){let e=await F(),n=Y(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(en.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await q(n.leadBox)){let a=await pt(n.leadBox,{lines:It}).catch(()=>"");if(oo(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 Hi(n);if(await K(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Re(n)}var Ep=(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 Ip(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 $r=new Map,Mr=t=>$r.get(t)||(Yt?Y(Yt,t)?.handoff:null)||null;async function zo(t,e){let n=await F(),r=Y(n,t);r&&(r.handoff={...r.handoff||{},...e},await K())}function i0(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 Rp(){let t=await F(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=i0(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await q(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=Ip(l),f=d[d.length-1],w=f.agent,h=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:ts,at:u},it(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:w,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 j({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 j({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 Wi(t,e,n=!0,r=null){let o=$r.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Y(await F(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Qo(e,n),i=await q(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:cp(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return $r.set(t,c),await zo(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),c0(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 zo(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),j({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),j({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function c0(t,e,n,r,o){let s=async(v,E={})=>{t.step=v,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${v}`),await zo(t.trackId,{step:v,toBox:t.toBox}),await j({flow:"lead-handoff",phase:v,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 F(),i=Y(a,t.trackId)?.boxes.find(v=>v.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 Il(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await ea(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:Zo(o)});let f=await pt(t.fromBox,{lines:It}).catch(()=>"");if(!so(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let v=ip(c,l,{skipPermissions:t.skipPermissions});await ea(t.fromBox,{agentCmd:v,agent:c,resumeSessionId:l,pinned:u});let E=await F(),J=Y(E,t.trackId);if(J){let I=J.boxes.find(M=>M.name===t.fromBox);I&&(I.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 F(),h=Y(w,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let g=h.boxes.find(v=>v.name===t.fromBox);if(!g)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let x=Date.now(),A=Ip(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",h.pendingHandoffHydrate={artifact:ts,at:x},it(h,"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 zo(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var l0=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,u0=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,qn=G.join(Ie,"worktrees"),Mi=[Jo,Di],Zt=new Map;var Gn=t=>{let e=new Map([...Zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Yt?Y(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)},qi=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 d0(t,e,n){let r=await F(),o=Y(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await K())}var Or=t=>d0(t.trackId,t.id,qi(t));async function Wn(t,e){t.step=e,await Or(t)}function f0(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 p0(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function m0(t,e,n){if(!p0(n)||Zt.has(e)||!await q(n.box).catch(()=>null))return!1;try{await D.access(n.dir)}catch{return!1}let o=await q(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),Pp(s,o).catch(a=>Mp(s,a)),!0}async function $p(){let t=await F(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Zt.has(r))continue;if(await m0(n,r,o)){e=!0,it(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await j({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=f0(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!en.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 j({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await sp(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||en.has(r.state)||Zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?G.join(qn,`${n.id}-${r.subtaskId}`):null;await j({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await sp(n,r.name,o)}}return e=await h0(t)||e,e&&await K(),e}async function sp(t,e,n){if(e&&await Wt(e).catch(()=>{}),n){let r=await q(t.leadBox).catch(()=>null);r?.cwd&&await hr(r.cwd,n).catch(()=>{})}}async function h0(t){let e;try{e=await D.readdir(qn)}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(G.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=G.join(qn,o),a=(t.tracks||[]).find(c=>o.startsWith(`${c.id}-`)),i=a?await q(a.leadBox).catch(()=>null):null;i?.cwd?await hr(i.cwd,s).catch(()=>{}):await D.rm(s,{recursive:!0,force:!0}).catch(()=>{}),await j({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Op=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Cr=class extends Error{};async function Cp(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!On(n))throw new Error("task (string) required");let s=await Qo(e,r),a=Y(await F(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await q(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 Pa(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let c=Xo().slice(0,8),l={id:c,trackId:t,trackName:a.name,agent:e,task:On(n),skipPermissions:r,reason:cp(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Op(a.name)}-${c}`,base:null,dir:G.join(qn,`${t}-${c}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Zt.set(c,l),await Or(l),w0(l,i,s).catch(u=>Mp(l,u)),l}async function Mp(t,e){t.failedStep=t.step,t.error=e instanceof Cr?"canceled":e?.message||String(e),t.step=e instanceof Cr?"canceled":"error",t.finishedAt=Date.now(),await Ki(t).catch(()=>{}),await Or(t).catch(()=>{}),await k0(t).catch(()=>{})}function Gi(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 Np(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 w0(t,e,n){await Wn(t,"snapshot"),t.base=await Nu(e.cwd,`bostrat: snapshot for subtask ${t.id}`),await Wn(t,"spawn"),await D.mkdir(qn,{recursive:!0}),await Ba(e.cwd,t.dir,t.branch,t.base),await Pi(t.dir),await g0(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 y0(t,o),await lp(t.trackId,o.name,Px(t.task),{kind:"subtask"}),await Pp(t,e)}async function Pp(t,e){await Wn(t,"working");let n=G.join(t.dir,Di);if(await S0(n,t),t.canceled)throw new Cr;await Wn(t,"merge"),await Pu(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await ju(e.cwd,t.base,t.branch,Mi),t.files=await Lu(e.cwd,t.base,t.branch,Mi);let r=await Uu(e.cwd,t.base,t.branch,Mi);t.applied=r.applied,t.conflicts=r.conflicts,await Or(t);let o=await x0(t,e,n);await Wn(t,"resume"),await lp(t.trackId,t.leadBox,Bx(t.task,t.stat,t.conflicts,o),{kind:"subtask-resume"}),await Wn(t,"reap"),await Ki(t),t.step="done",t.finishedAt=Date.now(),await b0(t),await Or(t)}async function g0(t,e){let n=G.join(t.dir,G.dirname(Jo));await D.mkdir(n,{recursive:!0});let r="";try{r=await cl(e.cwd,e.sessionId)}catch{}if(!r)try{r=await D.readFile(G.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}
|
|
206
|
+
|
|
207
|
+
**Delegated task:** ${t.task}
|
|
208
|
+
|
|
209
|
+
**Lead agent:** ${e.agent||"claude"} \xB7 **workspace:** ${e.cwd}
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Conversation so far (lead \u2192 you)
|
|
214
|
+
|
|
215
|
+
${r}
|
|
216
|
+
`;await D.writeFile(G.join(t.dir,Jo),o)}async function y0(t,e){let n=await F(),r=Y(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}),it(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]||{},...qi(t)},await K())}async function b0(t){let e=await F(),n=Y(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),it(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 K()}async function k0(t){let e=await F(),n=Y(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"),it(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]||{},...qi(t)},await K()}async function x0(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=G.join(e.cwd,r);await D.mkdir(G.dirname(o),{recursive:!0}),await D.copyFile(n,o)}catch{}return r}async function Ki(t){if(t.box&&await Wt(t.box).catch(()=>{}),t.dir){let n=(await q(t.leadBox).catch(()=>null))?.cwd;n&&await hr(n,t.dir).catch(()=>{})}}async function S0(t,e){let n=(e.startedAt||Date.now())+l0;for(;;){if(e.canceled||e.forceMerge)return;try{await D.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Vi(u0)}}function Vi(t){return new Promise(e=>setTimeout(e,t))}import Lr from"node:fs/promises";import ms from"node:path";import dS from"node:os";import v0 from"node:fs/promises";import Bp from"node:path";import T0 from"node:os";var Dp=()=>Bp.join(process.env.BOSTRAT_STATE_DIR||Bp.join(process.env.HOME||T0.tmpdir(),".bostrat"),"integrations.json");async function nn(){try{return JSON.parse(await v0.readFile(Dp(),"utf8"))||{}}catch{return{}}}var rs=["linked","prOpened","prMerged","create"],Ji=["prompt","auto","off"],A0={create:["prompt","off"]},jp=t=>A0[t]??Ji;function os(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of rs){let s=jp(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 rn(){let t=await nn(),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:os(t),defaultTeam:n}:null}async function ss(){let t=await nn(),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:os(t)}}async function zi(){return os(await nn())}async function Lp(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${rs.join(", ")}`);for(let[r,o]of e){if(!rs.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${rs.join(", ")}`);let s=jp(r);if(!s.includes(o))throw new Error(s.length<Ji.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Ji.join(", ")}`)}let n=await nn();return n.ticketFlow={...os(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await qt(Dp(),n,{mode:384}),n.ticketFlow}async function Nr(){let t=(await nn()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Up(){let t=(await nn()).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 on(){let t=(await nn()).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}}import Dr from"node:fs/promises";import Yp from"node:path";import Y0 from"node:os";import{randomUUID as Zp}from"node:crypto";var Fp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function _0(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Hp(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function is(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=Fp();for(let d of n){let f=d.payload?.model||"unknown",w=d.provider||"unknown",h=d.boxId||"unattributed",g=_0(d.payload?.tokens),x=Number(d.payload?.usd)||0;!x&&g&&(x=Kt(f,g),x>0&&i++);let A=g?g.input+g.output+g.cacheRead+g.cacheWrite:0;if(!x&&A>0&&!No(f)&&(c++,l.add(f)),a+=x,Hp(u,g),r[f]||={usd:0,tokens:Fp(),events:0},r[f].usd+=x,r[f].events+=1,Hp(r[f].tokens,g),o[w]||={usd:0,events:0},o[w].usd+=x,o[w].events+=1,s[h]||={usd:0,events:0,provider:w,models:new Set,label:e[h]||h},s[h].usd+=x,s[h].events+=1,f!=="unknown"&&s[h].models.add(f),d.epoch!=null){let S=(s[h].epochs||={})[d.epoch]||={usd:0,events:0,provider:w,models:new Set};S.usd+=x,S.events+=1,S.provider=w,f!=="unknown"&&S.models.add(f)}}return{totalUsd:Pr(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:as(r,d=>({...d,usd:Pr(d.usd)})),byProvider:as(o,d=>({...d,usd:Pr(d.usd)})),byBox:as(s,d=>({usd:Pr(d.usd),events:d.events,provider:d.provider,label:d.label,models:[...d.models],...d.epochs?{epochs:as(d.epochs,f=>({usd:Pr(f.usd),events:f.events,provider:f.provider,models:[...f.models]}))}:{}}))}}var Pr=t=>Math.round(t*1e4)/1e4,as=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));var Yi={};Hc(Yi,{createComment:()=>P0,findIssueUrl:()=>$0,issueStates:()=>j0,mdToAdf:()=>Gp,moveIssue:()=>L0,parseIssueRef:()=>R0,resolveIssue:()=>O0,uploadImage:()=>B0,upsertCard:()=>C0,whoami:()=>Xi});var Kn=t=>String(t.baseUrl||"").replace(/\/$/,""),qp=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function sn(t,e,n,r){let o=await fetch(`${Kn(t)}${n}`,{method:e,headers:{authorization:qp(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 E0=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,I0=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function R0(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(E0);return r?r[1].toUpperCase():null}let n=e.match(I0);return n?n[1]:null}function $0(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 O0(t,e){let n;try{n=await sn(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 C0(t,e,{url:n,title:r,subtitle:o}){await sn(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 M0=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function N0(t){let e=[],n=0;for(let r of t.matchAll(M0)){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 Gp(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
217
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...N0(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 P0(t,e,n){let r=await sn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Gp(n)});return r?.id?`${Kn(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Xi(t){let e=await sn(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 B0(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:qp(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 D0={new:"unstarted",indeterminate:"started",done:"completed"},Wp=t=>D0[t?.key]??"started";async function j0(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([sn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),sn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Wp(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Wp(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function L0(t,e,n){await sn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var ec={};Hc(ec,{createComment:()=>V0,createIssue:()=>tc,createProject:()=>Qi,findIssueUrl:()=>q0,issueStates:()=>z0,moveIssue:()=>X0,parseIssueRef:()=>W0,resolveIssue:()=>G0,teams:()=>Vn,uploadImage:()=>J0,upsertCard:()=>K0,whoami:()=>Zi});var U0=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function Qt(t,e,n={}){let r=await fetch(U0(),{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)throw new Error(`linear: ${o.errors[0].message}`);return o?.data??{}}var F0=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,H0=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function W0(t){let e=String(t||"").trim(),n=e.match(F0);if(n)return n[1].toUpperCase();let r=e.match(H0);return r?r[1]:null}function q0(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 G0(t,e){let r=(await Qt(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 K0(t,e,{url:n,title:r,subtitle:o}){let s=await Qt(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 V0(t,e,n){let r=await Qt(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 Zi(t){let e=await Qt(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 J0(t,{filename:e,contentType:n,buffer:r}){let o=await Qt(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
218
|
+
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
219
|
+
success
|
|
220
|
+
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
221
|
+
}
|
|
222
|
+
}`,{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 z0(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 X0(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 Vn(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 Qi(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 tc(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 Qp={linear:{api:ec,config:rn},jira:{api:Yi,config:ss}};async function ic(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Qp)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function an(t){let e=Qp[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function cc(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 tm=()=>process.env.BOSTRAT_STATE_DIR||Yp.join(process.env.HOME||Y0.tmpdir(),".bostrat"),sc=()=>Yp.join(tm(),"tickets.json"),em=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),ls=t=>`${em()}/track/${t}`,cs=null,nc=null,rc=Promise.resolve();async function us(){return cs||(nc??=Dr.readFile(sc(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>cs=t).finally(()=>nc=null),nc)}async function Kp(){let t=JSON.stringify({tickets:cs},null,2),e=`${sc()}.${process.pid}.tmp`;await Dr.mkdir(tm(),{recursive:!0}),await Dr.writeFile(e,t),await Dr.rename(e,sc())}async function Oe(){rc=rc.then(Kp,Kp),await rc.catch(()=>{})}async function te(t){return(await us())[t]??null}async function nm(){return{...await us()}}async function rm(){return(await ic()).length>0}async function Jn(t,e,{source:n="api"}={}){let r=await ic();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 us())[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 jr(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await Br(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await am(t.id,c,"linked"),c}async function lc(t){let e=await us();return e[t]?(delete e[t],await Oe(),!0):!1}var ac=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function om(t,e){if(!t.track?.cwd)return null;let n=is(await P({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Z0(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${ac(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${ac(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Q0(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(ac(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function jr(t,e){let n=await an(e);if(!n)return;let r=await om(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:ls(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Q0(e,r)})}function ds(t){let e=new URL(em());return`${e.protocol}//${t}.${e.host}/`}var tS={linked:"linked",opened:"prOpened",merged:"prMerged"};function eS(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 sm(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 am(t,e,n){let r=await an(e);if(!r)return;let o=r.cfg.ticketFlow?.[tS[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=cc(s,a),c=eS(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 sm(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:Zp(),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 im(t){let e=await te(t);if(!e)throw new Error("no ticket linked to this Track");let n=await an(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=cc(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 uc(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 an(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=cc(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 sm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function cm(t,e){let n=await te(t);if(!n)throw new Error("no ticket linked to this Track");let r=await an(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 lm(t,e,n){if(!e)throw new Error("offerId required");let r=await P({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 uc(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 Vp=50;async function um(){let t=await rn();if(!t)throw new Error("no ticketing integration configured");return Vn(t)}function nS(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>Vp)throw new Error(`too many issues (${n.length}); cap is ${Vp} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function rS(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 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 dm(t,e){let n=await rn();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=nS(e),o=await Vn(n),s=await rS(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Zp();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 fm(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await P({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 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 rn();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(g=>g.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 Qi(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 g=await tc(a,{teamId:c.id,title:h.title,description:h.description,projectId:d,priority:h.priority});l.push({kind:"issue",key:g.key,title:g.title,url:g.url})}catch(g){u.push({title:h.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(h=>h.kind==="issue"),w=null;return i.linkCurrentTrack&&f.length===1&&!await te(t.id)&&(w=await Jn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:w?w.key:null}}async function Br(t,e){let n=await an(t);n&&await n.api.createComment(n.cfg,t,e)}async function oS(t,e,n){let r=await an(t);if(!r)return null;let o=await Ae(e);if(!o)return null;let s=Ze(o,n);if(!s)return null;let a=await Dr.readFile(Bt(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 Jp=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),oc=new Map;function sS(t,e){if(Jp()<=0)return jr(t,e);if(oc.has(t))return;let n=setTimeout(()=>{oc.delete(t),te(t).then(r=>r?jr(t,r):null).catch(()=>{})},Jp());n.unref?.(),oc.set(t,n)}var zp=new Map;async function Xp(t,e,n){let r=zp.get(t)??0;Date.now()-r<600*1e3||(zp.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 aS(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 ic())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 Xp(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 jr(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 jr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await om(n,t.trackId).catch(()=>null):null;await Br(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
223
|
+
|
|
224
|
+
Agent cost for this Track: ${Z0(o)}.`:"")),await am(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await oS(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Br(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
225
|
+
|
|
226
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await Br(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 Br(n,`**Bostrat** \u2014 live preview **${r.name}**: ${ds(r.slug)}
|
|
227
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await sS(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Xp(t.trackId,s?.cwd??null,o)}}function pm({resolveTrack:t}){Mt.on("event",e=>{aS(e,{resolveTrack:t}).catch(()=>{})})}import iS from"ws";var mm=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function fs(t,e,n={}){let r=await fetch(`${mm()}/${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 dc=(t,e)=>fs("chat.postMessage",t,e),zn=(t,e)=>fs("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 cS(t,e,n={}){let r=await fetch(`${mm()}/${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 hm(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await cS("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 fs("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var lS=2200;function wm({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 fs("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new iS(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,h=setTimeout(()=>{w=!0,f()},lS);Promise.resolve().then(()=>e(d)).then(g=>{clearTimeout(h),!w&&(w=!0,f(d.accepts_response_payload?g:void 0))},g=>{clearTimeout(h),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 bm=()=>process.env.BOSTRAT_STATE_DIR||ms.join(process.env.HOME||dS.tmpdir(),".bostrat"),wc=()=>ms.join(bm(),"slack.json"),ps=null,fc=null,pc=Promise.resolve();async function yc(){return ps||(fc??=Lr.readFile(wc(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>ps=t).finally(()=>fc=null),fc)}async function gm(){let t=JSON.stringify({threads:ps},null,2),e=`${wc()}.${process.pid}.tmp`;await Lr.mkdir(bm(),{recursive:!0}),await Lr.writeFile(e,t),await Lr.rename(e,wc())}async function ue(){pc=pc.then(gm,gm),await pc.catch(()=>{})}async function bc(t){return(await yc())[t]??null}async function fS(t,e){let n=await yc();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var U=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function Ur(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function pS(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Ur(n.replace(/\n+/g," "),e)}function km(t){let e=`*${U(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${U(t.repoLabel)} \xB7 \`${U(t.branch||"?")}\` \xB7 lead *${U(t.lead)}*`,r=t.kickoff?`> ${U(Ur(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
228
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${ls(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function gc(t,e){await zn(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:km(e)})}async function ln(t,e,n,r=void 0){return dc(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var ym=new Set;function cn(t){ym.add(t),t.catch(()=>{}).finally(()=>ym.delete(t))}var Q=t=>({response_type:"ephemeral",text:t}),mS="*/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._",hS=["claude","cursor","codex"];function mc(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 hc=(t,e)=>Q(`No live Track matches \u201C${U(e)}\u201D. Live: ${t.map(n=>`\`${U(n.name)}\``).join(" \xB7 ")||"none"}`);async function xm(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,{...Q(`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,{...Q("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?ms.basename(c.base):"fresh workspace"),lead:"claude"},u=await dc(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:km(l),unfurl_links:!1});l.ts=u.ts,(await yc())[i.id]=l,await ue(),await _t(o,{...Q(`\u2713 *${U(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await _t(o,{...Q(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function wS(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await bc(n.id);s&&(s.archivedAt=Date.now(),await ue(),await gc(e,s).catch(()=>{})),await _t(r,{...Q(`\u2713 *${U(n.name)}* archived.`),replace_original:!0})}catch(s){await _t(r,{...Q(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function gS(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await _t(o,{...Q(`\u2713 Handing *${U(n.name)}* to ${U(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await _t(o,{...Q(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function yS(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,{...Q(`\u2713 Stopped *${U(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,{...Q(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function bS(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Ur(`${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 *${U(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function kS(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?ms.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 *${U(s.name)}* \u2014 ${U(i)}${a.branch?` \`${U(a.branch)}\``:""} \xB7 lead ${U(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
229
|
+
`)+(o?`
|
|
230
|
+
_${o} archived_`:"")}async function xS(t,e){let n=await on();if(!n)return Q("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 Q("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?(cn(xm(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),Q(`Starting *${U(u)}* on ${U(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:bS(u,i)}}case"archive":{if(!a.length)return Q("Usage: `/bostrat archive <track>`");let{match:i,live:c}=mc(await r.listTracks(),a.join(" "));return i?(cn(wS(t,n,{track:i,responseUrl:e.response_url})),Q(`Archiving *${U(i.name)}*\u2026`)):hc(c,a.join(" "))}case"handoff":{let i=r.knownAgents??hS,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return Q(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:d}=mc(await r.listTracks(),l);return u?(cn(gS(t,n,{track:u,agent:c,responseUrl:e.response_url})),Q(`Handing *${U(u.name)}* to ${U(c)}\u2026`)):hc(d,l)}case"stop":{if(!a.length)return Q("Usage: `/bostrat stop <track>`");let{match:i,live:c}=mc(await r.listTracks(),a.join(" "));return i?(cn(yS(t,n,{track:i,responseUrl:e.response_url})),Q(`Stopping *${U(i.name)}*\u2026`)):hc(c,a.join(" "))}case"list":return Q(await kS(r));default:return Q(mS)}}async function SS(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await bc(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 ue(),await zn(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${U(l||"question")}~
|
|
231
|
+
\u2713 *${U(a)}*`}}]}).catch(()=>{})}}catch(c){await _t(n.response_url,Q(`Couldn't answer: ${c?.message||c}`))}}async function vS(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,xo(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,Q(`Couldn't act on the offer: ${c?.message||c}`))}}async function TS(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await on();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,{...Q(`Starting *${U(s)}*\u2026`),replace_original:!0}),cn(xm(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"?cn(SS(t,r,e,o)):n.action_id==="bostrat-ship"&&cn(vS(t,r,e,o))}async function AS(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 fS(e.channel,e.thread_ts);if(!n)return;let r=await on();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await ln(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function _S(t){return async e=>{if(e.type==="slash_commands")return xS(t,e.payload);if(e.type==="interactive"){await TS(t,e.payload);return}if(e.type==="events_api"){await AS(t,e.payload?.event);return}}}var ES=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 IS(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${U(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:Ur(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 RS(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await zn(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${U(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await ln(t,e,r.question||"The lead needs an answer",IS(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await ue()}async function $S(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 ue(),await zn(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${U(s||"question")}~
|
|
232
|
+
\u2713 _answered_`}}]})}async function OS(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 ln(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${U(Ur(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await ue()}async function CS(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await ue();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await zn(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function MS(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??NS)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await hm(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${U(o.name)}*${a}${o.group?` (group \`${U(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${U(a.name)}*`).join(", ");await ln(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${U(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function NS(t,e){let n=await Ae(t);if(!n)return null;let r=Ze(n,e);if(!r)return null;let o=await Lr.readFile(Bt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function PS(t,e){if(!ES.has(e.type))return;let n=await bc(e.trackId);if(!n)return;let r=await on();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 ue(),await gc(r,n);return;case"turn.agent":{if(!o.text)return;let s=pS(o.text);if(!s)return;await ln(r,n,s,[{type:"section",text:{type:"mrkdwn",text:U(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${ls(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return RS(r,n,e);case"prompt.answered":return $S(r,n,e);case"pr.offered":return OS(r,n,e);case"pr.offer.answered":return CS(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 ln(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 ue(),await gc(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await ln(r,n,`Live preview ${o.name}: ${ds(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${U(o.name)}*: ${ds(o.slug)}
|
|
233
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return MS(r,n,e,t.deps)}}function Sm(t){let e={deps:t};Mt.on("event",s=>{PS(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await on()||(r&&clearInterval(r),r=null,n=wm({getToken:async()=>(await on())?.appToken,onEnvelope:_S(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import BS from"node:fs/promises";function DS(t){let e=new Map;for(let n of String(t||"").split(`
|
|
234
|
+
`)){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 jS(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 LS(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ne(e.cwd,e.sessionId):e?.cwd?Fs(e.cwd):null}async function vm(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 US(r,o,t,i);break;default:break}return a}async function US(t,e,n,r){let o=[],s=LS(n,e);if(s)try{o=DS(await BS.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=jS(await P(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 h of Object.keys(w.tokens))w.tokens[h]+=d.tokens[h];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 FS from"node:fs";import kc from"node:fs/promises";import HS from"node:http";import Xn from"node:path";import ws from"node:os";import{createRequire as WS}from"node:module";import xc from"ws";var hs=(()=>{try{return WS(import.meta.url)("../package.json").version||null}catch{return null}})(),qS=process.env.BOSTRAT_STATE_DIR||Xn.join(process.env.HOME||ws.tmpdir(),".bostrat");function GS(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 KS({home:t=process.env.HOME||ws.homedir(),env:e=process.env}={}){let n=r=>{try{return FS.statSync(r),!0}catch{return!1}};return{claude:n(Xn.join(t,".claude",".credentials.json")),codex:n(Xn.join(e.CODEX_HOME||Xn.join(t,".codex"),"auth.json"))}}var xt=1,Tm=1e3,VS=3e4,Am=100,JS=2e3,zS=25e3;function _m({url:t,token:e,nodeId:n=ws.hostname(),name:r=ws.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");let i=s.replace(/^http/,"ws"),c=new Map,l=new Map,u=Xn.join(qS,"plane-link","cursor.json"),d={},f=new Map,w=null,h=!1,g=!1,x=Tm,A=null,S=null,v=null,E=!1;async function J(){try{d=JSON.parse(await kc.readFile(u,"utf8"))}catch{d={}}}function I(){E=!0,!S&&(S=setTimeout(async()=>{if(S=null,!!E){E=!1;try{await kc.mkdir(Xn.dirname(u),{recursive:!0}),await kc.writeFile(u,JSON.stringify(d))}catch(N){a.error("[plane-link] cursor flush failed:",N?.message||N)}}},JS),S.unref?.())}function M(N){return!w||w.readyState!==xc.OPEN?!1:(w.send(JSON.stringify(N)),!0)}function m(N){for(let{trackId:H,event:_}of N)f.set(_.id,{trackId:H,seq:_.seq});return M({v:xt,ch:"events",type:"append",events:N})}async function p(){let N;try{N=await o()}catch(H){a.error("[plane-link] backfill listTracks failed:",H?.message||H);return}M({v:xt,ch:"ctl",type:"tracks",ids:N.map(H=>H.id).filter(H=>typeof H=="string")});for(let H of N){if(!h)return;let _;try{_=await P(H)}catch{continue}let W=d[H.id]??0,X=_.filter(lt=>lt.seq>W&&typeof lt.id=="string");for(let lt=0;lt<X.length;lt+=Am){let tt=X.slice(lt,lt+Am).map(wt=>({trackId:H.id,event:wt}));if(!m(tt))return}}}function b(N){!h||!N?.trackId||typeof N.id!="string"||m([{trackId:N.trackId,event:N}])}let T=new Map;async function ot(N){let{reqId:H,method:_,path:W,headers:X={},body:lt}=N,tt=Object.fromEntries(Object.entries(X).filter(([,Ct])=>Ct!=null)),wt=new AbortController;T.set(H,wt);try{if(N.host){await $(N,tt,wt);return}let Ct=await fetch(s+W,{method:_,headers:tt,body:lt?Buffer.from(lt,"base64"):void 0,signal:wt.signal});if(M({v:xt,ch:"control",type:"http_head",reqId:H,status:Ct.status,headers:{"content-type":Ct.headers.get("content-type")||"application/octet-stream"}}),Ct.body){for await(let De of Ct.body)if(!M({v:xt,ch:"control",type:"http_data",reqId:H,data:Buffer.from(De).toString("base64")}))break}M({v:xt,ch:"control",type:"http_end",reqId:H,error:null})}finally{T.delete(H)}}function $(N,H,_){let{reqId:W,method:X,path:lt,body:tt}=N,wt=new URL(s);return new Promise(Ct=>{let De=HS.request({host:wt.hostname,port:wt.port,method:X,path:lt,headers:{...H,host:N.host},signal:_.signal},Dt=>{let{connection:y,"keep-alive":B,"transfer-encoding":dn,upgrade:zr,...je}=Dt.headers;M({v:xt,ch:"control",type:"http_head",reqId:W,status:Dt.statusCode||502,headers:je}),Dt.on("data",Is=>{M({v:xt,ch:"control",type:"http_data",reqId:W,data:Buffer.from(Is).toString("base64")})||Dt.destroy()}),Dt.on("end",()=>{M({v:xt,ch:"control",type:"http_end",reqId:W,error:null}),Ct()}),Dt.on("error",()=>{M({v:xt,ch:"control",type:"http_end",reqId:W,error:"view upstream error"}),Ct()})});De.on("error",Dt=>{M({v:xt,ch:"control",type:"http_end",reqId:W,error:Dt?.message||"view request failed"}),Ct()}),tt&&De.write(Buffer.from(tt,"base64")),De.end()})}function z(){if(g)return;w=new xc(t,{headers:{authorization:`Bearer ${e}`}}),w.on("open",()=>{M({v:xt,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:hs,providers:KS()})}),w.on("message",H=>{let _;try{_=JSON.parse(H)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){h=!0,x=Tm,a.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&hs&&GS(hs,_.minNodeVersion)&&a.error(`[plane-link] this node is bostrat-node ${hs}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),v&&clearInterval(v),v=setInterval(()=>M({v:xt,ch:"ctl",type:"ping"}),zS),v.unref?.(),p().catch(W=>a.error("[plane-link] backfill failed:",W?.message||W));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let W of _.ids){let X=f.get(W);X&&(f.delete(W),(d[X.trackId]??0)<X.seq&&(d[X.trackId]=X.seq))}I();return}if(_.ch==="control"&&_.type==="http_open"){ot(_).catch(W=>M({v:xt,ch:"control",type:"http_end",reqId:_.reqId,error:W?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){T.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let W=new xc(i+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);c.set(_.streamId,W),l.set(_.streamId,[]),W.on("open",()=>{let lt=l.get(_.streamId)||[];l.delete(_.streamId);for(let{buf:tt,bin:wt}of lt)W.send(tt,{binary:wt})}),W.on("message",(lt,tt)=>{let wt=tt?lt:Buffer.from(String(lt));M({v:xt,ch:"attach",type:"data",streamId:_.streamId,data:wt.toString("base64"),...tt?{bin:!0}:{}})});let X=()=>{l.delete(_.streamId),c.delete(_.streamId)&&M({v:xt,ch:"attach",type:"close",streamId:_.streamId})};W.on("close",X),W.on("error",X);return}if(_.ch==="attach"&&_.type==="data"){let W=c.get(_.streamId);if(!W)return;let X=Buffer.from(_.data,"base64"),lt=_.bin===!0,tt=l.get(_.streamId);tt?tt.push({buf:X,bin:lt}):W.send(X,{binary:lt});return}if(_.ch==="attach"&&_.type==="close"){let W=c.get(_.streamId);c.delete(_.streamId),l.delete(_.streamId),W?.close();return}});let N=()=>{h&&a.log("[plane-link] disconnected from plane"),h=!1,v&&(clearInterval(v),v=null),f.clear();for(let H of c.values())H.close();c.clear(),l.clear();for(let H of T.values())H.abort();T.clear(),!(g||A)&&(A=setTimeout(()=>{A=null,z()},x),A.unref?.(),x=Math.min(x*2,VS))};w.on("close",N),w.on("error",N)}return Mt.on("event",b),J().then(z),{stop:()=>{g=!0,Mt.off("event",b),A&&clearTimeout(A),S&&clearTimeout(S),v&&clearInterval(v),w?.close()},status:()=>({connected:h,nodeId:n,cursor:{...d},inflight:f.size})}}function XS(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 Em=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Im=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Sc=t=>Math.round(t*1e4)/1e4;async function Rm(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=Em(),u=0,d=[];for(let f of t||[]){let w=await P(f),h=is(w,{boxLabels:XS(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 g of h.unpricedModels||[])c.add(g);Im(l,h.totalTokens);for(let[g,x]of Object.entries(h.byModel))n[g]||={usd:0,events:0,tokens:Em()},n[g].usd=Sc(n[g].usd+x.usd),n[g].events+=x.events,Im(n[g].tokens,x.tokens);for(let[g,x]of Object.entries(h.byProvider))r[g]||={usd:0,events:0},r[g].usd=Sc(r[g].usd+x.usd),r[g].events+=x.events;e.push({id:f.id,name:f.name,usd:h.totalUsd,rollup:h})}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:Sc(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 Fr=t=>`$${t.toFixed(4)}`,Yn=(t,e)=>String(t).padEnd(e);function $m(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 ${Fr(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(` ${Yn(a,26)} ${Yn(Fr(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(` ${Yn(a.name||a.id,30)} ${Fr(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Yn(c.label,40)} ${Yn(Fr(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))}${Yn(Fr(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
235
|
+
`)}import sv from"node:fs/promises";import av from"node:path";var YS=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],ZS={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 gs(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of YS){let o=0;for(let s of ZS[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var QS=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),tv=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Om(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))QS.has(r)&&(e+=o||0),tv.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function vc(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 ys=t=>t.payload?.ts??t.ts,ev=12e4,Ce=t=>Math.round(t*1e4)/1e4,nv=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function rv(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>ys(S)>=e,a=gs([vc(n?.name),vc(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 v=gs(S.payload.task);v&&i.set(S.boxId,v)}let c=new Map,l=S=>{let v=S||"unattributed";return c.has(v)||c.set(v,{segments:[u(null)],lastTs:null,provider:null}),c.get(v)},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,v)=>ys(S)-ys(v)),f=0,w=0,h=0,g={turns:0};for(let S of d){let v=l(S.boxId),E=v.segments[v.segments.length-1],J=ys(S);switch(v.lastTs!=null&&J>v.lastTs&&s(S)&&(E.activeMs+=Math.min(J-v.lastTs,ev)),v.lastTs=J,S.provider&&(v.provider=S.provider),S.type){case"turn.user":{let I=S.payload?.text;if(S.source==="hook"||!String(I||"").trim())break;let M=u(gs(I));v.segments.push(M),s(S)&&(M.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let I=S.payload?.tool;I&&(E.tools[I]=(E.tools[I]||0)+1),s(S)&&(E.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let I=S.payload||{},M={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},m=Number(I.usd)||0;!m&&I.tokens&&(m=Kt(I.model,M)),!m&&I.tokens&&!No(I.model)&&(g.turns+=1),E.usd+=m,E.tokens.input+=M.input,E.tokens.output+=M.output,E.tokens.cacheRead+=M.cacheRead,E.tokens.cacheWrite+=M.cacheWrite;let p=I.model||"unknown";E.models[p]=(E.models[p]||0)+m,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"&&(h+=1);break;default:break}}let x=new Map,A=!1;for(let[S,v]of c)for(let E of v.segments){if(!(E.usd>0||E.activeMs>0||E.turns>0||E.toolCalls>0))continue;let I=E.textCat?.category??i.get(S)?.category??Om(E.tools)?.category??a?.category??"uncategorized";x.has(I)||x.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let M=x.get(I);M.usd+=E.usd,M.activeMs+=E.activeMs,M.turns+=E.turns,M.toolCalls+=E.toolCalls;for(let[p,b]of Object.entries(E.models))M.models[p]=(M.models[p]||0)+b;let m=E.provider||v.provider;m&&(M.providers[m]=(M.providers[m]||0)+E.usd),m==="cursor"&&E.usd===0&&(A=!0)}return{perCategory:x,handoffs:f,merged:w,failed:h,sawCursorActivity:A,unpricedTurns:g.turns}}function Mm(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:v,merged:E,failed:J,sawCursorActivity:I,unpricedTurns:M}=rv(g,{since:r});I&&(i=!0),c+=M;let m={trackId:x?.id,name:x?.name||x?.id,archived:!!x?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:v,subtasksMerged:E,subtasksFailed:J};for(let[p,b]of S){m.usd+=b.usd,m.activeMs+=b.activeMs,m.turns+=b.turns,m.categories[p]={usd:Ce(b.usd),activeMs:b.activeMs};for(let[$,z]of Object.entries(b.models))m.models[$]=Ce((m.models[$]||0)+z);for(let[$,z]of Object.entries(b.providers))m.providers[$]=Ce((m.providers[$]||0)+z);o.has(p)||o.set(p,nv());let T=o.get(p);T.usd+=b.usd,T.activeMs+=b.activeMs,T.turns+=b.turns,T.toolCalls+=b.toolCalls;for(let[$,z]of Object.entries(b.models))T.models[$]=(T.models[$]||0)+z;for(let[$,z]of Object.entries(b.providers))T.providers[$]=(T.providers[$]||0)+z;let ot=T.byTrack.get(x?.id)||{usd:0,activeMs:0};ot.usd+=b.usd,ot.activeMs+=b.activeMs,T.byTrack.set(x?.id,ot)}(m.usd>0||m.activeMs>0||m.turns>0)&&(m.usd=Ce(m.usd),m.dominantCategory=ov(m.categories),s.push(m))}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])),h=[...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:Cm(x.models),providers:Cm(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:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var ov=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},Cm=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ce(n)]));var Tc={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function iv(t){let e=String(t||"7d");return e in Tc?{key:e,ms:Tc[e]}:{key:"7d",ms:Tc["7d"]}}function cv(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=ko(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 lv(t){if(!t?.cwd)return null;try{let n=(await sv.readFile(av.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 Nm(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=iv(e),s=[];for(let a of t||[]){let i=await P(a);s.push({track:a,events:cv(a,i),brief:await lv(a)})}return{window:r,...Mm(s,{windowMs:o,now:n})}}import Zn from"node:fs/promises";import uv from"node:fs";import ft from"node:path";import dv from"node:os";import{execFile as fv}from"node:child_process";import{promisify as pv}from"node:util";var mv=pv(fv),Pm=process.env.BOSTRAT_STATE_DIR||ft.join(process.env.HOME||dv.tmpdir(),".bostrat"),Ac=!1;async function Hr(t,e){let n=await Zn.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await Zn.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await Zn.mkdir(ft.dirname(e),{recursive:!0}),await Zn.copyFile(t,e),!0)}var hv=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Wr(t,e){let n=0,r=await Zn.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(hv.has(o.name))continue;let s=ft.join(t,o.name);o.isDirectory()?n+=await Wr(s,ft.join(e,o.name)):o.isFile()&&(n+=await Hr(s,ft.join(e,o.name))?1:0)}return n}async function Bm({stateDir:t=Pm,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Pm}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await Zn.mkdir(e,{recursive:!0});let o=0;o+=await Hr(ft.join(t,"tracks.json"),ft.join(e,"tracks.json"))?1:0,o+=await Hr(ft.join(t,"mocks.json"),ft.join(e,"mocks.json"))?1:0,o+=await Hr(ft.join(t,"runbooks.json"),ft.join(e,"runbooks.json"))?1:0,o+=await Wr(ft.join(t,"runbooks"),ft.join(e,"runbooks")),o+=await Wr(ft.join(t,"mock-archive"),ft.join(e,"mock-archive")),o+=await Hr(ft.join(t,"events.jsonl"),ft.join(e,"events.jsonl"))?1:0,o+=await Wr(ft.join(t,"events"),ft.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Wr(ft.join(l.cwd,".bostrat"),ft.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>mv("git",["-C",e,...l]);uv.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 ia(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||Ac))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Ac=!1}catch{Ac=!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 wv}from"node:child_process";import{promisify as gv}from"node:util";import{randomUUID as yv}from"node:crypto";import bv from"node:fs";import kv from"node:os";import xv from"node:path";var Ne=gv(wv),bs=t=>new Promise(e=>setTimeout(e,t)),Pe=Ms,_c=process.env.BOSTRAT_USAGE_PROBE_CWD||xv.join(kv.homedir(),".bostrat","usage-probe"),Sv=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,vv=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,Tv=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Dm=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Me=null,qr=null,Gr=null;async function Av(){try{return await Ne(O,C("has-session","-t",Pe)),!0}catch{return!1}}function _v(){Me&&clearTimeout(Me),Me=setTimeout(()=>Ev().catch(()=>{}),Sv),Me.unref&&Me.unref()}async function Ev(){Me&&(clearTimeout(Me),Me=null);try{await Ne(O,C("kill-session","-t",Pe))}catch{}}async function Iv(){let t=Date.now()+Tv;for(;;){let e=await Ec().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await bs(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await bs(500)}}async function Rv(){if(await Av())return;if(!await ee())throw new Error("host tmux server not running \u2014 start globo-tmux.service");bv.mkdirSync(_c,{recursive:!0}),await pe(_c);let t=yv();await Ne(O,C("new-session","-d","-s",Pe,"-c",_c,"-x","200","-y","60"));let e=`${Cs} --session-id ${t}`;await Ne(O,C("send-keys","-t",Pe,e,"Enter")),await Iv()}async function Ec(){let{stdout:t}=await Ne(O,C("capture-pane","-t",Pe,"-p"));return t}async function $v(){await Rv(),await Ne(O,C("send-keys","-t",Pe,"-l","/usage")),await Ne(O,C("send-keys","-t",Pe,"Enter"));let t=Date.now()+vv,e="";for(;await bs(400),e=await Ec(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await bs(900),e=await Ec(),await Ne(O,C("send-keys","-t",Pe,"Escape")).catch(()=>{}),_v(),e}function Ov(t){let e=String(t||"").split(`
|
|
236
|
+
`).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 Cv(t){let e=String(t||"").split(`
|
|
237
|
+
`).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 jm({fresh:t=!1}={}){return!t&&qr&&Date.now()-qr.at<Dm?qr.data:Gr||(Gr=(async()=>{try{let e=await $v(),n=Ov(e),r={...n,contributors:Cv(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return qr={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 qr={at:Date.now()-(Dm-3e3),data:n},n}finally{Gr=null}})(),Gr)}var Mv=262144;function Nv(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 Lm(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:Nv(t.resets_in_seconds)}:null}function Pv(t){let e=String(t||"").split(`
|
|
238
|
+
`),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:Lm(i.primary),week:Lm(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 Bv(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await Ge(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 St(n.file,Mv),o=Pv(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 Um(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?Bv(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await jm({fresh:e})}}function Fm(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 Dv(t){let[e,n]=t.split("/"),r=Fm(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 jv(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(Dv).filter(Boolean)}function Lv(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 Hm(t){let e=jv(t),n=e.length===0;return{open:n,clientIp:Lv,allowed(r){if(n)return!0;let o=Fm((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import Uv from"node:http";import Fv from"node:net";function Wm(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 qm(t){let e=Wm(t.headers.host);return e?{slug:e,view:ui(e)}:null}var Hv=t=>`<!doctype html><meta charset="utf-8">
|
|
239
|
+
<meta http-equiv="refresh" content="3">
|
|
240
|
+
<title>${t.name} \u2014 starting</title>
|
|
241
|
+
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
242
|
+
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
243
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function Gm(t,e,n){let r=qm(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(Hv(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=Uv.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 Km(t,e,n){let r=qm(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
|
|
244
|
+
\r
|
|
245
|
+
bostrat: no live view "${s}"\r
|
|
246
|
+
`),e.destroy(),!0;let a=Fv.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
|
|
247
|
+
`)+`\r
|
|
248
|
+
\r
|
|
249
|
+
`),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 Vm(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}.${Wv(n.host)}/`}function Wv(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 Wm(e)&&ui(n[0])?n.slice(1).join("."):e}import{execFile as qv}from"node:child_process";import{promisify as Gv}from"node:util";var Cc=Gv(qv),Xm=3e4,Kv=30*6e4,Jm=Xm,Vv=5*6e4,Jv=10080*6e4,zv=60*6e4,Xv=3,Ym="number,url,title,headRefName,headRefOid,state",Oc=60*6e4,zm=new Map;function Yv(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Oc)}function Zv(t,e){return t.armedAt&&e-t.armedAt<Kv||Yv(t,e)?Jm:e-(t.firstSeen||0)>Jv?zv:Vv}async function Qv(t,e){let{stdout:n}=await Cc("gh",["pr","view",String(e),"--repo",t,"--json",Ym],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await Gt({slug:t})}});return JSON.parse(n||"null")}async function tT(t){let{stdout:e}=await Cc("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Ym],{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 eT(){let t=new Map;for(let e of await dt().catch(()=>[]))t.set(e.id,e);return t}async function nT(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 rT(t,e){let n=await gi(t);return n?Tr(n,e):null}async function oT(t,e,n){let r=await gi(t);if(!r)throw new Error(`no local checkout for ${t}`);return Ar(r,e,{method:n})}async function sT(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 ks(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 aT=10*6e4;async function iT(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 ks(t,r,e,n,`check${r.checks.failed===1?"":"s"} failed: ${s}`);return}if(r.mergeable==="CONFLICTING"){await ks(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<aT)return;await ks(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 ks(t,r,e,n,String(s?.message||s).trim()||"merge failed");return}await Zm(t,{...r,state:"MERGED"},e,n)}}async function Ic(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 cT(t,e){try{return await Cc("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Rc(t,e,n,r=()=>!0){return(await P(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var $c=(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 lT(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 To(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 gd(t.repo,t.number,s).catch(()=>{}),!i&&!c&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Rc(a,"pushed",t.number,u=>u.payload?.sha===s)&&await Ic(a,"pushed",{...$c(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Oc)await Ao(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 Ao(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=t.ciFixAwait.since||0;a&&!await Rc(a,"ci-fixed",t.number,l=>(l.payload?.ts||0)>=c)&&await Ic(a,"ci-fixed",{...$c(t,e),passed:i.checks.passed}).catch(()=>{}),await Ao(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Oc)await Eo(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Eo(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){let c=t.reconcileAwait.since||0;a&&!await Rc(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Ic(a,"reconciled",{...$c(t,e),base:i.baseRefName||null}).catch(()=>{}),await Eo(t.repo,t.number).catch(()=>{})}}}}async function Zm(t,e,n,r){let o=n.get(t.trackId);o&&((await P(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.mark(t.repo,t.number,"merged").catch(()=>{})}async function uT(t={}){let{now:e=Date.now(),auth:n=mi,view:r=Qv,list:o=tT,links:s=yd,trackMap:a=eT,emitMerged:i=nT,mark:c=ze,batchThreshold:l=Xv,detail:u=rT,merge:d=oT,lookup:f=Pt,disarm:w=Ro,emitDisarmed:h=sT,localHasCommit:g=cT}=t;if(!(await n().catch(()=>null))?.authenticated)return 0;let S=(await s().catch(()=>[])).filter(I=>e-(zm.get(kt(I.repo,I.number))||0)>=Zv(I,e));if(!S.length)return 0;let v=new Map;for(let I of S)zm.set(kt(I.repo,I.number),e),v.has(I.repo)||v.set(I.repo,[]),v.get(I.repo).push(I);let E=await a().catch(()=>new Map),J={now:e,emitMerged:i,mark:c,detail:u,merge:d,lookup:f,disarm:w,emitDisarmed:h,localHasCommit:g};for(let[I,M]of v){let m;if(M.length>l)m=await o(I).catch(()=>null);else{m=new Map;for(let p of M){let b=await r(I,p.number).catch(()=>null);b?.number!=null&&m.set(Number(b.number),b)}}if(m)for(let p of M){let b=m.get(Number(p.number));b&&(b.state==="MERGED"?await Zm(p,b,E,J):b.state==="CLOSED"?await c(p.repo,p.number,"closed").catch(()=>{}):(await lT(p,b,E,J).catch(()=>{}),p.autoMerge&&await iT(p,E,J).catch(()=>{})))}}return S.length}function Qm(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await uT()}catch{}finally{t=!1}}},Xm)}import de from"node:fs/promises";import Qn from"node:path";import ah from"node:os";import th from"node:fs/promises";import xs from"node:path";import{execFile as dT}from"node:child_process";import{promisify as fT}from"node:util";import{fileURLToPath as pT}from"node:url";var mT=fT(dT),nh=xs.dirname(pT(import.meta.url)),hT=xs.resolve(nh,"..","..","client","dist"),wT="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",gT=new Map([...wT].map((t,e)=>[t,e]));function yT(t){let e=[],n=0,r=0;for(let o of t){let s=gT.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 bT(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=yT(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 kT(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 eh=new Map;async function xT(t){let e;try{e=await th.stat(t)}catch{return null}let n=eh.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await th.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:bT(r.mappings||"")};return eh.set(t,o),o}catch{return null}}var Mc=null;function ST(){return Mc||(Mc=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):mT("git",["rev-parse","--short","HEAD"],{cwd:xs.resolve(nh,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Mc}var vT=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function TT(t,{distDir:e=hT}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(vT)){let[a,i,c,l]=s,u=await xT(xs.join(e,"assets",`${i}.map`)),d=u?kT(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 rh(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await ST();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await TT(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var AT=()=>process.env.BOSTRAT_STATE_DIR||Qn.join(process.env.HOME||ah.tmpdir(),".bostrat"),Be=()=>Qn.join(AT(),"telemetry"),ih=()=>Qn.join(Be(),".relay-offsets.json"),_T=50,ET=14,IT=3e4,oh=500,Ot=(t,e)=>typeof t=="string"?t.slice(0,e):"";function RT(t,e){if(!t||typeof t!="object")return null;let n=Ot(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:Ot(t.anonId,64),sessionId:Ot(t.sessionId,64),userId:Ot(t.userId,64),release:Ot(t.release,64),appVersion:Ot(t.appVersion,32),route:Ot(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:Ot(t.error.kind,256),message:Ot(t.error.message,8192),stack:Ot(t.error.stack,32768),fingerprint:Ot(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:Ot(s?.n,64),r:Ot(s?.r,128)}))),o}var ch=t=>new Date(t).toISOString().slice(0,10),sh=0;async function lh(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,_T):[]).map(s=>RT(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await rh(s);await de.mkdir(Be(),{recursive:!0});let o=Qn.join(Be(),`${ch(e)}.ndjson`);return await de.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
250
|
+
`).join(""),"utf8"),e-sh>6*3600*1e3&&(sh=e,$T(e).catch(()=>{})),r.length}async function $T(t=Date.now()){let e=ch(t-ET*24*3600*1e3),n=[];try{n=await de.readdir(Be())}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 de.rm(Qn.join(Be(),s),{force:!0});let o=await dh();for(let s of r)delete o[s];await fh(o)}var Ss=new Map;function uh(t,e=Date.now()){let n=t||"unknown",r=Ss.get(n);return r||(Ss.size>512&&Ss.clear(),r={tokens:40,at:e},Ss.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 dh(){try{return JSON.parse(await de.readFile(ih(),"utf8"))||{}}catch{return{}}}async function fh(t){await de.mkdir(Be(),{recursive:!0}),await de.writeFile(ih(),JSON.stringify(t),"utf8")}function OT(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||ah.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 CT({fetchImpl:t=fetch}={}){let e=await Up();if(!e)return{sent:0,disabled:!0};let n;try{n=(await de.readdir(Be())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await dh(),o=0;for(let s of n){let a=Qn.join(Be(),s),i=r[s]||0,c;try{let f=await de.open(a,"r");try{let w=(await f.stat()).size;if(w<=i)continue;c=Buffer.alloc(w-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(`
|
|
251
|
+
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=oh){let w=d.slice(f,f+oh).map(g=>OT(g)),h=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(!h||!h.ok)return{sent:o,disabled:!1};o+=w.length}r[s]=i+l+1,await fh(r)}return{sent:o,disabled:!1}}var Nc=null,Pc=!1;function ph(){Nc||(Nc=setInterval(async()=>{if(!Pc){Pc=!0;try{await CT()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Pc=!1}}},IT),Nc.unref?.())}import Ts from"node:fs/promises";import yh from"node:path";import MT from"node:os";var bh=()=>process.env.BOSTRAT_STATE_DIR||yh.join(process.env.HOME||MT.tmpdir(),".bostrat"),kh=()=>yh.join(bh(),"dictation-glossary.json"),NT=100,PT=500,BT=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],As=null,Bc=null,Dc=Promise.resolve();function mh(){let t={},e=Date.now();for(let n of BT)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function xh(){return As||(Bc??=Ts.readFile(kh(),"utf8").then(t=>JSON.parse(t)?.terms??mh()).catch(()=>mh()).then(t=>As=t).finally(()=>Bc=null),Bc)}async function hh(){let t=JSON.stringify({terms:As},null,2),e=kh(),n=`${e}.${process.pid}.tmp`;await Ts.mkdir(bh(),{recursive:!0}),await Ts.writeFile(n,t),await Ts.rename(n,e)}function DT(){return Dc=Dc.then(hh,hh),Dc.catch(()=>{})}async function jc(){return Object.values(await xh()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var jT=t=>Math.max(1,Math.ceil(t.length/4));async function LT(){let t=[],e=0;for(let{term:n}of await jc()){let r=jT(n);if(!(e+r>PT)&&(t.push(n),e+=r,t.length>=NT))break}return t}async function UT(t,e){let n=await xh(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var wh=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),vs=t=>t.toLowerCase();function gh(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Sh(t,e){let n=wh(t),r=wh(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]=vs(n[u])===vs(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;)vs(n[c])===vs(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(gh(r[l])&&i.add(r[l]),l++);for(;l<s;l++)gh(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await UT(u,"learned");return await DT(),[...i]}var Kr=null;function FT(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 vh(){if(!Kr)return null;let t=await Nr();if(!t||t.apiKey.slice(-6)!==Kr.keyTail)return null;let{keyTail:e,...n}=Kr;return n}async function Th(t,e){let n=await Nr();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 LT())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=FT(o.status,i);c.persistent&&(Kr={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 Kr=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}import HT from"node:path";import WT from"node:os";var QO=process.env.BOSTRAT_STATE_DIR||HT.join(process.env.HOME||WT.tmpdir(),".bostrat");function Ah(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}var $h=ct.dirname(GT(import.meta.url)),_h=Number(process.env.PORT)||3362,_s=Ah(process.env),Oh=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,tr=Hm(process.env.BOSTRAT_ALLOW_IPS),k=fe();k.disable("x-powered-by");k.use((t,e,n)=>{if(tr.allowed(tr.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});k.use(Gm);k.use("/api/mocks",fe.json({limit:"1mb"}));k.use("/api/mocks",fe.raw({type:"image/png",limit:"24mb"}));k.use("/api/runbooks",fe.json({limit:"1mb"}));k.use("/api/telemetry",fe.json({limit:"256kb"}));k.use("/api/dictation",fe.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));k.use("/api/tracks/:id/upload",fe.raw({type:()=>!0,limit:"25mb"}));k.use(fe.json({limit:"64kb"}));k.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ht,hostTmuxAlive:await ee(),autoTriggerPct:Oh})});k.get("/api/sessions",async(t,e)=>{e.json({sessions:await yn()})});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 q(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 q(n);if(!r)return e.status(404).json({error:"session not found"});let o=await sr(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 _l(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=co(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:Bi,tickets:await nm()})});k.get("/api/cost",async(t,e)=>{try{let n=await Rm(await dt());if(n.cursorUsage=Ld(),t.query.format==="text")return e.type("text/plain").send($m(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 Nm(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 vm(t.body||{},dp);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(!uh(tr.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await lh(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 oe(),r=new Set((await Promise.all(n.map(a=>Z(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await lr()).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 fo(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:uo()})});k.post("/api/github/refresh",(t,e)=>{Ul(),e.json({ok:!0})});async function Ch({repo:t=null,number:e=null,branch:n=null}){let r=t?await Z(t):null;if(r&&e!=null){let s=await Pt(r,e),a=s&&await rt(s.trackId);if(a)return a}return n?(await dt()).find(s=>s.workspace?.branch===n&&(!s.workspace?.base||!t||s.workspace.base===t||r&&s.workspace.repo===r))??null:null}async function un(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Ch({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 zT(t){let e=(await dt()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Z(n.path);if(r)for(let o of n.prs){if(await Pt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&(!a.workspace?.base||a.workspace.base===n.path||a.workspace.repo===r));s&&await Nt({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 ff();await zT(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,headRefName:a,title:i,url:c}=t.body||{};try{if(s){let d=o||"merge";if(!qo.has(d))throw new Error(`invalid merge method: ${d}`);let f=Number(r);if(!Number.isInteger(f)||f<=0)throw new Error("invalid PR number");let w=await Z((await Te(n)).path);if(!w)throw new Error("repo has no GitHub remote");let h=await Ch({repo:n,number:f,branch:a}).catch(()=>null);if(!h)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(await Nt({repo:w,number:f,trackId:h.id,url:c,headRef:a,title:i,source:"matched"}).catch(()=>{}),!await Io(w,f,{merge:{method:d}}))throw new Error("couldn't record the auto-merge arm");return Xe(),await un("auto-armed",{number:f,url:c,title:i,branch:a,repo:n,track:h}),e.json({ok:!0,number:f,auto:!0})}let l=await Ar(n,r,{method:o||"squash"});await un("merged",{number:l.number,url:c,title:i,branch:a,repo:n});let u=await Z(n).catch(()=>null);u&&await ze(u,l.number,"merged").catch(()=>{}),e.json(l)}catch(l){let u=l?.message||String(l),d=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(u)?400:502;e.status(d).json({error:u})}});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 Z((await Te(n)).path).catch(()=>null),l=c?await Ro(c,i).catch(()=>null):null;try{await pf(n,i)}catch(u){if(!l)throw u}Xe(),await un("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 Z(n).catch(()=>null),s=await wf(n,{method:r||"merge",onMerged:a=>{un("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&ze(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:hf()})});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"});let i=await gf(a.cwd,{title:r,body:o,base:s}),c=await Z(a.cwd).catch(()=>null);c&&i.number&&await Nt({repo:c,number:i.number,trackId:a.id,url:i.url,headRef:a.workspace?.branch??null,title:r,source:"api"}),await un("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 Tr(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 yf(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 Et.promises.mkdir(c,{recursive:!0}),await Et.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
|
|
252
|
+
${w.log??"(no fetchable log \u2014 see link)"}
|
|
253
|
+
`).join(`
|
|
254
|
+
`));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 Z(n).catch(()=>null);f&&(await Nt({repo:f,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await hd(f,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await un("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 Tr(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 Z(n).catch(()=>null);c&&(await Nt({repo:c,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await wd(c,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await un("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=ld.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 P(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,xo(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 Vr=t=>e=>({...e,url:Vm(e,t.headers)});k.get("/api/views",async(t,e)=>{try{let n=await Zd({trackId:t.query.track||void 0});e.json({views:n.map(Vr(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 Qd(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Vr(t)(n),session:Mn(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 di({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:Vr(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:Vr(t)(await tf(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:Vr(t)(await ef(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 nf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var XT=[["linear",rn,Zi],["jira",ss,Xi]],Lc=new Map;k.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await zi(),r={};for(let[o,s,a]of XT){let i=await s();if(!i){Lc.delete(o),r[o]={configured:!1};continue}let c=Lc.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},Lc.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 zi()})}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 Lp(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 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})}});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 rm(),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 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})}});k.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await lc(t.params.id)})});var Es=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 im(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Es(r)).json({error:r})}});k.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await uc(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Es(r)).json({error:r})}});k.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await cm(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Es(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 lm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Es(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 dm(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 um()})}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 fm(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 Rf({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=Ze(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(Bt(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=Ze(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(Bt(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 $f({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 Of(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 Mf(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 Nf(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 Hf({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 Ti(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 Ti(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Lf(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(jn(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 Wf({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 qf(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 Gf(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 Kf(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 Th(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 Nr(),terms:await jc(),problem:await vh()})});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 Sh(n,r)})});k.post("/api/tracks",async(t,e)=>{let{name:n,cwd:r,repo:o,agent:s,skipPermissions:a,ticket:i}=t.body||{};try{let c=await Fi({name:n,cwd:r,repo:o,agent:s,skipPermissions:a!==!1}),l=null;if(i)try{await Jn(c,i,{source:"create"})}catch(u){l=u?.message||String(u)}e.json({ok:!0,track:c,...l?{ticketWarning:l}:{}})}catch(c){let l=c?.message||String(c);e.status(/required|unknown agent|invalid|must be/i.test(l)?400:500).json({error:l})}});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:Mr(t.params.id),subtasks:Gn(t.params.id).map(Jr)})});k.delete("/api/tracks/:id",async(t,e)=>{try{let n=await vp(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await lc(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 ns(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 Tp(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 ji(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}
|
|
255
|
+
data: ${JSON.stringify(i)}
|
|
256
|
+
|
|
257
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await P(n))i.seq>o&&r(i);let s=Zu(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
258
|
+
|
|
259
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});k.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await _p(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})}});k.post("/api/tracks/:id/say",async(t,e)=>{let{text:n,attachments:r}=t.body||{};try{e.json(await $e(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})}});k.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await rt(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 Zf(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})}});k.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await rt(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||!_i(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(Ai(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 YT=/^[0-9a-fA-F-]{8,64}$/;k.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await rt(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(!YT.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=ne(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=qT.createInterface({input:Et.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let f of u){if(!f.includes(c))continue;let w;try{w=JSON.parse(f)}catch{continue}if(w.uuid===o){l=w,u.close();break}}let d=l?Hs(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)})}});k.post("/api/tracks/:id/interrupt",async(t,e)=>{try{e.json(await Ui(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})}});k.delete("/api/tracks/:id/queue",(t,e)=>{e.json({ok:!0,removed:es(t.params.id)})});k.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await Li(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})}});k.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await xp({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})}});k.post("/api/tracks/:id/navigate-prompt",async(t,e)=>{let{promptId:n,questionIndex:r}=t.body||{};try{e.json(await pp(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})}});k.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n}=t.body||{};try{e.json(await gp(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})}});k.get("/api/tracks/:id/usage",async(t,e)=>{let n=await rt(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 Um(r,{fresh:o}))});k.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await yp(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})}});k.post("/api/tracks/:id/subtask",async(t,e)=>{let{agent:n,task:r,skipPermissions:o,reason:s}=t.body||{};try{let a=await Cp(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Jr(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})}});k.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:Gn(t.params.id).map(Jr)})});k.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await fp(t.params.id,t.params.sid);if(!n)return e.status(404).json({error:"subtask not found"});e.json(n)});k.post("/api/tracks/:id/subtask/:sid/cancel",(t,e)=>{try{e.json({ok:!0,subtask:Jr(Gi(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});k.post("/api/tracks/:id/subtask/:sid/merge-now",(t,e)=>{try{e.json({ok:!0,subtask:Jr(Np(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Jr(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}k.post("/api/tracks/:id/handoff-lead",async(t,e)=>{let{toAgent:n,skipPermissions:r,reason:o}=t.body||{};try{let s=await Wi(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})}});k.post("/api/tracks/:id/spin-up-view",async(t,e)=>{try{e.json(await wp(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})}});k.get("/api/tracks/:id/handoff-lead",(t,e)=>{let n=Mr(t.params.id);if(!n)return e.json({running:!1});e.json({running:!n.finishedAt,step:n.step,error:n.error,fromBox:n.fromBox,toBox:n.toBox,toAgent:n.toAgent,skipPermissions:n.skipPermissions,startedAt:n.startedAt,finishedAt:n.finishedAt})});k.get("/api/debug/handoff",async(t,e)=>{let n=Math.min(Number(t.query.limit)||80,500);e.json({verbose:Zr(),paths:zc,trace:await Jc({limit:n})})});var Uc=ct.resolve($h,"..","..","client","dist");Et.existsSync(Uc)&&(k.use(fe.static(Uc)),k.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ct.join(Uc,"index.html"))));var Mh=KT.createServer(k),ZT=new JT({noServer:!0}),Nh=(t,e,n)=>{if(!tr.allowed(tr.clientIp(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
260
|
+
\r
|
|
261
|
+
`),e.destroy();return}if(Km(t,e,n))return;let r=ol(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
262
|
+
\r
|
|
263
|
+
`),e.destroy();return}let o=r.sessionName;if(!ut(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
264
|
+
\r
|
|
265
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;ZT.handleUpgrade(t,e,n,i=>el(i,o,{cols:s,rows:a}))};Mh.on("upgrade",Nh);var Ph=process.env.BOSTRAT_CERT_DIR||ct.join(process.env.HOME||Fc.tmpdir(),".bostrat","certs"),Eh=Number(process.env.BOSTRAT_TLS_PORT)||3443,Ih=ct.join(Ph,"bostrat.globopi.key"),Rh=ct.join(Ph,"bostrat.globopi.crt");if(Et.existsSync(Ih)&&Et.existsSync(Rh)){let t=VT.createServer({key:Et.readFileSync(Ih),cert:Et.readFileSync(Rh)},k);t.on("upgrade",Nh),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(Eh,_s,()=>{console.log(`[bostrat-agent] https listening on ${_s||"0.0.0.0"}:${Eh}`)})}Mh.listen(_h,_s,()=>{console.log(`[bostrat-agent] listening on ${_s||"0.0.0.0"}:${_h}`),console.log(`[bostrat-agent] tmux socket: ${Ht}`),console.log(`[bostrat-agent] auto-trigger default: ${Oh}%`),console.log(`[bostrat-agent] ip allowlist: ${tr.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),Rp().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),$p().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),Ap().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)),kp().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)),nA(),eA(),Qm(),pm({resolveTrack:rt}),Sm({listTracks:dt,createTrack:Fi,archiveTrack:ns,listRepos:oe,resolveTrack:rt,sayToLead:$e,answerPrompt:Li,startLeadHandoff:Wi,interruptLead:Ui,listSubtaskJobs:Gn,cancelSubtask:Gi,readEvents:P,appendEvent:R,knownAgents:Bi}),QT(),ph(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(_m({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Fc.hostname(),name:process.env.BOSTRAT_NODE_NAME||Fc.hostname(),listTracks:dt}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function QT(){tA(),Uo().catch(()=>{}),setInterval(()=>Uo().catch(()=>{}),1e4)}function tA(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","gh"])try{let e=ct.resolve($h,"..","bin",t),n=ct.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Et.existsSync(e))continue;Et.mkdirSync(n,{recursive:!0});let r=ct.join(n,t);(Et.existsSync(r)?Et.readlinkSync(r):null)!==e&&(Et.rmSync(r,{force:!0}),Et.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 eA(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await dt(),r=await Bm({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 nA(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await dt();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await ji(n.id).catch(()=>{}),await bf(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|