bostrat-node 0.2.20 → 0.2.22
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/dist/index.js +147 -146
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).includes(a));if(!s.length)return;await
|
|
1
|
+
var mx=Object.defineProperty;var bf=(t,e)=>{for(var n in e)mx(t,n,{get:e[n],enumerable:!0})};import ft from"node:path";import Kt from"node:fs";import tP from"node:readline";import{fileURLToPath as eP}from"node:url";import _e from"express";import nP from"node:http";import rP from"node:https";import wf from"node:os";import{WebSocketServer as oP}from"ws";import{execFile as DS}from"node:child_process";import{promisify as BS}from"node:util";import da from"node:fs";import{randomUUID as jS}from"node:crypto";import Gt from"node:fs/promises";import vf from"node:os";import Bt from"node:path";import{execFile as bx}from"node:child_process";import{promisify as kx}from"node:util";import{createHash as hx,randomBytes as wx,timingSafeEqual as gx}from"node:crypto";var Js="BOSTRAT_HOOK_TOKEN",Xs="x-bostrat-hook";function kf(){return wx(32).toString("hex")}function So(t){let e=typeof t=="string"?t.trim():"";return e?hx("sha256").update(e).digest("hex"):null}function xf(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:gx(Buffer.from(t),Buffer.from(e))}var pc=new Map,mc=new Map,yx=6e4;function Ys(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(pc.set(t,(pc.get(t)||0)+1),mc.has(t)&&n-mc.get(t)<yx)return!1;mc.set(t,n);let o=Object.entries(e).filter(([,s])=>s!=null&&s!=="").map(([s,a])=>`${s}=${a}`).join(" ");return r(`[hook-audit] ${t} n=${pc.get(t)}${o?` ${o}`:""}`),!0}async function Sf(t,{byToken:e,legacy:n}){let r=typeof t=="string"?t.trim():"";if(!r)return{mode:"legacy",target:await n()};let o=await e(r);return o?.track?{mode:"token",target:o}:{mode:"refused",target:null}}function Zs(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function xx(){return process.env.CLAUDE_CONFIG_DIR?Bt.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Bt.join(vf.homedir(),".claude.json")}function Sx(){return process.env.CLAUDE_CONFIG_DIR?Bt.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Bt.join(vf.homedir(),".claude","settings.json")}async function en(t){if(!t||typeof t!="string")return;let e=Bt.resolve(t),n=xx(),r=Sx(),o={};try{o=JSON.parse(await Gt.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 Gt.mkdir(Bt.dirname(n),{recursive:!0}),await Gt.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Gt.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' -H "${Xs}: $${Js}" --data-binary @- >/dev/null 2>&1 || true`,u=[{hooks:[{type:"command",command:l}]}];i.hooks=i.hooks||{};for(let f of["SessionStart","UserPromptSubmit","Stop"])i.hooks[f]=u;i.hooks.PostToolUse=[{matcher:"*",hooks:[{type:"command",command:l}]}]}await Gt.mkdir(Bt.dirname(r),{recursive:!0}),await Gt.writeFile(r,JSON.stringify(i,null,2)),await vx(e)}async function vx(t){try{let e=Bt.resolve(t),n=Bt.join(e,".bostrat","memory","claude"),r=Bt.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Gt.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes(Bt.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Gt.mkdir(n,{recursive:!0}),await Gt.mkdir(Bt.dirname(r),{recursive:!0}),await Gt.writeFile(r,JSON.stringify(o,null,2)),await Tx(e,["/.claude/settings.local.json"])}catch{}}async function Tx(t,e){try{let{stdout:n}=await kx(bx)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=Bt.resolve(t,n.trim()),o=await Gt.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
|
+
`).includes(a));if(!s.length)return;await Gt.mkdir(Bt.dirname(r),{recursive:!0}),await Gt.appendFile(r,(o&&!o.endsWith(`
|
|
3
3
|
`)?`
|
|
4
4
|
`:"")+s.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`)}catch{}}import
|
|
6
|
+
`)}catch{}}import hc from"node:fs/promises";import Ax from"node:os";import Qs from"node:path";function Ex(){let t=process.env.CODEX_HOME||Qs.join(Ax.homedir(),".codex");return Qs.join(t,"config.toml")}function Tf(t){return`[projects."${t}"]`}function _x(t,e){let n=Tf(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 mr(t){if(!t||typeof t!="string")return;let e=Qs.resolve(t),n=Ex(),r="";try{r=await hc.readFile(n,"utf8")}catch{}if(_x(r,e))return;let o=Tf(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),f=/trust_level\s*=/.test(u)?u.replace(/trust_level\s*=\s*"[^"]*"/,'trust_level = "trusted"'):`${u.replace(/\s*$/,"")}
|
|
7
7
|
trust_level = "trusted"
|
|
8
8
|
`;r=r.slice(0,a)+f+r.slice(l)}else{let s=`
|
|
9
9
|
${o}
|
|
10
10
|
trust_level = "trusted"
|
|
11
11
|
`;r=r.trimEnd()+s+(r.length&&!r.endsWith(`
|
|
12
12
|
`)?`
|
|
13
|
-
`:"")}try{await
|
|
14
|
-
`,"utf8")}import j from"node:fs/promises";import
|
|
13
|
+
`:"")}try{await hc.mkdir(Qs.dirname(n),{recursive:!0}),await hc.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Af from"node:fs/promises";import Ix from"node:os";import vo from"node:path";function Rx(t){let n=vo.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||vo.join(Ix.homedir(),".cursor");return vo.join(r,"projects",n)}async function hr(t){if(!t||typeof t!="string")return;let e=vo.resolve(t),n=Rx(e),r=vo.join(n,".workspace-trusted");await Af.mkdir(n,{recursive:!0}),await Af.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import j from"node:fs/promises";import nS from"node:os";import X from"node:path";import{execFile as rS}from"node:child_process";import{promisify as oS}from"node:util";import Fx from"node:fs";import _f from"node:fs/promises";import Ao from"node:path";import Ux from"node:os";import{fileURLToPath as Hx}from"node:url";import{execFile as Wx}from"node:child_process";import{promisify as qx}from"node:util";import gc from"node:fs/promises";import nn from"node:path";import{execFile as $x}from"node:child_process";import{promisify as Ox}from"node:util";var Cx=Ox($x),Ef=process.env.HOME||"/home/globo",Mx=nn.join(process.env.BOSTRAT_STATE_DIR||nn.join(Ef,".bostrat"),"repos"),Nx=[...(process.env.BOSTRAT_REPOS_ROOT||nn.join(Ef,"Documents")).split(","),Mx].map(t=>t.trim()).filter(Boolean),Px=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Dx=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function Bx(t){try{return(await gc.stat(nn.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function jx(t){let e=i=>gc.readFile(nn.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 Ie(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await Bx(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:nn.basename(o),branch:await jx(o)});return}if(s>=Px)return;let i;try{i=await gc.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||Dx.has(c.name)||await e(nn.join(o,c.name),s+1)}for(let o of Nx)await e(nn.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await et(o.path))?.toLowerCase();if(s){if(n.has(s))continue;n.add(s)}r.push(o)}return r.sort((o,s)=>o.name.localeCompare(s.name))}function Lx(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var wc=new Map;async function et(t){if(!t)return null;if(wc.has(t))return wc.get(t);let e=null;try{let{stdout:n}=await Cx("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=Lx(n)}catch{e=null}return wc.set(t,e),e}async function To(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await Ie())if((await et(n.path))?.toLowerCase()===e)return n.path;return null}var If=qx(Wx),Kx=process.env.BOSTRAT_STATE_DIR||Ao.join(process.env.HOME||Ux.tmpdir(),".bostrat"),Gx=Ao.join(Kx,"repos"),Rf=/^[^/\s]+\/[^/\s]+$/,Vx=5*6e4,zx=6e4,Jx=10*6e4,$f=fetch,yc=new Map,Bn=null;function Qt(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 ta(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Of=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),wr=(t,e)=>Object.assign(new Error(t),{status:e});async function ea(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Rf.test(n))throw wr("repo must be owner/name",400);let r=yc.get(n);if(r&&r.expiresAt-Date.now()>Vx)return r;if(!ta(e))throw wr("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await $f(`${Qt(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Of(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw wr(`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 wr(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 yc.set(n,a),a}async function Eo({env:t=process.env,force:e=!1}={}){if(!ta(t))return{configured:!1,repos:[],username:null};if(!e&&Bn&&Date.now()-Bn.at<zx)return Bn.data;try{let n=await $f(`${Qt(t)}/v1/github/repos`,{headers:Of(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return Bn={at:Date.now(),data:r},r}catch(n){return Bn?Bn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Cf(){yc.clear(),Bn=null}async function de({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await et(t):null);if(!r)return{};let{token:o}=await ea(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function bc({env:t=process.env}={}){return(await Eo({env:t})).username??null}var Xx=()=>Hx(new URL("../bin/bostrat-git-credential",import.meta.url)),Yx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function Zx(t){let[e,n]=String(t).split("/");return Ao.join(Gx,e,n)}async function kc(t,{env:e=process.env}={}){let n=(...r)=>If("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Xx()}" ${Yx(e)}`),await n("config","credential.useHttpPath","true")}async function xc(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Rf.test(String(t||"")))throw wr(`bad repo slug "${t}"`,400);let r=Zx(t);if(Fx.existsSync(Ao.join(r,".git")))return r;let{token:o}=await ea(t,{env:e});await _f.mkdir(Ao.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await If("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Jx,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await _f.rm(r,{recursive:!0,force:!0}).catch(()=>{}),wr(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await kc(r,{env:e}),r}var Qx=6e4,tS=5e3,Sc=new Map,eS=(...t)=>fetch(...t);function Mf(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Nf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function Pf(t,e,n){let r=Qt(n),o=new AbortController,s=setTimeout(()=>o.abort(),tS);try{return await eS(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function Df(t,{env:e=process.env,force:n=!1}={}){if(!Mf(e))return{facts:[],source:"unconfigured"};let r=Sc.get(t);if(!n&&r&&Date.now()-r.at<Qx)return{facts:r.facts,source:"cache"};try{let o=await Pf(`/v1/memory/${encodeURIComponent(t)}`,{headers:Nf(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return Sc.set(t,{at:Date.now(),facts:a}),{facts:a,source:"plane"}}catch(o){return r?{facts:r.facts,source:"stale",error:o?.message}:{facts:[],source:"unreachable",error:o?.message}}}async function na(t,e,{env:n=process.env}={}){if(!Mf(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await Pf(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...Nf(n),"content-type":"application/json"},body:JSON.stringify({facts:e})},n);if(!r.ok)throw new Error(`plane ${r.status}`);let o=await r.json(),s=Array.isArray(o?.results)?o.results:[];return Sc.delete(t),{pushed:s.filter(a=>a.outcome==="accepted").length,rejected:s.filter(a=>a.outcome==="rejected"),source:"plane"}}catch(r){return{pushed:0,source:"unreachable",error:r?.message}}}var sS=oS(rS),vc=process.env.BOSTRAT_STATE_DIR||X.join(process.env.HOME||nS.tmpdir(),".bostrat"),Bf=200,aS=25*1024;function ra(){return X.join(vc,"memory","repos")}var Re="operator";function jn(t){return t===Re?X.join(vc,"memory",Re):X.join(ra(),t)}var Uf=".tombstones.json";async function _o(t){try{return JSON.parse(await j.readFile(X.join(t,Uf),"utf8"))}catch{return{}}}async function Tc(t,e){let n=await _o(t);for(let[r,o]of e)n[r]=Math.max(Number(n[r])||0,o);await j.mkdir(t,{recursive:!0}),await j.writeFile(X.join(t,Uf),JSON.stringify(n))}function jf(t){let e=String(t||"").trim();if(!e)return null;let r=(e.replace(/\/+$/,"").split(/[/:]/).pop()||"").replace(/\.git$/i,"").toLowerCase();return/^[a-z0-9][a-z0-9._-]*$/.test(r)?r:null}async function Lf(t){try{let{stdout:e}=await sS("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function Ac(t){if(!t)return[];let e=jf(await Lf(t));if(e)return[e];let n=[],r=[];try{r=await j.readdir(t,{withFileTypes:!0})}catch{return[]}for(let o of r){if(!o.isDirectory()||o.name.startsWith("."))continue;let s=jf(await Lf(X.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function oa(t){let e=/^---\n([\s\S]*?)\n---\n?/.exec(t);return e?{fm:e[1],body:t.slice(e[0].length)}:{fm:null,body:t}}function rn(t,e){let n=new RegExp(`^\\s*${e}:\\s*(.+?)\\s*$`,"m").exec(t||"");return n?n[1].replace(/^"|"$/g,""):null}async function fe(t,e=null){let n=e??await j.readFile(t,"utf8").catch(()=>""),{fm:r}=oa(n),o=rn(r,"modified"),s=o?Date.parse(o):NaN;if(Number.isFinite(s))return s;try{return(await j.stat(t)).mtimeMs}catch{return 0}}function Hf(t,e){let n=rn(t,"scope"),r=/^repo:([a-z0-9][a-z0-9._-]*)$/i.exec(n||"");if(r)return r[1].toLowerCase();if((n||"").toLowerCase()===Re){let o=rn(t,"trust");if(!o||/^operator/i.test(o))return Re}return e}var gr=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function Ec(t,e,n,r=new Map){let o=(t||"").split(`
|
|
15
15
|
`),s=new Map;for(let c of(e||"").split(`
|
|
16
|
-
`)){let l=
|
|
16
|
+
`)){let l=gr(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=gr(c);return l?(a.add(l),n.has(l)&&s.has(l)?s.get(l):c):c});for(let[c,l]of s)a.has(c)||!n.has(c)||(a.add(c),i.push(l));for(let c of n){if(a.has(c))continue;let l=r.get(c);l&&i.push(l)}return i.join(`
|
|
17
17
|
`).replace(/\n{3,}/g,`
|
|
18
18
|
|
|
19
|
-
`)}function
|
|
20
|
-
`)){let r=
|
|
19
|
+
`)}function Wf(t,e){let n=rn(e,"name")||t.replace(/\.md$/,""),r=rn(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function Io(t){let e=[];try{e=await j.readdir(t,{withFileTypes:!0})}catch{return[]}return e.filter(n=>n.isFile()&&n.name.endsWith(".md")&&n.name!=="MEMORY.md").map(n=>n.name)}async function qf(t,e){let n=X.join(t,".bostrat","memory"),r=await Io(n);if(!r.length)return{harvested:0,changedByKey:new Map};let o=new Map,s=new Map,a=0,i=new Map;for(let l of r){let u=X.join(n,l),f;try{f=await j.readFile(u,"utf8")}catch{continue}let{fm:d}=oa(f),p=Hf(d,e);if(!p)continue;let m=jn(p),w=X.join(m,l),y=await fe(u,f);if(i.has(m)||i.set(m,await _o(m)),(i.get(m)[l]||0)>=y)continue;let k=await fe(w).catch(()=>0);if(await j.access(w).then(()=>!0).catch(()=>!1)&&k>=y)continue;await j.mkdir(m,{recursive:!0}),await j.writeFile(w,f),a++;let _=o.get(m);_||o.set(m,_={won:new Set,generated:new Map}),_.won.add(l),_.generated.set(l,Wf(l,d)),s.has(p)||s.set(p,new Set),s.get(p).add(l)}let c=await j.readFile(X.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=X.join(l,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Ec(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function iS(){let t=X.join(vc,"workspaces"),e=[];try{e=await j.readdir(t,{withFileTypes:!0})}catch{return{harvested:0}}let n=0,r=new Map;for(let o of e){if(!o.isDirectory())continue;let s=X.join(t,o.name);if(!await j.access(X.join(s,".bostrat","memory")).then(()=>!0).catch(()=>!1))continue;let[i]=await Ac(s),c=await qf(s,i||null);n+=c.harvested;for(let[l,u]of c.changedByKey){r.has(l)||r.set(l,new Set);for(let f of u)r.get(l).add(f)}}return{harvested:n,changedByKey:r}}var cS=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,lS=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=gr(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function uS(t,e){let n=jn(t),r=await _o(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!cS.test(l)||l==="MEMORY.md")continue;let u=X.join(n,l),f=await j.access(u).then(()=>!0).catch(()=>!1),d=f?await fe(u):0,p=Number(c.modified)||0;if(c.deleted){p>d&&(f&&await j.rm(u,{force:!0}),await Tc(n,[[l,p]]),f&&(a.push(l),i++));continue}f&&d>=p||(r[l]||0)>=p||(await j.mkdir(n,{recursive:!0}),await j.writeFile(u,String(c.content??"")),o.add(l),typeof c.indexLine=="string"&&c.indexLine.trim()&&s.set(l,c.indexLine),i++)}if(o.size||a.length){let c=X.join(n,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=Ec(l,[...s.values()].join(`
|
|
21
21
|
`),o);if(a.length){let f=new Set(a);u=u.split(`
|
|
22
|
-
`).filter(d=>!f.has(
|
|
23
|
-
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function
|
|
24
|
-
`);e.length>
|
|
25
|
-
`);for(;Buffer.byteLength(n,"utf8")>
|
|
26
|
-
`);return n}var
|
|
22
|
+
`).filter(d=>!f.has(gr(d))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function _c(t,e=null){let n=jn(t),r=e?.length?e:await Io(n);if(!r.length)return{pushed:0,source:"empty"};let o=lS(await j.readFile(X.join(n,"MEMORY.md"),"utf8").catch(()=>"")),s=[];for(let a of r){let i=X.join(n,a),c;try{c=await j.readFile(i,"utf8")}catch{continue}s.push({file:a,content:c,modified:await fe(i,c),indexLine:o.get(a)??null})}return na(t,s)}async function dS(t){let{facts:e,source:n}=await Df(t);e.length?await uS(t,e):n==="plane"&&await _c(t)}function fS(t){let e=t.split(`
|
|
24
|
+
`);e.length>Bf&&(e=e.slice(0,Bf));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>aS&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var pS=`# Track memory
|
|
27
27
|
|
|
28
28
|
Shared Track memory (all providers) lives one level up: read \`../MEMORY.md\` for the
|
|
29
29
|
index and the fact files beside it. Facts you save here stay Claude-local but ride the
|
|
30
30
|
same sync; prefer adding durable cross-provider facts to \`../\` following its format.
|
|
31
|
-
`;async function
|
|
31
|
+
`;async function mS(t,e){if(!t||!e?.length)return{materialized:!1,reason:"no-repo-key"};let n=X.join(t,".bostrat","memory"),r=0,o=0,s=[];for(let u of e){let f=jn(u),d=await _o(f);for(let[y,k]of Object.entries(d)){let S=X.join(n,y);await j.access(S).then(()=>!0).catch(()=>!1)&&await fe(S)<=k&&(await j.rm(S,{force:!0}),o++)}let p=await Io(f);if(!p.length)continue;await j.mkdir(n,{recursive:!0});for(let y of p){let k=X.join(f,y),S=X.join(n,y),_=await fe(k),$=await fe(S).catch(()=>0);await j.access(S).then(()=>!0).catch(()=>!1)&&$>=_||(await j.copyFile(k,S),r++)}let m=await j.readFile(X.join(f,"MEMORY.md"),"utf8").catch(()=>""),w=u===Re?"## operator memory":`## repo: ${u}`;m.trim()&&s.push(e.length>1?`${w}
|
|
32
32
|
|
|
33
33
|
${m.trim()}`:m.trim())}if(!s.length)return o?{materialized:!1,reason:"empty-store",cleaned:o}:{materialized:!1,reason:"empty-store"};let i="_Shared Track memory (point-in-time facts). Verify file/line citations against the current code before acting on them; correct a wrong fact in place (bump `modified`, use `supersedes`) rather than working around it._\n\n"+s.join(`
|
|
34
34
|
|
|
35
35
|
`)+`
|
|
36
|
-
`,c=
|
|
37
|
-
`).filter(p=>!s.has(
|
|
38
|
-
`);await j.writeFile(c,u).catch(()=>{});let f=
|
|
39
|
-
`).filter(l=>
|
|
40
|
-
`)).catch(()=>{});let c=await
|
|
41
|
-
`,n!=null?{mode:n}:void 0),await
|
|
42
|
-
`,"utf8");let n=(await
|
|
43
|
-
`).filter(Boolean);n.length>
|
|
36
|
+
`,c=fS(i);await j.mkdir(n,{recursive:!0}),await j.writeFile(X.join(n,"MEMORY.md"),c);let l=X.join(n,"claude");return await j.mkdir(l,{recursive:!0}),await j.writeFile(X.join(l,"MEMORY.md"),pS,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var Ff=0,hS=3e4;async function Kf(t){try{let e=await Ac(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,Re];if(Date.now()-Ff>hS){Ff=Date.now(),await wS();let{changedByKey:r}=await iS();for(let[o,s]of r)await _c(o,[...s])}for(let r of n)await dS(r);return await mS(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function wS(){let t=0,e=[];try{e=await j.readdir(ra(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=X.join(ra(),n.name),o=jn(Re),s=new Set,a=new Map;for(let p of await Io(r)){let m=X.join(r,p),w;try{w=await j.readFile(m,"utf8")}catch{continue}let{fm:y}=oa(w);if(Hf(y,n.name)!==Re)continue;let k=X.join(o,p),S=await fe(k).catch(()=>0),_=await fe(m,w);await j.mkdir(o,{recursive:!0}),_>S&&await j.writeFile(k,w),await j.rm(m,{force:!0}),s.add(p),a.set(p,Wf(p,y)),t++}if(!s.size)continue;let i=Date.now();await Tc(r,[...s].map(p=>[p,i])),await na(n.name,[...s].map(p=>({file:p,deleted:!0,modified:i})));let c=X.join(r,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(p=>!s.has(gr(p))).join(`
|
|
38
|
+
`);await j.writeFile(c,u).catch(()=>{});let f=X.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Ec(d,l,s,a))}return{moved:t}}async function Gf(){let t=[],e=[Re];try{for(let n of await j.readdir(ra(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=jn(n),o=await Io(r),s=await _o(r);if(!o.length&&!Object.keys(s).length)continue;let a=[];for(let i of o){let c=await j.readFile(X.join(r,i),"utf8").catch(()=>""),{fm:l}=oa(c);a.push({file:i,name:rn(l,"name")||i.replace(/\.md$/,""),description:rn(l,"description")||"",scope:rn(l,"scope")||null,modified:await fe(X.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function Vf(t,e){if(!/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(e)||e==="MEMORY.md")throw new Error("bad fact filename");let n=jn(t),r=X.join(n,e);if(!await j.access(r).then(()=>!0).catch(()=>!1))throw new Error(`fact ${e} not found in ${t}`);let s=Date.now();await j.rm(r,{force:!0}),await Tc(n,[[e,s]]);let a=X.join(n,"MEMORY.md"),i=await j.readFile(a,"utf8").catch(()=>"");await j.writeFile(a,i.split(`
|
|
39
|
+
`).filter(l=>gr(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await na(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function zf(t){try{let e=await Ac(t),{harvested:n,changedByKey:r}=await qf(t,e[0]||null);for(let[o,s]of r)await _c(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import Cc from"node:fs/promises";import op from"node:path";import Qf from"node:fs/promises";import tp from"node:os";import ep from"node:path";import Ic from"node:fs/promises";import gS from"node:path";var yS=0;async function jt(t,e,{mode:n}={}){await Ic.mkdir(gS.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++yS}.tmp`;await Ic.writeFile(r,JSON.stringify(e,null,2)+`
|
|
41
|
+
`,n!=null?{mode:n}:void 0),await Ic.rename(r,t)}var bS=1e4,kS=3e4,Jf=2e3,xS=5,Xf=100,SS=60*6e4,vS=()=>process.env.BOSTRAT_STATE_DIR||ep.join(process.env.HOME||tp.tmpdir(),".bostrat"),np=()=>ep.join(vS(),"content-outbox.json"),TS=()=>process.env.BOSTRAT_NODE_ID||tp.hostname(),AS=(...t)=>fetch(...t),ES=kS,_S=SS;function ht(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var $o=(t=process.env)=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function Oo(t,e={},n=process.env){let r=new AbortController,o=setTimeout(()=>r.abort(),bS);try{return await AS(`${Qt(n)}${t}`,{...e,signal:r.signal})}finally{clearTimeout(o)}}var aa=null,Rc=null,sa=null,Ro=null;async function rp(){return aa||(Rc||=Qf.readFile(np(),"utf8").then(t=>JSON.parse(t)).catch(()=>({ops:[]})).then(t=>aa={ops:Array.isArray(t?.ops)?t.ops:[],parked:Array.isArray(t?.parked)?t.parked:[]}).finally(()=>Rc=null),Rc)}async function ia(){await jt(np(),aa).catch(()=>{})}function Yf(t){return t.op==="blob"?`blob ${t.family} ${t.id} ${t.v} ${t.kind}`:`${t.op} ${t.family} ${t.id}`}async function Oc(t){let e=await rp(),n=Yf(t);if(t.op==="delete")e.ops=e.ops.filter(r=>!(r.family===t.family&&r.id===t.id)),e.ops.push(t);else{e.ops=e.ops.filter(o=>!(o.op==="delete"&&o.family===t.family&&o.id===t.id));let r=e.ops.findIndex(o=>Yf(o)===n);r>=0?e.ops[r]=t:e.ops.push(t)}e.ops.length>Jf&&(e.ops=e.ops.slice(-Jf)),await ia()}function Zf(){sa||(sa=setTimeout(()=>{sa=null,yr().catch(()=>{})},ES),sa.unref?.())}function $c(t){let e=new Error(`plane ${t}`);return e.refused=!0,e}async function IS(t,e=process.env){if(t.op==="record"){let n=await Oo(`/v1/content/${encodeURIComponent(t.family)}`,{method:"POST",headers:{...$o(e),"content-type":"application/json"},body:JSON.stringify({records:[{id:t.id,trackId:t.trackId??null,data:t.data,updatedAt:t.updatedAt,originNode:TS()}]})},e);if(!n.ok)throw $c(n.status);let o=(await n.json().catch(()=>null))?.results?.[0];return o?.outcome==="rejected"?{delivered:!1,rejected:o.reason}:{delivered:!0}}if(t.op==="blob"){let n=await Qf.readFile(t.path).catch(()=>null);if(!n)return{delivered:!0,dropped:"source file gone"};let r=await Oo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}/blob?v=${t.v}&kind=${encodeURIComponent(t.kind)}`,{method:"POST",headers:{...$o(e),"content-type":"application/octet-stream"},body:n},e);if(!r.ok&&r.status!==400)throw $c(r.status);return r.status===400?{delivered:!1,rejected:(await r.json().catch(()=>null))?.error||"rejected"}:{delivered:!0}}if(t.op==="delete"){let n=await Oo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}?deletedAt=${t.deletedAt}`,{method:"DELETE",headers:$o(e)},e);if(!n.ok)throw $c(n.status);return{delivered:!0}}return{delivered:!0,dropped:"unknown op"}}async function yr({env:t=process.env,log:e=console,now:n=Date.now()}={}){return ht(t)?Ro||(Ro=(async()=>{let r=await rp();RS(r,n);let o=0,s=0;for(;r.ops.length;){let a=r.ops[0],i;try{i=await IS(a,t)}catch(c){if(!c?.refused)return Zf(),{flushed:o,parked:s,pending:r.ops.length,error:c?.message||String(c)};if(a.refusals=(a.refusals||0)+1,a.refusals<xS)return Zf(),await ia(),{flushed:o,parked:s,pending:r.ops.length,error:c?.message||String(c)};e.error?.(`[content-plane] ALARM parking ${a.op} ${a.family}/${a.id} after ${a.refusals} refusals (${c.message}) \u2014 draining the ${r.ops.length-1} ops behind it`),r.ops.shift(),r.parked.push({...a,parkedAt:n,error:c.message}),r.parked.length>Xf&&(r.parked=r.parked.slice(-Xf)),s++,await ia();continue}i.rejected&&e.error?.(`[content-plane] ALARM plane rejected ${a.op} ${a.family}/${a.id}: ${i.rejected}`),r.ops.shift(),o++,await ia()}return{flushed:o,parked:s,pending:0}})().finally(()=>{Ro=null}),Ro):{flushed:0,source:"unconfigured"}}function RS(t,e){if(!t.parked.length||t.ops.length)return;let n=t.parked.filter(r=>e-(r.parkedAt||0)>=_S);if(n.length){t.parked=t.parked.filter(r=>!n.includes(r));for(let r of n)t.ops.push({...r,refusals:0,parkedAt:void 0,error:void 0})}}async function Nt(t,e,{trackId:n=null,env:r=process.env}={}){return ht(r)?(await Oc({op:"record",family:t,id:e.id,trackId:n??e.trackId??null,data:e,updatedAt:Number(e.updatedAt)||Date.now()}),yr({env:r})):{queued:!1,source:"unconfigured"}}async function on(t,e,n,r,o,{env:s=process.env}={}){return ht(s)?(await Oc({op:"blob",family:t,id:e,v:Number(n),kind:r,path:o}),yr({env:s})):{queued:!1,source:"unconfigured"}}async function ne(t,e,{deletedAt:n=Date.now(),env:r=process.env}={}){return ht(r)?(await Oc({op:"delete",family:t,id:e,deletedAt:n}),yr({env:r})):{queued:!1,source:"unconfigured"}}async function Rt(t,{env:e=process.env}={}){if(!ht(e))return{records:[],source:"unconfigured"};try{let n=await Oo(`/v1/content/${encodeURIComponent(t)}?tombstones=1`,{headers:$o(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);let r=await n.json();return{records:Array.isArray(r?.records)?r.records:[],source:"plane"}}catch(n){return{records:[],source:"unreachable",error:n?.message}}}async function ca(t,e,n,r,{env:o=process.env}={}){if(!ht(o))return null;try{let s=await Oo(`/v1/content/${encodeURIComponent(t)}/${encodeURIComponent(e)}/blob?v=${Number(n)}&kind=${encodeURIComponent(r)}`,{headers:$o(o)},o);if(!s.ok)return null;let a=Buffer.from(await s.arrayBuffer());return r==="png"?a:a.toString("utf8")}catch{return null}}var $S=".bostrat/handoff",OS="latest.md",CS=t=>/workspaces[/\\]([0-9a-f]{8})$/.exec(t||"")?.[1]??null;async function sp(t,e,{box:n=null,agent:r=null}={}){if(!t||!ht())return{pushed:!1};try{let o=Date.now();return await Nt("checkpoints",{id:t,trackId:t,v:o,box:n,agent:r,updatedAt:o},{trackId:t}),await on("checkpoints",t,o,"md",e),{pushed:!0,v:o}}catch{return{pushed:!1}}}async function ap(t){try{let e=CS(t);if(!e||!ht())return{restored:!1};let n=op.join(t,$S,OS);if(await Cc.stat(n).then(()=>!0,()=>!1))return{restored:!1};let{records:r,source:o}=await Rt("checkpoints");if(o!=="plane")return{restored:!1};let s=r.find(i=>!i.deletedAt&&i.data?.id===e);if(!s?.data?.v)return{restored:!1};let a=await ca("checkpoints",e,s.data.v,"md");return typeof a!="string"||!a?{restored:!1}:(await Cc.mkdir(op.dirname(n),{recursive:!0}),await Cc.writeFile(n,a),{restored:!0,v:s.data.v})}catch{return{restored:!1}}}import Co from"node:fs/promises";import MS from"node:os";import cp from"node:path";var NS=[/[A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,}/g,/\bsk-[A-Za-z0-9_-]{16,}/g,/\bghs_[A-Za-z0-9]{16,}|\bghp_[A-Za-z0-9]{16,}|\bgithub_pat_[A-Za-z0-9_]{20,}/g,/\bFlyV1 [A-Za-z0-9+/=_-]{16,}/g,/\bey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g];function Mo(t){let e=String(t??"");for(let n of NS)e=e.replace(n,"\u2039redacted\u203A");return e}var Mc=process.env.BOSTRAT_DEBUG_DIR||cp.join(MS.homedir(),".bostrat","debug"),br=cp.join(Mc,"handoff.jsonl"),ip=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function la(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function PS(t){try{await Co.mkdir(Mc,{recursive:!0}),await Co.appendFile(br,`${JSON.stringify(t)}
|
|
42
|
+
`,"utf8");let n=(await Co.readFile(br,"utf8")).split(`
|
|
43
|
+
`).filter(Boolean);n.length>ip&&await Co.writeFile(br,`${n.slice(-ip).join(`
|
|
44
44
|
`)}
|
|
45
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
46
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
47
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!
|
|
48
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}var
|
|
49
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
50
|
-
`);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
|
|
51
|
-
`).trim():""}function
|
|
52
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.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"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let w=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof w=="string"&&
|
|
53
|
-
|
|
54
|
-
`):""}import
|
|
55
|
-
`);if(!
|
|
56
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
57
|
-
`);return e}function
|
|
58
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
59
|
-
`),o=r.findIndex(i=>
|
|
60
|
-
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var
|
|
61
|
-
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function
|
|
62
|
-
`))
|
|
63
|
-
`).slice(-
|
|
64
|
-
`);return
|
|
65
|
-
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return
|
|
66
|
-
${o}`.trim()||e.slice(0,2e3)}function
|
|
67
|
-
`):""}async function
|
|
45
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${br}: ${e?.message||e}`)}}async function q(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&&la())try{e.paneTail=Mo((await mt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(la()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed|launch)/.test(String(e.phase||e.step||"")))&&await PS(e)}async function lp({limit:t=80}={}){try{return(await Co.readFile(br,"utf8")).split(`
|
|
46
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=Mo(o.paneTail)),o}catch{return{raw:Mo(r)}}})}catch{return[]}}var up={DEBUG_DIR:Mc,LOG_FILE:br};var rt=BS(DS);function Sr(t){return new Promise(e=>setTimeout(e,t))}function ua(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 C=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux";function fp(t){return`/tmp/tmux-${t}/default`}function LS({explicit:t,legacyExists:e,runtimeDir:n,uid:r}){if(t)return t;let o=fp(r);return e?o:n?`${n}/bostrat/tmux-default`:o}var dp=typeof process.getuid=="function"?process.getuid():0,Vt=LS({explicit:process.env.BOSTRAT_TMUX_SOCKET,legacyExists:(()=>{try{return da.existsSync(fp(dp))}catch{return!1}})(),runtimeDir:process.env.XDG_RUNTIME_DIR,uid:dp}),FS=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",Pc=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",US="CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN";function Dc(){return process.env.BOSTRAT_CLAUDE_ALT_SCREEN==="1"?[]:["-e",`${US}=1`]}var Bc="bostrat-usage-probe",HS=new Set([Bc]);function jc(t,e){let n=Ln(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Ln(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function sn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}function WS(t){let e=String(t||"").trim();return!e||/fake-|\.sh(\s|$)/.test(e)?!1:Ln(e)||/(^|\/|\s)codex(\s|$)/.test(e)||/cursor-agent/.test(e)||/(^|\/|\s)gemini(\s|$)/.test(e)}async function pp(t,{loginShell:e=!1,failOpen:n=!1}={}){let r=await mp(t,{loginShell:e});return r===fa?!0:r===kr?!1:n}var fa="present",kr="absent",xr="unknown",Nc=5e3,qS=[250,1e3,3e3],KS=t=>new Promise(e=>setTimeout(e,t));async function mp(t,{loginShell:e=!1,timeoutMs:n=Nc}={}){if(!t)return kr;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await rt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:n}),fa}catch(s){return s?.killed||s?.signal?xr:s?.code===1||s?.code===127?kr:xr}}async function pa(t,{retries:e=qS,sleep:n=KS,probe:r=mp}={}){let o=String(t||"").trim().split(/\s+/)[0],s=await r(o,{timeoutMs:Nc});for(let a=0;s===xr&&a<e.length;a++)await n(e[a]),s=await r(o,{timeoutMs:Nc*2});return s}var Lc={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash",gh:"apt install gh (Debian: enable cli.github.com/packages) / brew install gh \u2014 see https://github.com/cli/cli#installation"};function N(...t){return["-S",Vt,...t]}async function an(){if(!da.existsSync(Vt))return!1;try{return await rt(C,N("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function No(){if(await an())return!0;try{da.mkdirSync(Vt.replace(/\/[^/]+$/,""),{recursive:!0}),await rt(C,N("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await an()}var GS=/^[A-Za-z0-9_.-]{1,64}$/;function yt(t){return typeof t=="string"&&GS.test(t)}async function $e(){if(!da.existsSync(Vt))return[];try{let t="#{session_name}|#{session_windows}|#{session_attached}|#{session_activity}|#{pane_current_path}|#{@bostrat-session}|#{@bostrat-title}|#{@bostrat-sid}|#{@bostrat-agent}",{stdout:e}=await rt(C,N("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
47
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!HS.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function tt(t){return(await $e()).find(n=>n.name===t)||null}var VS=/^[A-Za-z0-9 ._↻-]+$/,zS=64;function JS(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=zS&&VS.test(e)}function Fc(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function hp(t){try{let{stdout:e}=await rt(C,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
48
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}var XS=()=>Number(process.env.BOSTRAT_LAUNCH_VERIFY_MS)||12e3,YS=1e3,ZS=/not found|Unknown command/i;function QS(t){let e=String(t||"").trim().split(/\s+/)[0]||"";if(e.includes("/"))return null;let n=[];for(let o=1;o<=e.length-3;o++)n.push(e.slice(o));if(!n.length)return null;let r=n.map(o=>o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return new RegExp(`\\b(?:${r.join("|")})`)}async function tv(t,e){let n=Date.now()+XS(),r=QS(e),o=!1,s=0;for(;;){if(await Sr(YS),s+=1,!await hp(t))return;let a=await mt(t,{lines:15}),i=!o&&ZS.test(a)&&!!r?.test(a);if(!i&&s>1&&!await vr(t))return;if(i){o=!0,await q({flow:"spawn",phase:"launch-retry",sessionName:t,agent:sn(e),promptHead:e.slice(0,120)}).catch(()=>{}),await rt(C,N("send-keys","-t",t,"C-u")),await rt(C,N("send-keys","-t",t,"-l",e)),await rt(C,N("send-keys","-t",t,"Enter"));continue}if(Date.now()>n){await q({flow:"spawn",phase:"launch-failed",sessionName:t,agent:sn(e),retried:o,error:"agent CLI never started after launch was typed"}).catch(()=>{});return}}}async function Fn(t,e=FS,n={}){if(!JS(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Fc(r);if(!yt(o))throw new Error("resulting tmux session name is invalid");if(!await No())throw new Error(`host tmux server not running on ${Vt} and auto-start failed \u2014 start one: ${C} -S ${Vt} new-session -d -s keeper "sleep 2147483647"`);if(await hp(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||Pc,a=n.agent||sn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||jS(),l=i?s:jc(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Ln(s),d=n.firstRun===!0;d||(f&&await en(e),a==="codex"&&await mr(e),a==="cursor"&&await hr(e),await Kf(e),await ap(e));let p=f&&!d?Zs():null,m=kf(),w=["-e",`${Js}=${m}`];p&&w.push("-e",`ANTHROPIC_API_KEY=${p}`),f&&w.push(...Dc());try{await rt(C,N("new-session","-d","-s",o,"-c",e,...w))}catch(y){throw ua(p?ua(y,p):y,m)}return await Un(o),await rt(C,N("set-option","-t",o,"@bostrat-session","1")),await rt(C,N("set-option","-t",o,"@bostrat-title",r)),await rt(C,N("set-option","-t",o,"@bostrat-sid",c)),await rt(C,N("set-option","-t",o,"@bostrat-agent",a)),await rt(C,N("send-keys","-t",o,l,"Enter")),WS(l)&&tv(o,l).catch(()=>{}),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:m}}async function pe(t){if(!yt(t))throw new Error("invalid session name");await rt(C,N("kill-session","-t",t))}async function wp(t,e){if(yt(t))try{let n=e?N("set-option","-t",t,"@bostrat-sid",e):N("set-option","-u","-t",t,"@bostrat-sid");await rt(C,n)}catch{}}async function mt(t,{lines:e=40}={}){if(!yt(t))return"";try{let n=N("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await rt(C,n);return String(r||"")}catch{return""}}var ev=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,nv=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,At=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,rv=Number(process.env.BOSTRAT_BOX_COLS)||120,ov=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function gp(t){if(!yt(t))return 0;try{let{stdout:e}=await rt(C,N("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
49
|
+
`).filter(Boolean).length}catch{return 0}}async function Un(t,{cols:e=rv,rows:n=ov}={}){if(!yt(t))return!0;if(await gp(t))return!1;try{let r=await yp(t);if(r.cols===e&&r.rows===n)return!0;await rt(C,N("resize-window","-t",t,"-x",String(e),"-y",String(n))),await rt(C,N("set-window-option","-t",t,"window-size","latest")),await Sr(80)}catch{}return!0}async function sv(t){if(!yt(t))return 0;try{let{stdout:e}=await rt(C,N("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function vr(t){let e=await sv(t);if(!e)return!1;try{return await rt("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function yp(t){if(!yt(t))return{cols:0,rows:0};try{let{stdout:e}=await rt(C,N("display-message","-t",t,"-p","#{window_width} #{window_height}")),[n,r]=String(e||"").trim().split(/\s+/).map(o=>Number(o)||0);return{cols:n,rows:r}}catch{return{cols:0,rows:0}}}async function ma(t,{cols:e=ev,rows:n=nv}={}){if(yt(t)&&!await gp(t))try{let r=await yp(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await rt(C,N("resize-window","-t",t,"-x",String(o),"-y",String(s))),await rt(C,N("set-window-option","-t",t,"window-size","latest")),await Sr(80))}catch{}}async function cn(t){return await ma(t),mt(t,{lines:At})}var av=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function vt(t,e,{settleMs:n=40,kind:r}={}){if(!yt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>av.has(s));for(let s=0;s<o.length;s++)await rt(C,N("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await Sr(n);return q({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function ha(t,e,{kind:n}={}){if(!yt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await rt(C,N("send-keys","-t",t,"-N",String(r),"BSpace")),q({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function wa(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!yt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await rt(C,N("send-keys","-t",t,"-l",s)),n>0&&await Sr(n);q({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function Et(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!yt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await rt(C,N("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await Sr(s),await rt(C,N("send-keys","-t",t,"Enter"))}q({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 iv from"node-pty";async function kp(t,e,n={}){if(!yt(e)){ga(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await an()){ga(t,`host tmux server not running on ${Vt}`),t.close(1011,"tmux server unavailable");return}if(!await tt(e)){ga(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=bp(n.cols,120,xp),o=bp(n.rows,32,Sp),s;try{s=iv.spawn(C,["-S",Vt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){ga(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Uc(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Uc(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"&&dv(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{}uv(e)})}var cv=6,lv=250;async function uv(t,e=cv,n=lv){for(let r=0;r<e;r++){if(await Un(t).catch(()=>!0))return!0;await new Promise(o=>setTimeout(o,n))}return!1}var xp=20,Sp=6;function dv(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=xp&&Number(e)>=Sp}function bp(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Uc(t,e){try{t.send(JSON.stringify(e))}catch{}}function ga(t,e){Uc(t,{type:"error",message:e})}function vp(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 Po from"node:fs";import fv from"node:fs/promises";import Tr from"node:path";import pv from"node:os";var Tp={"claude-opus-5":1e6,"claude-opus-4-8":1e6,"claude-opus-4-7":1e6,"claude-opus-4-6":1e6,"claude-sonnet-5":1e6,"claude-sonnet-4-6":1e6,"claude-haiku-4-5":2e5,"claude-fable-5":1e6},mv=2e5,Hc=process.env.BOSTRAT_CLAUDE_PROJECTS||Tr.join(pv.homedir(),".claude","projects");function Wc(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function qc(t){let e=Tr.join(Hc,Wc(t)),n;try{n=Po.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Tr.join(e,o);try{let{mtimeMs:a}=Po.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function Oe(t,e){if(!e)return null;let n=Tr.join(Hc,Wc(t),`${e}.jsonl`);return Po.existsSync(n)?n:null}function Ap(t){if(!t)return[];let e=Tr.join(Hc,Wc(t)),n;try{n=Po.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Po.statSync(Tr.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Lt(t,e=131072){let n=await fv.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 hv(t){let e=t.split(`
|
|
50
|
+
`);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 Do(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=Oe(t,e),o=!0,!r)return null}else if(n){if(r=qc(t),o=!1,!r)return null}else return null;let s;try{s=hv(await Lt(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Tp,s.model),i=Tp[s.model]||mv;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 wv(t,e){return!t||!e?null:Oe(t,e)}function gv(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(`
|
|
51
|
+
`).trim():""}function yv(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 bv(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Kc(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 Ep(t,e){if(!t.uuid)return null;let n=Kc(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 kv=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Gc(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Lt(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
52
|
+
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.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"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let w=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof w=="string"&&kv.test(w)&&s.set(m.id,w.split("/").pop())}if(bv(l.content)){let m=Ep(c,s);m&&o.push({role:"assistant",text:"",images:m,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=gv(l.content),d=u==="assistant"?yv(l.content):null,p=u==="user"?Ep(c,s):null;!f&&!d&&!p||o.push({role:u,text:f,tools:d||void 0,images:p||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var xv=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function _p(t,e,{turns:n=16}={}){let r=wv(t,e);if(!r)return"";let o=await Gc(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}:** ${xv(s.text||c,700)} ${i}`.trim()}).join(`
|
|
53
|
+
|
|
54
|
+
`):""}import gT from"node:fs";import Ir from"node:fs/promises";import Rr from"node:path";import Sv from"node:fs/promises";import{execFile as vv}from"node:child_process";import{promisify as Tv}from"node:util";var Vc=Tv(vv),Av=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Ip=new Set,Rp=Promise.resolve(),zc=null;function Op(t){zc=typeof t=="function"?t:null}function Ev(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Cp({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Ip.has(r)||(Ip.add(r),Rp=Rp.then(()=>_v({sessionName:t,from:e,to:r})).catch(()=>{}))}async function _v({sessionName:t,from:e,to:n}){await q({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await Vc("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await Sv.realpath(r),s=Ev(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await Vc("npm",["install","-g","@openai/codex"],{env:a,timeout:Av});let i=(await Vc(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await q({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await $p({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await q({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await $p({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function $p(t){if(zc)try{await zc(t)}catch{}}var Iv=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,Mp=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function Rv(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
55
|
+
`);if(!Mp.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(Iv);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"&&Mp.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 ya=/(^|\n)\s*›/,Dp=/^\s*Welcome to Codex\b/;function Ar(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
56
|
+
`);for(let r=n.length-1;r>=0;r--)if(Dp.test(n[r]))return n.slice(r).join(`
|
|
57
|
+
`);return e}function $v(t){let e=String(t).trim();if(!e)return!1;let n=(e.match(/[A-Za-z]/g)||[]).length;return n>=3&&n/e.length>=.5}function Ov(t){return/device code/i.test(t)?"works from the device you're reading this on":/sign in with chatgpt/i.test(t)?"opens a browser link on the node itself \u2014 can't finish from another device":""}var Np=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function Cv(t,e=""){let n=Ar(t);if(ya.test(n)||!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n))return null;let r=n.split(`
|
|
58
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(Np);if(!u)continue;s<0&&(s=l);let f="";for(let d=l+1;d<Math.min(l+3,r.length);d++){let p=r[d].trim();if(p){if(Np.test(r[d])||/^Press enter/.test(p))break;f=p;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:f})}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&&!Dp.test(r[l])&&$v(u)&&i.push(u)}let c=i.join(" ").slice(0,400)||"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=>{let u=Ov(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var Pp=/Enter this one-time code/,Mv=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function Nv(t,e=""){let n=Ar(t);if(ya.test(n)||!Pp.test(n))return null;let r=n.split(`
|
|
59
|
+
`),o=r.findIndex(i=>Pp.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Mv);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 Pv(t,e=""){let n=Ar(t);return ya.test(n)||!/open the following link to authenticate/.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 Dv(t,e=""){let n=Ar(t);return ya.test(n)||!/Paste or type your API key/.test(n)?null:{provider:"codex",box:e,multiSelect:!1,cursorIndex:0,answerMode:"esc",promptId:"codex:signin-apikey",questions:[{question:"Codex is asking for an OpenAI API key. For safety a key can only be typed in the Terminal tab, never through chat. To use your ChatGPT subscription instead, go back and pick a sign-in option.",header:"Sign in",multiSelect:!1,options:[{label:"Go back to sign-in options",description:""}]}]}}var Jc=/Run Everything|Add a follow-up|Plan, search, build anything/;function Bv(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||Jc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
60
|
+
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var jv=/click this link to log in|Signing in with the browser/;function Lv(t,e=""){let n=String(t);if(!jv.test(n)||Jc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
61
|
+
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function Fv(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||Jc.test(n)?null:{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:"cursor:signin-qr",questions:[{question:"Cursor is showing a QR sign-in code in the Terminal tab \u2014 scan it from another device to log in. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function Bp(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=Nv(e,n)||Pv(e,n)||Dv(e,n)||Cv(e,n);if(r)return r}if(t==="cursor"){let r=Lv(e,n)||Fv(e,n)||Bv(e,n);if(r)return r}return Rv(e,{provider:t,box:n})}function jp(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 Uv=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Xc=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Bo=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,$t={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,busyAlt:/^[·✢✳✶✻✽✺✹✸✷]\s+[A-Z][\w '’-]{1,40}…/m,auth:/Select login method:|Paste code here if prompted|OAuth error:|Choose the text style that looks best|Quick safety check: Is this a project you created/,block:/Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/m},codex:{up:/(^|\n)\s*›/,busy:/esc to interrupt|\bWorking \(/,block:/Update available![\s\S]*\b(?:Update now|Skip until next version)\b[\s\S]*Press enter to continue/,auth:/Sign in with ChatGPT[\s\S]*Press enter to continue|Finish signing in via your browser|Paste or type your API key/},cursor:{up:/Run Everything|Add a follow-up|Plan, search, build anything/,busy:/ctrl\+c to stop|^\s*[⠁-⣿]+\s+\S/m,block:/Workspace Trust Required|Trust this workspace|\[a\]\s*Trust|Trusting workspace/,auth:/Press any key to log in|click this link to log in|Scan this QR code to log in|Signing in with the browser/}},Hv=/Trust this workspace|\[a\]\s*Trust/,Wv=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function qv(t){let e=String(t||"");if(!$t.codex.block.test(e))return null;let n=Ar(e);if(!$t.codex.block.test(n))return null;let r=n.match(Wv);return{from:r?.[1]||null,to:r?.[2]||null}}function ln(t){return new Promise(e=>setTimeout(e,t))}var Kv=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,ba=new Map;function Lp(t,e){let n=Date.now(),r=Kv();for(let[s,a]of ba)n-a>=r&&ba.delete(s);let o=`${t}::${e}`;return ba.has(o)?!1:(ba.set(o,n),!0)}var Gv=68,Vv=/^\s*[─━]{10,}\s*$/;function zv(t){let e=0;for(let n of String(t).split(`
|
|
62
|
+
`))Vv.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Yc(t,e){let n=$t[t];if(!n?.busy)return{state:"idle",reason:null};if(Qc(t,e))return{state:"unknown",reason:"blocked"};if(n.up&&!n.up.test(e))return{state:"idle",reason:null};if(n.busy.test(e))return{state:"busy",reason:null};if(n.busyAlt&&n.busyAlt.test(e))return{state:"busy",reason:null};let r=zv(e);return t==="claude"&&r>0&&r<Gv?{state:"unknown",reason:"narrow"}:{state:"idle",reason:null}}function Zc(t,e){return Yc(t,e).state}var Jv=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/,Xv=15;function Up(t,e){if(t!=="claude")return!1;let n=String(e||"").split(`
|
|
63
|
+
`).slice(-Xv).join(`
|
|
64
|
+
`);return Jv.test(n)}function jo(t,e){return Zc(t,e)==="busy"}function ka(t,e){return Zc(t,e)!=="idle"}function xa(t,e){let n=$t[t];return n?.up?n.up.test(e):!0}function Yv(t,e){let n=$t[t];return n?Qc(t,e)?!1:n.up.test(e)&&Zc(t,e)==="idle":!0}function Qc(t,e){let n=$t[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function Fp(t,e,n){let r=Date.now()+n,o=0;for(await Un(t).catch(()=>{});;){let s=await mt(t);if(Yv(e,s)){await ln(Bo);return}if(e==="cursor"&&Hv.test(s)&&!$t.cursor.up.test(s)&&o<3){if(!Lp(t,"cursor-trust")){await ln(Xc);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await Et(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await ln(Bo);continue}if(e==="codex"&&o<3){let a=qv(s);if(a){if(!Lp(t,`codex-update:${a.from}->${a.to}`)){await ln(Xc);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await Et(t,"3",{enter:!1,kind:"codex-update-skip"}),Cp({sessionName:t,from:a.from,to:a.to}),await ln(Bo);continue}}if(Date.now()>r){if(Qc(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=$t.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&$t.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&$t.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&$t.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),q({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}if(await vr(t)){let a=`${e} isn't running in this box \u2014 its pane is sitting at a bare shell (the launch command was likely swallowed at shell startup, or the CLI exited). Relaunch it from the Terminal tab, or stop and reopen the box, then send again.`;throw q({flow:"agent-ready",phase:"blocked",reason:"cli-not-running",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.`),q({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!$t[e]?.block?.test(s),paneUp:!!$t[e]?.up?.test(s),paneBusy:!!$t[e]?.busy?.test(s)}).catch(()=>{}),await ln(Bo);return}await ln(Xc)}}function Hp({agent:t,agentCmd:e}={}){let n=e?sn(e):sn(t||"");return{label:n,realClaude:e?Ln(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 Sa({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=Hp({agent:n,agentCmd:r});return!e||!o&&!s?null:un({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function un({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Uv()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=Hp({agent:r,agentCmd:o});if(i&&t&&await en(t),c&&t&&await mr(t),l&&t&&await hr(t),(i||c||l)&&n){await Fp(n,a,s);return}let u=$t[r]?r:$t[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Fp(n,u,s);return}await ln(Bo)}var Zv=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),Qv=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function Ce(t){return t==="codex"?{enter:!0,submitDelayMs:Zv}:t==="claude"?{enter:!0,submitDelayMs:Qv}:{enter:!0}}import Ta from"node:fs/promises";import zp from"node:path";var tT=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],eT={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 Er(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of tT){let o=0;for(let s of eT[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var nT=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),rT=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Wp(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))nT.has(r)&&(e+=o||0),rT.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Lo(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 qp=1.5,Kp=.75,oT=new Set("a an and are as at be been but by for from had has have he her his i if in into is it its of on or our so than that the their them then there these they this those to was were will with would you your we".split(" "));function Vp(t){let e=Lo(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||oT.has(r)||n.push(r);return n}function sT(t){let e=[];for(let n of t||[]){let r=n.payload||{},o=String(r.text||"").trim();if(n.type==="turn.user"){if(n.source==="hook"||!o)continue;e.push(Gp(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(Gp(n,"assistant",o))}}return e}function Gp(t,e,n){let r=Er(n);return{id:t.id,seq:t.seq,ts:t.payload?.ts??t.ts,role:e,boxId:t.boxId??null,provider:t.provider??null,text:n,tags:r?[r.category]:[]}}function Fo(t){let e=sT(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=Vp(a.text);r[i]=c.length,o+=c.length;let l=new Map;for(let u of c)l.set(u,(l.get(u)||0)+1);for(let[u,f]of l){let d=n.get(u);d||n.set(u,d=new Map),d.set(i,f)}});let s=e.length;return{chunks:e,postings:n,lengths:r,N:s,avgdl:s?o/s:0}}function va(t,e,n={}){let{k:r=8,role:o=null,tag:s=null,boxId:a=null}=n;if(!t||!t.N)return[];let i=[...new Set(Vp(e))];if(!i.length)return[];let c=new Map;for(let u of i){let f=t.postings.get(u);if(!f)continue;let d=f.size,p=Math.log(1+(t.N-d+.5)/(d+.5));for(let[m,w]of f){let y=t.lengths[m],k=w+qp*(1-Kp+Kp*(y/(t.avgdl||1))),S=p*(w*(qp+1)/k);c.set(m,(c.get(m)||0)+S)}}let l=[];for(let[u,f]of c){let d=t.chunks[u];o&&d.role!==o||s&&!d.tags.includes(s)||a&&d.boxId!==a||l.push({chunk:d,score:f})}return l.sort((u,f)=>f.score-u.score||f.chunk.seq-u.chunk.seq),l.slice(0,r)}var Jp="Relevant prior discussion (verbatim, retrieved";async function aT(t){let e=zp.join(t,".bostrat","events"),n;try{n=await Ta.readdir(e)}catch{return Fo([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await Ta.readFile(zp.join(e,o),"utf8");for(let a of s.split(`
|
|
65
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return Fo(r)}function iT(t){let e=String(t||""),n=a=>{let i=e.match(a);return i?i[1].trim():""},r=n(/#+\s*Objective\s*\n([\s\S]*?)(?:\n#+\s|$)/i),o=n(/#+\s*Next steps?[^\n]*\n([\s\S]*?)(?:\n#+\s|$)/i);return`${r}
|
|
66
|
+
${o}`.trim()||e.slice(0,2e3)}function cT(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=va(t,e,{k:n*2+4}),a=[],i=0;for(let{chunk:c}of s){if(o&&o(c))continue;let l=c.role==="user"?"Operator":`Agent${c.provider?` (${c.provider})`:""}`,u=c.text.trim(),f=u.length>800?u.slice(0,800)+"\u2026":u,d=`- **${l}:** ${f}`;if(i+d.length>r||(a.push(d),i+=d.length,a.length>=n))break}return a.length?["",`## ${Jp}, not a summary)`,"_Auto-selected from the Track's event log by lexical relevance to the Objective / Next steps. Verbatim excerpts, not re-summarization (docs/memory-segmentation-indexing-scope.md \xA71.3)._","",...a,""].join(`
|
|
67
|
+
`):""}async function Xp(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await Ta.readFile(e,"utf8");if(a.includes(Jp))return{appended:!1,reason:"already-enriched"};let i=await aT(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=cT(i,iT(a),{k:r,maxChars:o,exclude:s});return c?(await Ta.appendFile(e,`
|
|
68
68
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
69
|
-
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import
|
|
70
|
-
`),
|
|
71
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function
|
|
69
|
+
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import tl from"node:fs/promises";import _r from"node:path";import lT from"node:os";import{EventEmitter as uT}from"node:events";import{randomUUID as dT}from"node:crypto";var fT=new Set(["session.ready","turn.user","turn.agent","turn.retracted","turn.interrupted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","memory.seed","handoff.artifact","code.pushed","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","flow.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","notify.raised","usage.cap.hit","usage.cap.warning","provider.connected","track.summary.recorded","changelog.published","stall.detected","error"]),pT=new Set(["user","agent","orchestrator","system"]),mT=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Ft=new uT;Ft.setMaxListeners(0);var el=process.env.BOSTRAT_STATE_DIR||_r.join(process.env.HOME||lT.tmpdir(),".bostrat");function Ea(t){return t?.cwd?_r.join(t.cwd,".bostrat","events"):_r.join(el,"events")}var Yp=(t,e)=>_r.join(t,`${e}.ndjson`),Aa=new Map;async function hT(t,e){if(!Aa.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Aa.set(e,o)}let n=Aa.get(e)+1;return Aa.set(e,n),n}function wT(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||!fT.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!pT.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!mT.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?_r.join(el,"events"):Ea(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=wT(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await hT(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||dT(),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 tl.mkdir(r,{recursive:!0}),await tl.appendFile(Yp(r,n),JSON.stringify(i)+`
|
|
70
|
+
`),Ft.emit("event",i),Ft.emit(`track:${n}`,i),i}async function L(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?_r.join(el,"events"):Ea(t));let o;try{o=await tl.readFile(Yp(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
71
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function Zp(t,e){let n=`track:${t}`;return Ft.on(n,e),()=>Ft.off(n,e)}var nm=".bostrat/handoff",rm="latest.md",Qp=32*1024;function yT(t){let e=Mo(String(t??"")),n=Buffer.byteLength(e,"utf8");return n<=Qp?{content:e,bytes:n,truncated:!1}:{content:`${Buffer.from(e,"utf8").subarray(0,Qp).toString("utf8").replace(/�+$/,"")}
|
|
72
72
|
|
|
73
|
-
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var zp="Checkpoint: write a work-in-progress handoff",Jp="HANDOFF WRITTEN",Xp="Resume from .bostrat/handoff/latest.md",sT="/clear",aT=t=>`${zp} 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 ${Jp} and take no further action.`,iT=`${Xp}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,Yp="Beat: write a",Zp="BEAT WRITTEN",cT=".bostrat/arc",lT="beat.txt",qp=200,Qp=4,tm=5,uT=36,dT=t=>`${Yp} short summary of what this workstream accomplished, 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). Format the file as: FIRST LINE = one sentence of at most 18 words, plain past tense, saying the outcome \u2014 no heading, no bullet, no trailing list. THEN, only if this workstream covered several separable pieces of work, up to ${Qp} further lines, each starting with "- " and naming ONE piece in at most ${tm} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${Zp} and take no further action.`,Kp=/^(?:[-–—*•·]|\d+[.)])\s+/,fT=(t,e,n)=>{let r=t.split(/\s+/).filter(Boolean).slice(0,e).join(" ");return r.length>n?`${r.slice(0,n-1).trimEnd()}\u2026`:r};function pT(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(Kp.test(s)){if(r.length>=Qp)continue;let a=fT(s.replace(Kp,"").replace(/[.;,:]+$/,""),tm,uT);a&&r.push(a)}else r.length||n.push(s);let o=n.join(" ").replace(/\s+/g," ").replace(/[:\-–—]+$/,"").trim();if(!o)throw new Error("beat file has no summary line");return o.length>qp&&(o=`${o.slice(0,qp-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function em(t,e,{timeoutMs:n=9e4}={}){let r=_r.join(e,cT),o=_r.join(r,lT);await Ar.mkdir(r,{recursive:!0});let s=await xa(o),a=await rt(t);if(!a)throw new Error(`session "${t}" not found`);return await Sa(t,a,dT(o)),await lm(o,s,n),pT(await Ar.readFile(o,"utf8"))}function nm(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Jp||n===Zp:n.startsWith(zp)||n===sT||n.startsWith(Xp)||n.startsWith(Yp):!1}var mT=6e5;function rm(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-mT;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 om(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 sm(t,{windows:e=[]}={}){if(nm(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 hT=3500,wT=3e5,Vc=new Map;function va(t){return Vc.get(t)||null}async function am(t){let e=Vc.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await rt(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:_r.join(n.cwd,Gp,Vp)};return Vc.set(t,r),gT(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function gT(t,e){t.step="summarize",await im(t.name,e),t.step="clear";let n=await rt(t.name);await Sa(t.name,n,"/clear"),await um(hT),t.step="resume",await Sa(t.name,n,iT),t.step="done",t.finishedAt=Date.now()}async function im(t,e){let n=_r.join(e,Gp),r=_r.join(n,Vp);await Ar.mkdir(n,{recursive:!0});let o=await xa(r),s=await rt(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Sa(t,s,aT(r)),await lm(r,o,wT),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await xa(r)});let a=await Fp(e,r,{exclude:l=>nm(l.role,l.text)});await G({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await I({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await Ar.readFile(r,"utf8");await I({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...oT(l),ts:Date.now()}})}catch{}}await jf(e);let c=await yT(n,r);return i&&await Jf(i,c||r,{box:t,agent:s?.agent??null}),r}async function cm(t){let e=await rt(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return im(t,e.cwd)}async function xa(t){try{return(await Ar.stat(t)).mtimeMs}catch{return 0}}async function lm(t,e,n){let r=Date.now()+n;for(;;){if(await xa(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await um(2e3)}}async function yT(t,e){try{if(!rT.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=_r.join(t,`${n}.md`);return await Ar.copyFile(e,r),r}catch{return null}}function um(t){return new Promise(e=>setTimeout(e,t))}async function Sa(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 ln({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await rt(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await Et(t,n,{kind:"orchestration-drive",...Ce(r.agent)})}import F from"node:fs/promises";import U from"node:path";import{randomUUID as Ts}from"node:crypto";import{execFile as WR}from"node:child_process";import{promisify as qR}from"node:util";import Jh from"node:path";var zc={"claude-opus-5":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,note:"GA rates; verified 2026-08-06 via the claude-api skill."}],"claude-opus-4-8":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,note:"verified 2026-08-06 via the claude-api skill."}],"claude-sonnet-5":[{from:null,to:"2026-08-31",input:2,output:10,cacheWrite:4,cacheRead:.2,note:"GA introductory pricing ($2/$10); verified 2026-08-06 via the claude-api skill."},{from:"2026-09-01",to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,note:"standard rates resume after the intro window; verified 2026-08-06."}],"claude-sonnet-4-6":[{from:null,to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,note:"verified 2026-08-06 via the claude-api skill."}],"claude-haiku-4-5":[{from:null,to:null,input:1,output:5,cacheWrite:2,cacheRead:.1,note:"verified 2026-08-06 via the claude-api skill."}],"claude-fable-5":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:1,note:"verified 2026-08-06 via the claude-api skill; 83% of fleet spend."}],"gpt-5.5":[{from:null,to:null,input:5,output:30,cacheWrite:5,cacheRead:.5,note:"verified 2026-07-03; no cache-write premium on 5.5-and-earlier."}],"gpt-5.6-sol":[{from:null,to:null,input:5,output:30,cacheWrite:6.25,cacheRead:.5,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-terra":[{from:null,to:null,input:2.5,output:15,cacheWrite:3.125,cacheRead:.25,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-luna":[{from:null,to:null,input:1,output:6,cacheWrite:1.25,cacheRead:.1,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}]},dm={opus:"claude-opus-5",opusplan:"claude-opus-5",default:"claude-opus-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"},bT=/^\d{4}-\d{2}-\d{2}$/;function fm(t,e,n){if(t==null)return e==="from"?-1/0:1/0;if(!bT.test(String(t)))throw new Error(`pricing.js: ${n} window ${e} must be YYYY-MM-DD, got ${JSON.stringify(t)}`);let r=Date.parse(`${t}T${e==="from"?"00:00:00.000":"23:59:59.999"}Z`);if(!Number.isFinite(r))throw new Error(`pricing.js: ${n} window ${e} is not a real date: ${t}`);return r}function kT(t){let e={};for(let[n,r]of Object.entries(t)){if(!Array.isArray(r)||r.length===0)throw new Error(`pricing.js: ${n} must be a non-empty array of rate windows`);e[n]=r.map(o=>({...o,fromMs:fm(o.from,"from",n),toMs:fm(o.to,"to",n)}))}return e}var xT=kT(zc);function ST(t){if(t==null)return Date.now();if(typeof t=="number")return Number.isFinite(t)?t:null;if(t instanceof Date){let n=t.getTime();return Number.isFinite(n)?n:null}let e=Date.parse(String(t));return Number.isFinite(e)?e:null}function vT(t){if(!t)return null;let e=String(t).trim();return zc[e]?e:dm[e]?dm[e]:Object.keys(zc).find(r=>e.startsWith(r))||null}function pm(t,e){let n=vT(t);if(!n)return null;let r=ST(e);return r===null?null:xT[n].find(o=>r>=o.fromMs&&r<=o.toMs)||null}var Ta=(t,e)=>pm(t,e)!==null;function me(t,e={},n){let r=pm(t,n);return r?((e.input||0)*r.input+(e.output||0)*r.output+(e.cacheWrite||0)*r.cacheWrite+(e.cacheRead||0)*r.cacheRead)/1e6:0}var mm=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function TT(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function hm(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Ln(t){let e=t.payload?.model||"unknown",n=TT(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=me(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!Ta(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function he(t,{boxLabels:e={}}={}){let n=(t||[]).filter(f=>f.type==="cost.recorded"),r={},o={},s={},a=0,i=0,c=0,l=new Set,u=mm();for(let f of n){let{usd:d,model:p,tokens:m,repriced:w,unpriced:y}=Ln(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(w&&i++,y&&(c++,l.add(p)),a+=d,hm(u,m),r[p]||={usd:0,tokens:mm(),events:0},r[p].usd+=d,r[p].events+=1,hm(r[p].tokens,m),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[S]||={usd:0,events:0,provider:b,models:new Set,label:e[S]||S},s[S].usd+=d,s[S].events+=1,p!=="unknown"&&s[S].models.add(p),f.epoch!=null){let E=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};E.usd+=d,E.events+=1,E.provider=b,p!=="unknown"&&E.models.add(p)}}return{totalUsd:Do(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Aa(r,f=>({...f,usd:Do(f.usd)})),byProvider:Aa(o,f=>({...f,usd:Do(f.usd)})),byBox:Aa(s,f=>({usd:Do(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:Aa(f.epochs,d=>({usd:Do(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var Do=t=>Math.round(t*1e4)/1e4,Aa=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import At from"node:fs/promises";import Ht from"node:path";import gm from"node:os";import{randomUUID as IT}from"node:crypto";import{execFile as RT}from"node:child_process";import{promisify as $T}from"node:util";import{chromium as OT}from"playwright-core";import Jc from"node:os";import{execFile as AT}from"node:child_process";import{promisify as _T}from"node:util";var ET=_T(AT);async function _a(){if(process.platform!=="darwin")return Jc.freemem();try{let{stdout:t}=await ET("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:Jc.freemem()}catch{return Jc.freemem()}}var MT=$T(RT),ym=process.env.BOSTRAT_STATE_DIR||Ht.join(process.env.HOME||gm.tmpdir(),".bostrat"),bm=Ht.join(ym,"mocks.json"),CT=process.env.BOSTRAT_MOCK_ENGINE||"chromium",NT=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",PT=process.env.BOSTRAT_FIREFOX_BIN||"firefox",DT=1300,Er=2,BT=32,km=1500,Ir=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,jT=700*1024*1024,oe=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
73
|
+
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var om="Checkpoint: write a work-in-progress handoff",sm="HANDOFF WRITTEN",am="Resume from .bostrat/handoff/latest.md",bT="/clear",kT=t=>`${om} 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 ${sm} and take no further action.`,xT=`${am}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,im="Beat: write a",cm="BEAT WRITTEN",ST=".bostrat/arc",vT="beat.txt",tm=200,lm=4,um=5,TT=36,AT=t=>`${im} short summary of what this workstream accomplished, 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). Format the file as: FIRST LINE = one sentence of at most 18 words, plain past tense, saying the outcome \u2014 no heading, no bullet, no trailing list. THEN, only if this workstream covered several separable pieces of work, up to ${lm} further lines, each starting with "- " and naming ONE piece in at most ${um} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${cm} and take no further action.`,em=/^(?:[-–—*•·]|\d+[.)])\s+/,ET=(t,e,n)=>{let r=t.split(/\s+/).filter(Boolean).slice(0,e).join(" ");return r.length>n?`${r.slice(0,n-1).trimEnd()}\u2026`:r};function _T(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(em.test(s)){if(r.length>=lm)continue;let a=ET(s.replace(em,"").replace(/[.;,:]+$/,""),um,TT);a&&r.push(a)}else r.length||n.push(s);let o=n.join(" ").replace(/\s+/g," ").replace(/[:\-–—]+$/,"").trim();if(!o)throw new Error("beat file has no summary line");return o.length>tm&&(o=`${o.slice(0,tm-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function dm(t,e,{timeoutMs:n=9e4}={}){let r=Rr.join(e,ST),o=Rr.join(r,vT);await Ir.mkdir(r,{recursive:!0});let s=await _a(o),a=await tt(t);if(!a)throw new Error(`session "${t}" not found`);return await Ia(t,a,AT(o)),await bm(o,s,n),_T(await Ir.readFile(o,"utf8"))}function fm(t,e){let n=(e||"").trim();return n?t==="assistant"?n===sm||n===cm:n.startsWith(om)||n===bT||n.startsWith(am)||n.startsWith(im):!1}var IT=6e5;function pm(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-IT;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 mm(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 hm(t,{windows:e=[]}={}){if(fm(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 RT=3500,$T=3e5,nl=new Map;function Ra(t){return nl.get(t)||null}async function wm(t){let e=nl.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await tt(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:Rr.join(n.cwd,nm,rm)};return nl.set(t,r),OT(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function OT(t,e){t.step="summarize",await gm(t.name,e),t.step="clear";let n=await tt(t.name);await Ia(t.name,n,"/clear"),await km(RT),t.step="resume",await Ia(t.name,n,xT),t.step="done",t.finishedAt=Date.now()}async function gm(t,e){let n=Rr.join(e,nm),r=Rr.join(n,rm);await Ir.mkdir(n,{recursive:!0});let o=await _a(r),s=await tt(t);await q({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ia(t,s,kT(r)),await bm(r,o,$T),await q({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await _a(r)});let a=await Xp(e,r,{exclude:l=>fm(l.role,l.text)});await q({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await R({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await Ir.readFile(r,"utf8");await R({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...yT(l),ts:Date.now()}})}catch{}}await zf(e);let c=await CT(n,r);return i&&await sp(i,c||r,{box:t,agent:s?.agent??null}),r}async function ym(t){let e=await tt(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return gm(t,e.cwd)}async function _a(t){try{return(await Ir.stat(t)).mtimeMs}catch{return 0}}async function bm(t,e,n){let r=Date.now()+n;for(;;){if(await _a(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await km(2e3)}}async function CT(t,e){try{if(!gT.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=Rr.join(t,`${n}.md`);return await Ir.copyFile(e,r),r}catch{return null}}function km(t){return new Promise(e=>setTimeout(e,t))}async function Ia(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 un({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await tt(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await Et(t,n,{kind:"orchestration-drive",...Ce(r.agent)})}import F from"node:fs/promises";import U from"node:path";import{randomUUID as Rs}from"node:crypto";import{execFile as f$}from"node:child_process";import{promisify as p$}from"node:util";import aw from"node:path";var rl={"claude-opus-5":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,note:"GA rates; verified 2026-08-06 via the claude-api skill."}],"claude-opus-4-8":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,note:"verified 2026-08-06 via the claude-api skill."}],"claude-sonnet-5":[{from:null,to:"2026-08-31",input:2,output:10,cacheWrite:4,cacheRead:.2,note:"GA introductory pricing ($2/$10); verified 2026-08-06 via the claude-api skill."},{from:"2026-09-01",to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,note:"standard rates resume after the intro window; verified 2026-08-06."}],"claude-sonnet-4-6":[{from:null,to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,note:"verified 2026-08-06 via the claude-api skill."}],"claude-haiku-4-5":[{from:null,to:null,input:1,output:5,cacheWrite:2,cacheRead:.1,note:"verified 2026-08-06 via the claude-api skill."}],"claude-fable-5":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:1,note:"verified 2026-08-06 via the claude-api skill; 83% of fleet spend."}],"gpt-5.5":[{from:null,to:null,input:5,output:30,cacheWrite:5,cacheRead:.5,note:"verified 2026-07-03; no cache-write premium on 5.5-and-earlier."}],"gpt-5.6-sol":[{from:null,to:null,input:5,output:30,cacheWrite:6.25,cacheRead:.5,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-terra":[{from:null,to:null,input:2.5,output:15,cacheWrite:3.125,cacheRead:.25,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-luna":[{from:null,to:null,input:1,output:6,cacheWrite:1.25,cacheRead:.1,note:"verified 2026-07-18; cacheWrite = 1.25\xD7 input (30-min TTL)."}]},xm={opus:"claude-opus-5",opusplan:"claude-opus-5",default:"claude-opus-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"},MT=/^\d{4}-\d{2}-\d{2}$/;function Sm(t,e,n){if(t==null)return e==="from"?-1/0:1/0;if(!MT.test(String(t)))throw new Error(`pricing.js: ${n} window ${e} must be YYYY-MM-DD, got ${JSON.stringify(t)}`);let r=Date.parse(`${t}T${e==="from"?"00:00:00.000":"23:59:59.999"}Z`);if(!Number.isFinite(r))throw new Error(`pricing.js: ${n} window ${e} is not a real date: ${t}`);return r}function NT(t){let e={};for(let[n,r]of Object.entries(t)){if(!Array.isArray(r)||r.length===0)throw new Error(`pricing.js: ${n} must be a non-empty array of rate windows`);e[n]=r.map(o=>({...o,fromMs:Sm(o.from,"from",n),toMs:Sm(o.to,"to",n)}))}return e}var PT=NT(rl);function DT(t){if(t==null)return Date.now();if(typeof t=="number")return Number.isFinite(t)?t:null;if(t instanceof Date){let n=t.getTime();return Number.isFinite(n)?n:null}let e=Date.parse(String(t));return Number.isFinite(e)?e:null}function BT(t){if(!t)return null;let e=String(t).trim();return rl[e]?e:xm[e]?xm[e]:Object.keys(rl).find(r=>e.startsWith(r))||null}function vm(t,e){let n=BT(t);if(!n)return null;let r=DT(e);return r===null?null:PT[n].find(o=>r>=o.fromMs&&r<=o.toMs)||null}var Tm=(t,e)=>vm(t,e)!==null;function dn(t,e={},n){let r=vm(t,n);return r?((e.input||0)*r.input+(e.output||0)*r.output+(e.cacheWrite||0)*r.cacheWrite+(e.cacheRead||0)*r.cacheRead)/1e6:0}var Am=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function jT(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Em(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Me(t){let e=t.payload?.model||"unknown",n=jT(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=t.source==="vendor",s=r,a=!1;if(n&&!o){let l=dn(e,n,t.ts);l>0&&(s=l,a=Math.abs(l-r)>1e-9)}let i=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,c=!s&&i>0&&!Tm(e,t.ts);return{usd:s,model:e,tokens:n,repriced:a,unpriced:c,asRecorded:r}}function me(t,{boxLabels:e={}}={}){let n=(t||[]).filter(d=>d.type==="cost.recorded"),r={},o={},s={},a=0,i=0,c=0,l=0,u=new Set,f=Am();for(let d of n){let{usd:p,model:m,tokens:w,repriced:y,unpriced:k,asRecorded:S}=Me(d);i+=S;let _=d.provider||"unknown",$=d.boxId||"unattributed";if(y&&c++,k&&(l++,u.add(m)),a+=p,Em(f,w),r[m]||={usd:0,tokens:Am(),events:0},r[m].usd+=p,r[m].events+=1,Em(r[m].tokens,w),o[_]||={usd:0,events:0},o[_].usd+=p,o[_].events+=1,s[$]||={usd:0,events:0,provider:_,models:new Set,label:e[$]||$},s[$].usd+=p,s[$].events+=1,m!=="unknown"&&s[$].models.add(m),d.epoch!=null){let z=(s[$].epochs||={})[d.epoch]||={usd:0,events:0,provider:_,models:new Set};z.usd+=p,z.events+=1,z.provider=_,m!=="unknown"&&z.models.add(m)}}return{totalUsd:$r(a),asRecordedUsd:$r(i),totalTokens:f,turns:n.length,repricedTurns:c,unpricedTurns:l,unpricedModels:[...u],byModel:$a(r,d=>({...d,usd:$r(d.usd)})),byProvider:$a(o,d=>({...d,usd:$r(d.usd)})),byBox:$a(s,d=>({usd:$r(d.usd),events:d.events,provider:d.provider,label:d.label,models:[...d.models],...d.epochs?{epochs:$a(d.epochs,p=>({usd:$r(p.usd),events:p.events,provider:p.provider,models:[...p.models]}))}:{}}))}}var $r=t=>Math.round(t*1e4)/1e4,$a=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import Tt from"node:fs/promises";import Ut from"node:path";import Im from"node:os";import{randomUUID as HT}from"node:crypto";import{execFile as WT}from"node:child_process";import{promisify as qT}from"node:util";import{chromium as KT}from"playwright-core";import ol from"node:os";import{execFile as LT}from"node:child_process";import{promisify as FT}from"node:util";var UT=FT(LT);async function Oa(){if(process.platform!=="darwin")return ol.freemem();try{let{stdout:t}=await UT("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:ol.freemem()}catch{return ol.freemem()}}var GT=qT(WT),Rm=process.env.BOSTRAT_STATE_DIR||Ut.join(process.env.HOME||Im.tmpdir(),".bostrat"),$m=Ut.join(Rm,"mocks.json"),VT=process.env.BOSTRAT_MOCK_ENGINE||"chromium",zT=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",JT=process.env.BOSTRAT_FIREFOX_BIN||"firefox",XT=1300,Or=2,YT=32,Om=1500,Cr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,ZT=700*1024*1024,re=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
74
74
|
${n.trim().split(`
|
|
75
75
|
`).slice(-6).join(`
|
|
76
|
-
`)}`:e),this.name="MockRenderError"}},Fn=null,Xc=null;async function ge(){return Fn||(Xc||=(async()=>{try{Fn=JSON.parse(await At.readFile(bm,"utf8"))}catch{Fn={mocks:[]}}finally{Xc=null}return Fn.mocks||=[],Fn})(),Xc)}async function we(){await Lt(bm,Fn)}var un=t=>Dt("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Un=(t,e,n)=>rn("mocks",t.id,e,n,Jt(t,e)[n]).catch(()=>{});function se(t,e,n={}){return I({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 Ea=t=>Ht.join(t,".bostrat","mocks");function Jt(t,e){let n=Ea(t.cwd);return{html:Ht.join(n,`${t.id}.v${e}.html`),png:Ht.join(n,`${t.id}.v${e}.png`)}}function Ne(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 Zc(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var LT=Buffer.from([137,80,78,71,13,10,26,10]);function xm(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(LT)}async function FT(t){let e=Buffer.alloc(24),n=await At.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function UT({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 HT(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,w)=>{r.onload=m,r.onerror=()=>w(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,f=s,d=-1,p=-1;for(let m=0;m<s;m++)for(let w=m*o*4,y=0;y<o;y++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),m<f&&(f=m),p=m);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:p}}},e.toString("base64"))}async function WT(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((f,d)=>{c.onload=f,c.onerror=()=>d(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 qT({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await OT.launch({executablePath:NT,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new oe("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Er})).newPage();a.setDefaultTimeout(Ir),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Ir}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Ir});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await HT(a,i),u=l&&UT({...l,pad:BT*Er,align:Er});u&&(i=await WT(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<km)throw new oe(`capture produced a ${i.length}-byte image`);let c=Zc(i);if(!c)throw new oe("output is not a PNG");return await At.writeFile(e,i),{width:Math.round(c.width/Er),height:Math.round(c.height/Er),dpr:Er,renderMs:Date.now()-r}}catch(s){throw s instanceof oe?s:new oe(/timeout/i.test(s?.message||"")?`render timed out after ${Ir}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function wm({htmlPath:t,outPath:e,width:n}){let r=await At.mkdtemp(Ht.join(gm.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await MT(PT,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Ir,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new oe(c?.killed?`render timed out after ${Ir}ms`:"firefox exited non-zero",s)}let a=await At.stat(e).catch(()=>null);if(!a||a.size<km)throw new oe(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Zc(await FT(e));if(!i)throw new oe("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await At.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function KT(t){if(CT==="firefox")return wm(t);try{return await qT(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return wm(t);throw e}}var GT=KT;var Yc=Promise.resolve();function Sm(t){return Yc=Yc.then(t,t),Yc}async function vm(t,e){let n=await ge(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await we(),await se(r,"rendering",{version:e});try{let o=await _a();if(o<jT)throw new oe(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Jt(r,e),a=await GT({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await At.rm(s.png,{force:!0}).catch(()=>{});return}Tm(r,{v:e,source:"html",htmlRel:Ht.relative(r.cwd,s.html),pngRel:Ht.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await we(),await se(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),un(r),Un(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await we(),await se(r,"error",{version:e,error:r.error}),un(r)}}}function Tm(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 VT({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?DT:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Hn({trackId:t,group:e}={}){let n=await ge(),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 dn(t){let n=(await ge()).mocks.find(r=>r.id===t);return n?{...n}:null}async function Am({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=VT({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!xm(r))throw new Error("image must be a PNG");let d=await ge(),p={id:IT().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:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await At.mkdir(Ea(o),{recursive:!0}),d.mocks.push(p),f?(await At.writeFile(Jt(p,1).html,n),await we(),await se(p,"created"),un(p),Un(p,1,"html"),Sm(()=>vm(p.id,1))):(await Em(p,1,r),await we(),await se(p,"created"),await se(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr}),un(p),Un(p,1,"png")),{...p}}async function _m(t,{html:e,image:n,width:r}={}){let s=(await ge()).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&&!xm(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 At.mkdir(Ea(s.cwd),{recursive:!0}),a?(s.state="queued",await At.writeFile(Jt(s,i).html,e),await we(),await se(s,"updated",{version:i}),un(s),Un(s,i,"html"),Sm(()=>vm(s.id,i))):(await Em(s,i,n),await we(),await se(s,"updated",{version:i}),await se(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),un(s),Un(s,i,"png")),{...s}}async function Em(t,e,n){let r=Zc(n);if(!r)throw new Error("image must be a PNG");let o=Jt(t,e);await At.writeFile(o.png,n),Tm(t,{v:e,source:"image",htmlRel:null,pngRel:Ht.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Im(t){let e=await ge(),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 se(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Rm(t){let e=await ge(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Jt(r,o);await At.rm(s.html,{force:!0}).catch(()=>{}),await At.rm(s.png,{force:!0}).catch(()=>{})}return await we(),await se(r,"deleted"),re("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function $m(t){let n=(await ge()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Ht.join(ym,"mock-archive",t.id),o=Ea(t.cwd),s=Ht.join(r,".bostrat","mocks");await At.mkdir(s,{recursive:!0});let a=await At.readdir(o).catch(()=>[]);for(let i of a)await At.copyFile(Ht.join(o,i),Ht.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 At.stat(Ht.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,i.updatedAt=Date.now(),un(i)}return await we(),n.length}async function Om(t){let e=await ge(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await we();for(let r of n)re("mocks",r.id).catch(()=>{})}return n.length}async function Mm(){if(!wt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Ot("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await ge();for(let r of n.mocks){await un(r);for(let o of r.versions||[])o.htmlRel&&await Un(r,o.v,"html"),o.pngRel&&await Un(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import jo from"node:fs/promises";import Wt from"node:path";import zT from"node:os";import JT from"node:net";import{randomUUID as XT}from"node:crypto";import{execFile as YT}from"node:child_process";import{promisify as ZT}from"node:util";var be=ZT(YT),QT=process.env.BOSTRAT_STATE_DIR||Wt.join(process.env.HOME||zT.tmpdir(),".bostrat"),Bm=Wt.join(QT,"views.json"),[Cm,Nm]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),fn=Number(process.env.BOSTRAT_VIEW_MAX)||3,tA="bostrat-view-",Rr=t=>`${tA}${t}`,Pm=new Set(["bostrat","www","api","views","app"]),Ia=t=>Object.assign(new Error(t),{status:400}),eA=new Set(["npm","pnpm","yarn","bun"]),el="package.json",nA=/[|;&`]|\$\(/,rA=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),oA=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,jm=t=>String(t).trim().split(/\s+/).filter(Boolean),sA=/^[A-Za-z_][A-Za-z0-9_]*=/;function aA(t){for(let e of jm(t))if(!sA.test(e))return e;return null}function Lm(t){let e=jm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function iA(t,e){let n=Lm(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Wt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Wt.resolve(t,n[r+1])}return t}var cA=t=>jo.stat(t).then(e=>e.isDirectory(),()=>!1),Fm=t=>jo.stat(t).then(e=>e.isFile(),()=>!1),Ra=t=>jo.realpath(t).catch(()=>Wt.resolve(t));async function lA(t,e){if(Wt.resolve(t)===Wt.resolve(e))return!0;let[n,r]=await Promise.all([Ra(t),Ra(e)]);return n===r}async function uA(t){let n=(await jo.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 Fm(Wt.join(t,o.name,el))?`./${o.name}`:null))).filter(Boolean)}async function dA(t,e){let[n,r]=await Promise.all([Ra(t),Ra(e)]),o=Wt.relative(n,r);if(o&&(o.startsWith("..")||Wt.isAbsolute(o)))throw Ia(`serve directory escapes the Track workspace: ${e}`)}async function fA(t,e){if(!await cA(t))throw Ia(`serve directory does not exist: ${t}`);let n=aA(e);if(!n)return;if(!n.includes("/")&&!await rp(n,{loginShell:!0,failOpen:!0}))throw Ia(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(nA.test(e)||rA.has(n)||!eA.has(n)||!Lm(e).includes("run")||oA.test(e))return;let r=iA(t,e);if(await Fm(Wt.join(r,el)))return;let o=await uA(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ia(`no ${el} in ${r}${s}`)}var ye=null,Qc=null;async function Be(){return ye||(Qc||=(async()=>{try{ye=JSON.parse(await jo.readFile(Bm,"utf8"))}catch{ye={views:[],adoptWatermarks:{}}}finally{Qc=null}return ye.views||=[],ye.adoptWatermarks||={},ye})(),Qc)}async function ke(){await Lt(Bm,ye)}function De(t,e,n={}){return I({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 Bo=new Map,ol=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,Um=12e3;function sl(t){return Bo.get(t)||null}function Hm(t,{name:e,box:n}={}){let r=Bo.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 Bo.set(t,o),o}function nl(t,e){let n=Bo.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function $a(t,{error:e=null}={}){let n=Bo.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function Wm(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 rl(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function pA(t,e){let n=rl(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!Pm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!Pm.has(s))return s}}function Lo(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=JT.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 al(t){if(process.platform!=="darwin"){await be("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await be("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{}}}function qm(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function mA(t,e){let n=new Set(t.views.map(o=>o.port)),r=qm();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=Cm;o<=Nm;o++)if(!(n.has(o)||r.has(o))&&!await Lo(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Cm}-${Nm}`)}async function hA(t){try{let{stdout:e}=await be(M,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
-
`).some(n=>n.trim()===
|
|
76
|
+
`)}`:e),this.name="MockRenderError"}},Hn=null,sl=null;async function we(){return Hn||(sl||=(async()=>{try{Hn=JSON.parse(await Tt.readFile($m,"utf8"))}catch{Hn={mocks:[]}}finally{sl=null}return Hn.mocks||=[],Hn})(),sl)}async function he(){await jt($m,Hn)}var fn=t=>Nt("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Wn=(t,e,n)=>on("mocks",t.id,e,n,zt(t,e)[n]).catch(()=>{});function oe(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 Ca=t=>Ut.join(t,".bostrat","mocks");function zt(t,e){let n=Ca(t.cwd);return{html:Ut.join(n,`${t.id}.v${e}.html`),png:Ut.join(n,`${t.id}.v${e}.png`)}}function Ne(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 il(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var QT=Buffer.from([137,80,78,71,13,10,26,10]);function Cm(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(QT)}async function tA(t){let e=Buffer.alloc(24),n=await Tt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function eA({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 nA(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,w)=>{r.onload=m,r.onerror=()=>w(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,f=s,d=-1,p=-1;for(let m=0;m<s;m++)for(let w=m*o*4,y=0;y<o;y++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),m<f&&(f=m),p=m);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:p}}},e.toString("base64"))}async function rA(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((f,d)=>{c.onload=f,c.onerror=()=>d(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 oA({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await KT.launch({executablePath:zT,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new re("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Or})).newPage();a.setDefaultTimeout(Cr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Cr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Cr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await nA(a,i),u=l&&eA({...l,pad:YT*Or,align:Or});u&&(i=await rA(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Om)throw new re(`capture produced a ${i.length}-byte image`);let c=il(i);if(!c)throw new re("output is not a PNG");return await Tt.writeFile(e,i),{width:Math.round(c.width/Or),height:Math.round(c.height/Or),dpr:Or,renderMs:Date.now()-r}}catch(s){throw s instanceof re?s:new re(/timeout/i.test(s?.message||"")?`render timed out after ${Cr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function _m({htmlPath:t,outPath:e,width:n}){let r=await Tt.mkdtemp(Ut.join(Im.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await GT(JT,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Cr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new re(c?.killed?`render timed out after ${Cr}ms`:"firefox exited non-zero",s)}let a=await Tt.stat(e).catch(()=>null);if(!a||a.size<Om)throw new re(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=il(await tA(e));if(!i)throw new re("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await Tt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function sA(t){if(VT==="firefox")return _m(t);try{return await oA(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return _m(t);throw e}}var aA=sA;var al=Promise.resolve();function Mm(t){return al=al.then(t,t),al}async function Nm(t,e){let n=await we(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await he(),await oe(r,"rendering",{version:e});try{let o=await Oa();if(o<ZT)throw new re(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=zt(r,e),a=await aA({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await Tt.rm(s.png,{force:!0}).catch(()=>{});return}Pm(r,{v:e,source:"html",htmlRel:Ut.relative(r.cwd,s.html),pngRel:Ut.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await he(),await oe(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),fn(r),Wn(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await he(),await oe(r,"error",{version:e,error:r.error}),fn(r)}}}function Pm(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 iA({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?XT:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function qn({trackId:t,group:e}={}){let n=await we(),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 pn(t){let n=(await we()).mocks.find(r=>r.id===t);return n?{...n}:null}async function Dm({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=iA({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!Cm(r))throw new Error("image must be a PNG");let d=await we(),p={id:HT().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:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await Tt.mkdir(Ca(o),{recursive:!0}),d.mocks.push(p),f?(await Tt.writeFile(zt(p,1).html,n),await he(),await oe(p,"created"),fn(p),Wn(p,1,"html"),Mm(()=>Nm(p.id,1))):(await jm(p,1,r),await he(),await oe(p,"created"),await oe(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr}),fn(p),Wn(p,1,"png")),{...p}}async function Bm(t,{html:e,image:n,width:r}={}){let s=(await we()).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&&!Cm(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 Tt.mkdir(Ca(s.cwd),{recursive:!0}),a?(s.state="queued",await Tt.writeFile(zt(s,i).html,e),await he(),await oe(s,"updated",{version:i}),fn(s),Wn(s,i,"html"),Mm(()=>Nm(s.id,i))):(await jm(s,i,n),await he(),await oe(s,"updated",{version:i}),await oe(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),fn(s),Wn(s,i,"png")),{...s}}async function jm(t,e,n){let r=il(n);if(!r)throw new Error("image must be a PNG");let o=zt(t,e);await Tt.writeFile(o.png,n),Pm(t,{v:e,source:"image",htmlRel:null,pngRel:Ut.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Lm(t){let e=await we(),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 oe(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Fm(t){let e=await we(),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=zt(r,o);await Tt.rm(s.html,{force:!0}).catch(()=>{}),await Tt.rm(s.png,{force:!0}).catch(()=>{})}return await he(),await oe(r,"deleted"),ne("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Um(t){let n=(await we()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Ut.join(Rm,"mock-archive",t.id),o=Ca(t.cwd),s=Ut.join(r,".bostrat","mocks");await Tt.mkdir(s,{recursive:!0});let a=await Tt.readdir(o).catch(()=>[]);for(let i of a)await Tt.copyFile(Ut.join(o,i),Ut.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 Tt.stat(Ut.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,i.updatedAt=Date.now(),fn(i)}return await he(),n.length}async function Hm(t){let e=await we(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await he();for(let r of n)ne("mocks",r.id).catch(()=>{})}return n.length}async function Wm(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await we();for(let r of n.mocks){await fn(r);for(let o of r.versions||[])o.htmlRel&&await Wn(r,o.v,"html"),o.pngRel&&await Wn(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import Ho from"node:fs/promises";import Ht from"node:path";import cA from"node:os";import lA from"node:net";import{randomUUID as uA}from"node:crypto";import{execFile as dA}from"node:child_process";import{promisify as fA}from"node:util";var ye=fA(dA),pA=process.env.BOSTRAT_STATE_DIR||Ht.join(process.env.HOME||cA.tmpdir(),".bostrat"),zm=Ht.join(pA,"views.json"),[qm,Km]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),mn=Number(process.env.BOSTRAT_VIEW_MAX)||3,mA="bostrat-view-",Mr=t=>`${mA}${t}`,Gm=new Set(["bostrat","www","api","views","app"]),Ma=t=>Object.assign(new Error(t),{status:400}),hA=new Set(["npm","pnpm","yarn","bun"]),ul="package.json",wA=/[|;&`]|\$\(/,gA=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),yA=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,Jm=t=>String(t).trim().split(/\s+/).filter(Boolean),bA=/^[A-Za-z_][A-Za-z0-9_]*=/;function kA(t){for(let e of Jm(t))if(!bA.test(e))return e;return null}function Xm(t){let e=Jm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function xA(t,e){let n=Xm(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Ht.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Ht.resolve(t,n[r+1])}return t}var SA=t=>Ho.stat(t).then(e=>e.isDirectory(),()=>!1),Ym=t=>Ho.stat(t).then(e=>e.isFile(),()=>!1),Na=t=>Ho.realpath(t).catch(()=>Ht.resolve(t));async function vA(t,e){if(Ht.resolve(t)===Ht.resolve(e))return!0;let[n,r]=await Promise.all([Na(t),Na(e)]);return n===r}async function TA(t){let n=(await Ho.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 Ym(Ht.join(t,o.name,ul))?`./${o.name}`:null))).filter(Boolean)}async function AA(t,e){let[n,r]=await Promise.all([Na(t),Na(e)]),o=Ht.relative(n,r);if(o&&(o.startsWith("..")||Ht.isAbsolute(o)))throw Ma(`serve directory escapes the Track workspace: ${e}`)}async function EA(t,e){if(!await SA(t))throw Ma(`serve directory does not exist: ${t}`);let n=kA(e);if(!n)return;if(!n.includes("/")&&!await pp(n,{loginShell:!0,failOpen:!0}))throw Ma(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(wA.test(e)||gA.has(n)||!hA.has(n)||!Xm(e).includes("run")||yA.test(e))return;let r=xA(t,e);if(await Ym(Ht.join(r,ul)))return;let o=await TA(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ma(`no ${ul} in ${r}${s}`)}var ge=null,cl=null;async function Be(){return ge||(cl||=(async()=>{try{ge=JSON.parse(await Ho.readFile(zm,"utf8"))}catch{ge={views:[],adoptWatermarks:{}}}finally{cl=null}return ge.views||=[],ge.adoptWatermarks||={},ge})(),cl)}async function be(){await jt(zm,ge)}function De(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 Uo=new Map,pl=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,Zm=12e3;function ml(t){return Uo.get(t)||null}function Qm(t,{name:e,box:n}={}){let r=Uo.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 Uo.set(t,o),o}function dl(t,e){let n=Uo.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Pa(t,{error:e=null}={}){let n=Uo.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function th(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 fl(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function _A(t,e){let n=fl(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!Gm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!Gm.has(s))return s}}function Wo(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=lA.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 hl(t){if(process.platform!=="darwin"){await ye("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await ye("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{}}}function eh(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function IA(t,e){let n=new Set(t.views.map(o=>o.port)),r=eh();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=qm;o<=Km;o++)if(!(n.has(o)||r.has(o))&&!await Wo(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${qm}-${Km}`)}async function RA(t){try{let{stdout:e}=await ye(C,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
+
`).some(n=>n.trim()===Mr(t))}catch{return!1}}async function qo(t){try{await ye(C,N("kill-session","-t",Mr(t)))}catch{}}async function wl(t){if(!await No())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Mr(t.slug),n=t.serveCwd??t.cwd,r=Ht.dirname(process.execPath);await ye(C,N("new-session","-d","-s",e,"-c",n)),await ye(C,N("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await ye(C,N("send-keys","-t",e,"Enter")),await ye(C,N("send-keys","-t",e,"-l",t.cmd)),await ye(C,N("send-keys","-t",e,"Enter"))}async function Vm(t,e=12){try{let{stdout:n}=await ye(C,N("capture-pane","-t",Mr(t),"-p"));return String(n||"").trimEnd().split(`
|
|
78
78
|
`).filter(Boolean).slice(-e).join(`
|
|
79
|
-
`)}catch{return""}}var Pe=new Map,wA=12e4;async function gA(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await hA(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Dm(t.slug)||"server exited before listening"),Pe.delete(t.id),await De(t,t.state,t.error?{error:t.error}:{}),!0}if(await Lo(t.port))return Pe.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await De(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>wA?(t.state="error",t.error=await Dm(t.slug)||"never started listening",await De(t,"error",{error:t.error}),!0):!1;let o=(Pe.get(t.id)||0)+1;return Pe.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var tl=!1;async function Oa(){if(!tl){tl=!0;try{let t=await Be(),e=!1;for(let n of t.views)await gA(n)&&(e=!0);e&&await ke()}finally{tl=!1}}}async function Ma({trackId:t}={}){await Oa();let e=await Be();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Km(t){let n=(await Be()).views.find(r=>r.id===t);return n?{...n}:null}function cl(t){return ye&&ye.views.find(e=>e.slug===t)||null}async function ll({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=Wt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await dA(r,u),await fA(u,n)},d=await Be();if(i!=="detected"){let w=rl(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===w||rl(S.name)===w||S.cmd===y&&Wt.resolve(S.serveCwd??S.cwd)===u));if(b)return yA(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=fn)throw new Error(`view cap reached (${fn} live) \u2014 stop one first`);let m={id:XT().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:pA(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await mA(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await il(m),d.views.push(m),await ke(),await De(m,"created",{origin:i}),nl(t,m.id),i==="detected"&&await Lo(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await ke(),await De(m,"live")),{...m}}async function yA(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 lA(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Lo(e.port))return await ke(),nl(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=fn)throw new Error(`view cap reached (${fn} live) \u2014 stop one first`);return e.origin==="detected"?(await al(e.port),e.origin="agent"):await Fo(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Pe.delete(e.id),await il(e),await ke(),await De(e,"created",{restarted:!0,reused:!0}),nl(e.trackId,e.id),{...e,reused:!0}}async function Gm(t){let n=(await Be()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await al(n.port):await Fo(n.slug),n.state!=="stopped"&&(n.state="stopped",Pe.delete(n.id),await ke(),await De(n,"stopped")),{...n}}async function Vm(t){let e=await Be(),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>=fn)throw new Error(`view cap reached (${fn} live) \u2014 stop one first`);return await Fo(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Pe.delete(n.id),await il(n),await ke(),await De(n,"created",{restarted:!0}),{...n}}async function zm(t){let e=await Be(),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 al(r.port):await Fo(r.slug),e.views.splice(n,1),Pe.delete(r.id),await ke(),await De(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Jm(t){let e=await Be(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Fo(r.slug),Pe.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ke()),n.length}var bA=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function Xm(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await Be(),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 ke()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(bA)){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(!qm().has(c)&&!n.views.some(l=>l.port===c)&&await Lo(c))try{i.push(await ll({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 Fe from"node:fs/promises";import Ue from"node:path";import kA from"node:os";import{randomUUID as xA}from"node:crypto";var Wn=process.env.BOSTRAT_STATE_DIR||Ue.join(process.env.HOME||kA.tmpdir(),".bostrat"),Zm=Ue.join(Wn,"runbooks.json"),Ca=Ue.join(Wn,"runbooks"),Ym=256*1024,SA=[{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 Uo(t){return SA.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var je=null,ul=null;async function He(){return je||(ul||=(async()=>{let t=!0;try{je=JSON.parse(await Fe.readFile(Zm,"utf8"))}catch{je={runbooks:[]},t=!1}finally{ul=null}return je.runbooks||=[],!t&&wt()&&await vA().catch(()=>{}),je})(),ul)}async function qn(){await Lt(Zm,je)}var Ho=t=>Promise.resolve(t).catch(()=>{});function Na(t){return Ho(Dt("runbooks",{...t},{trackId:t.trackId}))}async function vA(){let{records:t,source:e}=await Ot("runbooks");if(!(e!=="plane"||!t.length)){await Fe.mkdir(Ca,{recursive:!0});for(let n of t){if(n.deletedAt||!n.data?.id)continue;let r=n.data;for(let o of r.versions||[]){let s=await na("runbooks",r.id,o.v,"md");typeof s=="string"&&await Fe.writeFile(Le(r,o.v),s).catch(()=>{})}je.runbooks.push(r)}je.runbooks.length&&await qn()}}function $r(t,e,n={}){return I({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 Le(t,e){return Ue.join(Ca,`${t.id}.v${e}.md`)}function Qm(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 th(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Ym)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Ym/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Uo(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 TA(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Pa({trackId:t,status:e}={}){let n=await He(),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 dl(t){let n=(await He()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function eh({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=th(r),c=await He(),l={id:xA().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 Fe.mkdir(Ca,{recursive:!0}),await Fe.writeFile(Le(l,1),r),l.versions.push({v:1,mdRel:Ue.relative(Wn,Le(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await qn(),await $r(l,"created",{version:1}),Na(l),Ho(rn("runbooks",l.id,1,"md",Le(l,1))),{...l}}async function nh(t,{md:e,note:n}={}){let o=(await He()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");TA(o,"update");let s=th(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Fe.mkdir(Ca,{recursive:!0}),await Fe.writeFile(Le(o,a),e),o.versions.push({v:a,mdRel:Ue.relative(Wn,Le(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await qn(),await $r(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),Na(o),Ho(rn("runbooks",o.id,a,"md",Le(o,a))),{...o}}async function rh(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await He()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await qn(),await $r(r,e==="done"?"done":"reopened"),Na(r),{...r})}async function oh(t){let e=await He(),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 Fe.rm(Ue.join(Wn,o.mdRel),{force:!0}).catch(()=>{});return await qn(),r.trackDeletedAt||await $r(r,"deleted"),Ho(re("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function sh(t){let n=(await He()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),r.updatedAt=Date.now(),await $r(r,"orphaned"),r.cwd=null,Na(r);return n.length&&await qn(),n.length}async function ah(t){let e=await He(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await $r(r,"deleted");for(let o of r.versions||[])await Fe.rm(Ue.join(Wn,o.mdRel),{force:!0}).catch(()=>{});Ho(re("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await qn()),n.length}async function ih(){if(!wt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Ot("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.runbooks){await Dt("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await rn("runbooks",r.id,o.v,"md",Ue.join(Wn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import Ko from"node:fs/promises";import xh from"node:path";import t_ from"node:os";import{randomUUID as Sh}from"node:crypto";import AA from"node:fs/promises";import ch from"node:path";import _A from"node:os";var lh=()=>ch.join(process.env.BOSTRAT_STATE_DIR||ch.join(process.env.HOME||_A.tmpdir(),".bostrat"),"integrations.json");async function We(){try{return JSON.parse(await AA.readFile(lh(),"utf8"))||{}}catch{return{}}}var Da=["linked","prOpened","prMerged","create"],fl=["prompt","auto","off"],EA={create:["prompt","off"]},uh=t=>EA[t]??fl;function Ba(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Da){let s=uh(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 Kn(){let t=await We(),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:Ba(t),defaultTeam:n}:null}async function ja(){let t=await We(),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:Ba(t)}}async function pl(){return Ba(await We())}async function dh(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Da.join(", ")}`);for(let[r,o]of e){if(!Da.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Da.join(", ")}`);let s=uh(r);if(!s.includes(o))throw new Error(s.length<fl.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${fl.join(", ")}`)}let n=await We();return n.ticketFlow={...Ba(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Lt(lh(),n,{mode:384}),n.ticketFlow}async function Wo(){let t=(await We()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function IA(){let t=(await We()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var RA={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function La(){let e=(await We()).voiceChat||{},n=e.provider||"openai",r=RA[n];if(!r)return null;let o=n==="openai"?await IA():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function fh(){let t=(await We()).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 Gn(){let t=(await We()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var hl={};lf(hl,{createComment:()=>jA,findIssueUrl:()=>CA,issueStates:()=>UA,mdToAdf:()=>hh,moveIssue:()=>HA,parseIssueRef:()=>MA,resolveIssue:()=>NA,uploadImage:()=>LA,upsertCard:()=>PA,whoami:()=>ml});var Or=t=>String(t.baseUrl||"").replace(/\/$/,""),mh=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Vn(t,e,n,r){let o=await fetch(`${Or(t)}${n}`,{method:e,headers:{authorization:mh(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 $A=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,OA=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function MA(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match($A);return r?r[1].toUpperCase():null}let n=e.match(OA);return n?n[1]:null}function CA(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 NA(t,e){let n;try{n=await Vn(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:`${Or(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function PA(t,e,{url:n,title:r,subtitle:o}){await Vn(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 DA=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function BA(t){let e=[],n=0;for(let r of t.matchAll(DA)){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 hh(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
80
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
79
|
+
`)}catch{return""}}var Pe=new Map,$A=12e4;async function OA(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await RA(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Vm(t.slug)||"server exited before listening"),Pe.delete(t.id),await De(t,t.state,t.error?{error:t.error}:{}),!0}if(await Wo(t.port))return Pe.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await De(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>$A?(t.state="error",t.error=await Vm(t.slug)||"never started listening",await De(t,"error",{error:t.error}),!0):!1;let o=(Pe.get(t.id)||0)+1;return Pe.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var ll=!1;async function Da(){if(!ll){ll=!0;try{let t=await Be(),e=!1;for(let n of t.views)await OA(n)&&(e=!0);e&&await be()}finally{ll=!1}}}async function Ba({trackId:t}={}){await Da();let e=await Be();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function nh(t){let n=(await Be()).views.find(r=>r.id===t);return n?{...n}:null}function gl(t){return ge&&ge.views.find(e=>e.slug===t)||null}async function yl({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=Ht.resolve(r,o??"."),f=i==="detected"?null:async()=>{await AA(r,u),await EA(u,n)},d=await Be();if(i!=="detected"){let w=fl(e),y=String(n).trim(),k=d.views.find(S=>S.trackId===t&&(S.slug===w||fl(S.name)===w||S.cmd===y&&Ht.resolve(S.serveCwd??S.cwd)===u));if(k)return CA(d,k,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=mn)throw new Error(`view cap reached (${mn} live) \u2014 stop one first`);let m={id:uA().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:_A(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await IA(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await wl(m),d.views.push(m),await be(),await De(m,"created",{origin:i}),dl(t,m.id),i==="detected"&&await Wo(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await be(),await De(m,"live")),{...m}}async function CA(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 vA(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Wo(e.port))return await be(),dl(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=mn)throw new Error(`view cap reached (${mn} live) \u2014 stop one first`);return e.origin==="detected"?(await hl(e.port),e.origin="agent"):await qo(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Pe.delete(e.id),await wl(e),await be(),await De(e,"created",{restarted:!0,reused:!0}),dl(e.trackId,e.id),{...e,reused:!0}}async function rh(t){let n=(await Be()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await hl(n.port):await qo(n.slug),n.state!=="stopped"&&(n.state="stopped",Pe.delete(n.id),await be(),await De(n,"stopped")),{...n}}async function oh(t){let e=await Be(),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>=mn)throw new Error(`view cap reached (${mn} live) \u2014 stop one first`);return await qo(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Pe.delete(n.id),await wl(n),await be(),await De(n,"created",{restarted:!0}),{...n}}async function sh(t){let e=await Be(),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 hl(r.port):await qo(r.slug),e.views.splice(n,1),Pe.delete(r.id),await be(),await De(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function ah(t){let e=await Be(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await qo(r.slug),Pe.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await be()),n.length}var MA=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function ih(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await Be(),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 be()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(MA)){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(!eh().has(c)&&!n.views.some(l=>l.port===c)&&await Wo(c))try{i.push(await yl({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 Fe from"node:fs/promises";import Ue from"node:path";import NA from"node:os";import{randomUUID as PA}from"node:crypto";var Kn=process.env.BOSTRAT_STATE_DIR||Ue.join(process.env.HOME||NA.tmpdir(),".bostrat"),lh=Ue.join(Kn,"runbooks.json"),ja=Ue.join(Kn,"runbooks"),ch=256*1024,DA=[{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 Ko(t){return DA.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var je=null,bl=null;async function He(){return je||(bl||=(async()=>{let t=!0;try{je=JSON.parse(await Fe.readFile(lh,"utf8"))}catch{je={runbooks:[]},t=!1}finally{bl=null}return je.runbooks||=[],!t&&ht()&&await BA().catch(()=>{}),je})(),bl)}async function Gn(){await jt(lh,je)}var Go=t=>Promise.resolve(t).catch(()=>{});function La(t){return Go(Nt("runbooks",{...t},{trackId:t.trackId}))}async function BA(){let{records:t,source:e}=await Rt("runbooks");if(!(e!=="plane"||!t.length)){await Fe.mkdir(ja,{recursive:!0});for(let n of t){if(n.deletedAt||!n.data?.id)continue;let r=n.data;for(let o of r.versions||[]){let s=await ca("runbooks",r.id,o.v,"md");typeof s=="string"&&await Fe.writeFile(Le(r,o.v),s).catch(()=>{})}je.runbooks.push(r)}je.runbooks.length&&await Gn()}}function Nr(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 Le(t,e){return Ue.join(ja,`${t.id}.v${e}.md`)}function uh(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 dh(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>ch)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${ch/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Ko(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 jA(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Fa({trackId:t,status:e}={}){let n=await He(),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 kl(t){let n=(await He()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function fh({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=dh(r),c=await He(),l={id:PA().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 Fe.mkdir(ja,{recursive:!0}),await Fe.writeFile(Le(l,1),r),l.versions.push({v:1,mdRel:Ue.relative(Kn,Le(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Gn(),await Nr(l,"created",{version:1}),La(l),Go(on("runbooks",l.id,1,"md",Le(l,1))),{...l}}async function ph(t,{md:e,note:n}={}){let o=(await He()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");jA(o,"update");let s=dh(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Fe.mkdir(ja,{recursive:!0}),await Fe.writeFile(Le(o,a),e),o.versions.push({v:a,mdRel:Ue.relative(Kn,Le(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Gn(),await Nr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),La(o),Go(on("runbooks",o.id,a,"md",Le(o,a))),{...o}}async function mh(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await He()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Gn(),await Nr(r,e==="done"?"done":"reopened"),La(r),{...r})}async function hh(t){let e=await He(),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 Fe.rm(Ue.join(Kn,o.mdRel),{force:!0}).catch(()=>{});return await Gn(),r.trackDeletedAt||await Nr(r,"deleted"),Go(ne("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function wh(t){let n=(await He()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),r.updatedAt=Date.now(),await Nr(r,"orphaned"),r.cwd=null,La(r);return n.length&&await Gn(),n.length}async function gh(t){let e=await He(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Nr(r,"deleted");for(let o of r.versions||[])await Fe.rm(Ue.join(Kn,o.mdRel),{force:!0}).catch(()=>{});Go(ne("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Gn()),n.length}async function yh(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.runbooks){await Nt("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await on("runbooks",r.id,o.v,"md",Ue.join(Kn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import Jo from"node:fs/promises";import Ch from"node:path";import mE from"node:os";import{randomUUID as Mh}from"node:crypto";import LA from"node:fs/promises";import bh from"node:path";import FA from"node:os";var kh=()=>bh.join(process.env.BOSTRAT_STATE_DIR||bh.join(process.env.HOME||FA.tmpdir(),".bostrat"),"integrations.json");async function We(){try{return JSON.parse(await LA.readFile(kh(),"utf8"))||{}}catch{return{}}}var Ua=["linked","prOpened","prMerged","create"],xl=["prompt","auto","off"],UA={create:["prompt","off"]},xh=t=>UA[t]??xl;function Ha(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ua){let s=xh(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 Vn(){let t=await We(),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:Ha(t),defaultTeam:n}:null}async function Wa(){let t=await We(),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:Ha(t)}}async function Sl(){return Ha(await We())}async function Sh(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ua.join(", ")}`);for(let[r,o]of e){if(!Ua.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ua.join(", ")}`);let s=xh(r);if(!s.includes(o))throw new Error(s.length<xl.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${xl.join(", ")}`)}let n=await We();return n.ticketFlow={...Ha(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await jt(kh(),n,{mode:384}),n.ticketFlow}async function Vo(){let t=(await We()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function HA(){let t=(await We()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var WA={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function qa(){let e=(await We()).voiceChat||{},n=e.provider||"openai",r=WA[n];if(!r)return null;let o=n==="openai"?await HA():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function vh(){let t=(await We()).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 zn(){let t=(await We()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var Tl={};bf(Tl,{createComment:()=>ZA,findIssueUrl:()=>VA,issueStates:()=>eE,mdToAdf:()=>Eh,moveIssue:()=>nE,parseIssueRef:()=>GA,resolveIssue:()=>zA,uploadImage:()=>QA,upsertCard:()=>JA,whoami:()=>vl});var Pr=t=>String(t.baseUrl||"").replace(/\/$/,""),Ah=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Jn(t,e,n,r){let o=await fetch(`${Pr(t)}${n}`,{method:e,headers:{authorization:Ah(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 qA=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,KA=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function GA(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(qA);return r?r[1].toUpperCase():null}let n=e.match(KA);return n?n[1]:null}function VA(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 zA(t,e){let n;try{n=await Jn(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:`${Pr(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function JA(t,e,{url:n,title:r,subtitle:o}){await Jn(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 XA=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function YA(t){let e=[],n=0;for(let r of t.matchAll(XA)){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 Eh(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
80
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...YA(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 ZA(t,e,n){let r=await Jn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Eh(n)});return r?.id?`${Pr(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function vl(t){let e=await Jn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(Pr(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function QA(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(`${Pr(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Ah(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??`${Pr(t)}/rest/api/3/attachment/content/${c.id}`}var tE={new:"unstarted",indeterminate:"started",done:"completed"},Th=t=>tE[t?.key]??"started";async function eE(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([Jn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),Jn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Th(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Th(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function nE(t,e,n){await Jn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var Il={};bf(Il,{createComment:()=>uE,createIssue:()=>_l,createProject:()=>El,findIssueUrl:()=>iE,issueStates:()=>fE,moveIssue:()=>pE,parseIssueRef:()=>aE,resolveIssue:()=>cE,teams:()=>Dr,uploadImage:()=>dE,upsertCard:()=>lE,whoami:()=>Al});var rE=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function ke(t,e,n={}){let r=await fetch(rE(),{method:"POST",headers:{"content-type":"application/json",authorization:t.apiKey},body:JSON.stringify({query:e,variables:n})}),o=await r.json().catch(()=>null);if(!r.ok)throw new Error(`linear: HTTP ${r.status}${o?.errors?.[0]?.message?` \u2014 ${o.errors[0].message}`:""}`);if(o?.errors?.length){let s=o.errors[0],a=(s.extensions?.validationErrors??[]).flatMap(i=>Object.values(i?.constraints??{})).join("; ");throw new Error(`linear: ${s.message}${a?` \u2014 ${a}`:""}`)}return o?.data??{}}var oE=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,sE=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function aE(t){let e=String(t||"").trim(),n=e.match(oE);if(n)return n[1].toUpperCase();let r=e.match(sE);return r?r[1]:null}function iE(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 cE(t,e){let r=(await ke(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 lE(t,e,{url:n,title:r,subtitle:o}){let s=await ke(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 uE(t,e,n){let r=await ke(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 Al(t){let e=await ke(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 dE(t,{filename:e,contentType:n,buffer:r}){let o=await ke(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
81
81
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
82
82
|
success
|
|
83
83
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
84
84
|
}
|
|
85
|
-
}`,{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 ZA(t,e){let n=await xe(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 QA(t,e,n){if(!(await xe(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 Mr(t){return((await xe(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var wh=255;async function gl(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>wh,a=s?`${o.slice(0,wh-1).trimEnd()}\u2026`:o,i=await xe(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function yl(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await xe(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 vh={linear:{api:bl,config:Kn},jira:{api:hl,config:ja}};async function _l(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(vh)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function zn(t){let e=vh[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function El(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 Th=()=>process.env.BOSTRAT_STATE_DIR||xh.join(process.env.HOME||t_.tmpdir(),".bostrat"),vl=()=>xh.join(Th(),"tickets.json"),Ah=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Fa=t=>`${Ah()}/track/${t}`,pn=null,kl=null,xl=Promise.resolve();async function Vo(){return pn||(kl??=Ko.readFile(vl(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(pn=t,!e&&wt()){let{records:n,source:r}=await Ot("tickets").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.id)continue;let{id:s,updatedAt:a,...i}=o.data;pn[s]=i}Object.keys(pn).length&&await Tl().catch(()=>{})}}return pn}).finally(()=>kl=null),kl)}async function Tl(){let t=JSON.stringify({tickets:pn},null,2),e=`${vl()}.${process.pid}.tmp`;await Ko.mkdir(Th(),{recursive:!0}),await Ko.writeFile(e,t),await Ko.rename(e,vl())}async function mn(t=null){if(xl=xl.then(Tl,Tl),await xl.catch(()=>{}),!t)return;let e=pn?.[t];e?Dt("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):re("tickets",t).catch(()=>{})}async function Se(t){return(await Vo())[t]??null}async function Ua(){return{...await Vo()}}async function _h(){return(await _l()).length>0}async function Cr(t,e,{source:n="api"}={}){let r=await _l();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 f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Vo())[t.id]=c,await mn(t.id),await I({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 Go(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await qo(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Rh(t.id,c,"linked"),c}async function Il(t){let e=await Vo();return e[t]?(delete e[t],await mn(t),!0):!1}var Al=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Eh(t,e){if(!t.track?.cwd)return null;let n=he(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function e_(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Al(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Al(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function n_(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Al(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Go(t,e){let n=await zn(e);if(!n)return;let r=await Eh(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Fa(t),title:`Bostrat Track: ${e.track.name}`,subtitle:n_(e,r)})}function Ha(t){let e=new URL(Ah());return`${e.protocol}//${t}.${e.host}/`}var r_={linked:"linked",opened:"prOpened",merged:"prMerged"};function o_(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 Ih(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await mn(t),await I({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 Rh(t,e,n){let r=await zn(e);if(!r)return;let o=r.cfg.ticketFlow?.[r_[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=El(s,a),c=o_(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 Ih(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await I({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Sh(),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 $h(t){let e=await Se(t);if(!e)throw new Error("no ticket linked to this Track");let n=await zn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=El(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await mn(t)),{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 Rl(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await Se(t);if(!r)throw new Error("no ticket linked to this Track");let o=await zn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=El(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 mn(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Ih(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Oh(t,e){let n=await Se(t);if(!n)throw new Error("no ticket linked to this Track");let r=await zn(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 I({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 Mh(t,e,n){if(!e)throw new Error("offerId required");let r=await L({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 Rl(t.id,o.payload.to?.id,{mode:"offer"})),await I({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 gh=50;async function Ch(){let t=await Kn();if(!t)throw new Error("no ticketing integration configured");return Mr(t)}function s_(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>gh)throw new Error(`too many issues (${n.length}); cap is ${gh} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function a_(t,e,n,r){let o=r??await Mr(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 Se(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 Nh(t,e){let n=await Kn();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=s_(e),o=await Mr(n),s=await a_(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Sh();return await I({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 Ph(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await I({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 Kn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Mr(a)).find(w=>w.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let m=await gl(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let w=await yl(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:m.title,message:w?.message||String(w)})}await I({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 I({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 d=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&d.length===1&&!await Se(t.id)&&(p=await Cr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function qo(t,e){let n=await zn(t);n&&await n.api.createComment(n.cfg,t,e)}async function i_(t,e,n){let r=await zn(t);if(!r)return null;let o=await dn(e);if(!o)return null;let s=Ne(o,n);if(!s)return null;let a=await Ko.readFile(Jt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var yh=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Sl=new Map;function c_(t,e){if(yh()<=0)return Go(t,e);if(Sl.has(t))return;let n=setTimeout(()=>{Sl.delete(t),Se(t).then(r=>r?Go(t,r):null).catch(()=>{})},yh());n.unref?.(),Sl.set(t,n)}var bh=new Map;async function kh(t,e,n){let r=bh.get(t)??0;Date.now()-r<600*1e3||(bh.set(t,Date.now()),await I({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function l_(t,{resolveTrack:e=null}={}){let n=await Se(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await _l())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 Cr(s,o,{source:"chat"})}catch(a){await kh(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 mn(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await mn(t.trackId),await Go(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 mn(t.trackId),await Go(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Eh(n,t.trackId).catch(()=>null):null;await qo(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
86
|
-
|
|
87
|
-
Agent cost for this Track: ${
|
|
88
|
-
|
|
89
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
90
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await c_(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await kh(t.trackId,s?.cwd??null,o)}}function Dh({resolveTrack:t}){Ut.on("event",e=>{l_(e,{resolveTrack:t}).catch(()=>{})})}async function Bh(){if(!wt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Ot("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Vo(),r=0;for(let[o,s]of Object.entries(n))await Dt("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import u_ from"node:crypto";import Wa from"node:fs/promises";import jh from"node:path";import d_ from"node:os";var Lh=process.env.BOSTRAT_STATE_DIR||jh.join(process.env.HOME||d_.tmpdir(),".bostrat"),Ml=jh.join(Lh,"pr-links.json"),V=(t,e)=>`${t}#${Number(e)}`,Fh=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=u_.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},hn=null,$l=null,Ol=Promise.resolve();async function kt(){return hn||($l??=Wa.readFile(Ml,"utf8").then(t=>({links:JSON.parse(t)?.links??{},existed:!0})).catch(()=>({links:{},existed:!1})).then(async({links:t,existed:e})=>{if(hn=t,!e&&wt()){let{records:n,source:r}=await Ot("prlinks").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.repo||o.data?.number==null)continue;let{id:s,updatedAt:a,...i}=o.data;hn[V(i.repo,i.number)]=i}Object.keys(hn).length&&await Cl().catch(()=>{})}}return hn}).finally(()=>$l=null),$l)}async function Cl(){let t=JSON.stringify({links:hn},null,2),e=`${Ml}.${process.pid}.tmp`;await Wa.mkdir(Lh,{recursive:!0}),await Wa.writeFile(e,t),await Wa.rename(e,Ml)}async function It(t){Ol=Ol.then(Cl,Cl),await Ol.catch(()=>{});let e=hn?.[t];e&&Dt("prlinks",{id:Fh(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var f_=new Set(["api","observed","reconciled"]);async function Xt({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 kt(),c=V(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:f_.has(a),firstSeen:Date.now()},await It(V(t,e)),{link:i[c],created:!0})}async function zo(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||n.announced?null:(n.announced=!0,await It(V(t,e)),n)}async function Bt(t,e){return!t||e==null?null:(await kt())[V(t,e)]??null}async function wn(t,e,n){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await It(V(t,e)),r):null}async function Uh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[V(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await It(V(t,e)),o):null}async function Hh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[V(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await It(V(t,e)),o):null}async function qa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await It(V(t,e)),r)}async function Ka(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await It(V(t,e)),n)}async function Ga(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await It(V(t,e)),r)}async function Va(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await It(V(t,e)),n)}async function Wh(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.baseFoldAt=n,await It(V(t,e)),r):null}async function qh(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await It(V(t,e)),n)}async function Kh(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[V(t,e)];return r?(r.lastHeadSha=n,await It(V(t,e)),r):null}async function Jo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await It(V(t,e)),r):null}async function Jn(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await It(V(t,e)),r):null}async function Xo(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await It(V(t,e)),r):null}async function Gh(t,e,n){if(!t||e==null)return null;let r=(await kt())[V(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await It(V(t,e)),r}async function Yo(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await It(V(t,e)),n)}var p_=new Set(["merged","closed","lost"]);async function Vh(){return Object.values(await kt()).filter(t=>!p_.has(t.state))}async function ve(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function zh(){if(!wt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Ot("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await kt(),r=0;for(let[o,s]of Object.entries(n))await Dt("prlinks",{id:Fh(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var Xh=t=>Math.round(t*100)/100;function m_(t){return!t||t.kind==="setup"?[]:qt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():Jh.basename(e.base)}))}function Yh(t){let e=new Map;for(let n of t||[])for(let r of m_(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function za(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var Nl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function h_(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function Pl(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,p]=f.split("#");return`${d.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Nl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Nl(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(f=>`${f.fromAgent}\u2192${f.toAgent}`).join(", ");n.push(`${Nl(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=he(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?h_(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function w_(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function Zh(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let p=za(d);p&&(!i||p>i)&&(i=p),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:Xh(l),firstActivityAt:c,lastActivityAt:i}}async function Qh(t){let e=Yh(t),n=await ve().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await L(i).catch(()=>[]);r.set(i.id,he(c).totalUsd)}let s=[...e.values()].map(a=>Zh(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function tw(t,e){let r=Yh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ve().catch(()=>[]),Hn().catch(()=>[]),Ma().catch(()=>[]),Pa().catch(()=>[]),Ua().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>(za(p)||p.createdAt||0)-(za(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await L(d).catch(()=>[]),m=he(p);l.set(d.id,m.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:w_(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:qt(d.workspace).map(y=>y.repo||Jh.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:Xh(m.totalUsd),lastActivityAt:za(d)})}return{...Zh(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as g_}from"node:child_process";import{promisify as y_}from"node:util";import{randomUUID as b_}from"node:crypto";var Nr=y_(g_),Ja=t=>new Promise(e=>setTimeout(e,t)),k_=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,ew=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,x_=250,S_=400,nw={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 Dl(t,e){let n=Date.now()+e;for(;;){if(await kr(t))return!0;if(Date.now()>n)return!1;await Ja(x_)}}async function v_(t,e){let n=nw[e]||nw.default;n.escape&&(await vt(t,["Escape"],{kind:"swap-exit"}),await Ja(150)),n.wipe&&await la(t,400,{kind:"swap-exit"}),await ua(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Ce(e).submitDelayMs||0;await Ja(Math.max(r,250)),await vt(t,["Enter"],{kind:"swap-exit"})}async function T_(t,e){try{let{stdout:n}=await Nr(M,N("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function rw(t){await Nr(M,N("respawn-pane","-k","-t",t))}async function Bl(t,e={}){if(!bt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await rt(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||on(n),a=Number(e.exitTimeoutMs)||k_;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await kr(t)&&(await v_(t,o),!await Dl(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await rw(t),!await Dl(t,ew))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Dn(n),l=c?Gs():null,u=await T_(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await Nr(M,N("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await Nr(M,N("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?oa(y,l):y}if(f&&!i&&(await rw(t),!await Dl(t,ew)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||b_(),m=d?n:Rc(n,p),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await tn(r.cwd),s==="codex"&&await dr(r.cwd),s==="cursor"&&await fr(r.cwd),await jn(t),await Nr(M,N("set-option","-t",t,"@bostrat-sid",p)),await Nr(M,N("set-option","-t",t,"@bostrat-agent",s)),await Ja(S_),await Et(t,m,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:w,forced:i,envRespawned:f}}import rE from"node:fs/promises";function A_(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===`
|
|
91
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
92
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
93
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
94
|
-
`)){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(
|
|
95
|
-
`)){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 f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let p=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let p=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(p?
|
|
96
|
-
`).trim():"",
|
|
97
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
98
|
-
`).trim():"";async function
|
|
99
|
-
`)){let p=d.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,f=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:
|
|
100
|
-
`;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
|
|
101
|
-
`)){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 f=[];if(u.type==="function_call"&&
|
|
102
|
-
`),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 f=r[u].match(
|
|
103
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
104
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
105
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
106
|
-
`),o=
|
|
107
|
-
`),o=
|
|
108
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
109
|
-
`)].reverse().find(a=>
|
|
110
|
-
`),o=
|
|
111
|
-
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(
|
|
112
|
-
`),o=
|
|
113
|
-
`).pop()||"")?!0:
|
|
114
|
-
`).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
|
|
115
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
85
|
+
}`,{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 fE(t,e){let n=await ke(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 pE(t,e,n){if(!(await ke(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 Dr(t){return((await ke(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var _h=255;async function El(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>_h,a=s?`${o.slice(0,_h-1).trimEnd()}\u2026`:o,i=await ke(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function _l(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await ke(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 Nh={linear:{api:Il,config:Vn},jira:{api:Tl,config:Wa}};async function Pl(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Nh)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Xn(t){let e=Nh[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Dl(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 Ph=()=>process.env.BOSTRAT_STATE_DIR||Ch.join(process.env.HOME||mE.tmpdir(),".bostrat"),Cl=()=>Ch.join(Ph(),"tickets.json"),Dh=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Ka=t=>`${Dh()}/track/${t}`,hn=null,Rl=null,$l=Promise.resolve();async function Yo(){return hn||(Rl??=Jo.readFile(Cl(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(hn=t,!e&&ht()){let{records:n,source:r}=await Rt("tickets").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.id)continue;let{id:s,updatedAt:a,...i}=o.data;hn[s]=i}Object.keys(hn).length&&await Ml().catch(()=>{})}}return hn}).finally(()=>Rl=null),Rl)}async function Ml(){let t=JSON.stringify({tickets:hn},null,2),e=`${Cl()}.${process.pid}.tmp`;await Jo.mkdir(Ph(),{recursive:!0}),await Jo.writeFile(e,t),await Jo.rename(e,Cl())}async function wn(t=null){if($l=$l.then(Ml,Ml),await $l.catch(()=>{}),!t)return;let e=hn?.[t];e?Nt("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):ne("tickets",t).catch(()=>{})}async function xe(t){return(await Yo())[t]??null}async function Ga(){return{...await Yo()}}async function Bh(){return(await Pl()).length>0}async function Br(t,e,{source:n="api"}={}){let r=await Pl();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 f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Yo())[t.id]=c,await wn(t.id),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 Xo(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await zo(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Fh(t.id,c,"linked"),c}async function Bl(t){let e=await Yo();return e[t]?(delete e[t],await wn(t),!0):!1}var Nl=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function jh(t,e){if(!t.track?.cwd)return null;let n=me(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function hE(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Nl(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Nl(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function wE(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Nl(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Xo(t,e){let n=await Xn(e);if(!n)return;let r=await jh(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Ka(t),title:`Bostrat Track: ${e.track.name}`,subtitle:wE(e,r)})}function Va(t){let e=new URL(Dh());return`${e.protocol}//${t}.${e.host}/`}var gE={linked:"linked",opened:"prOpened",merged:"prMerged"};function yE(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 Lh(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await wn(t),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 Fh(t,e,n){let r=await Xn(e);if(!r)return;let o=r.cfg.ticketFlow?.[gE[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Dl(s,a),c=yE(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 Lh(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:Mh(),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 Uh(t){let e=await xe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Xn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Dl(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await wn(t)),{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 jl(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await xe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Xn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Dl(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 wn(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Lh(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Hh(t,e){let n=await xe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Xn(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 Wh(t,e,n){if(!e)throw new Error("offerId required");let r=await L({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 jl(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 Ih=50;async function qh(){let t=await Vn();if(!t)throw new Error("no ticketing integration configured");return Dr(t)}function bE(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>Ih)throw new Error(`too many issues (${n.length}); cap is ${Ih} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function kE(t,e,n,r){let o=r??await Dr(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 xe(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 Kh(t,e){let n=await Vn();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=bE(e),o=await Dr(n),s=await kE(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Mh();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 Gh(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await Vn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Dr(a)).find(w=>w.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let m=await El(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let w=await _l(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:m.title,message:w?.message||String(w)})}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 d=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&d.length===1&&!await xe(t.id)&&(p=await Br(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function zo(t,e){let n=await Xn(t);n&&await n.api.createComment(n.cfg,t,e)}async function xE(t,e,n){let r=await Xn(t);if(!r)return null;let o=await pn(e);if(!o)return null;let s=Ne(o,n);if(!s)return null;let a=await Jo.readFile(zt(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 Rh=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Ol=new Map;function SE(t,e){if(Rh()<=0)return Xo(t,e);if(Ol.has(t))return;let n=setTimeout(()=>{Ol.delete(t),xe(t).then(r=>r?Xo(t,r):null).catch(()=>{})},Rh());n.unref?.(),Ol.set(t,n)}var $h=new Map;async function Oh(t,e,n){let r=$h.get(t)??0;Date.now()-r<600*1e3||($h.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 vE(t,{resolveTrack:e=null}={}){let n=await xe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Pl())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 Br(s,o,{source:"chat"})}catch(a){await Oh(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 wn(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await wn(t.trackId),await Xo(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 wn(t.trackId),await Xo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await jh(n,t.trackId).catch(()=>null):null;await zo(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
86
|
+
|
|
87
|
+
Agent cost for this Track: ${hE(o)}.`:"")),await Fh(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await xE(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await zo(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
88
|
+
|
|
89
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await zo(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 zo(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Va(r.slug)}
|
|
90
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await SE(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Oh(t.trackId,s?.cwd??null,o)}}function Vh({resolveTrack:t}){Ft.on("event",e=>{vE(e,{resolveTrack:t}).catch(()=>{})})}async function zh(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Yo(),r=0;for(let[o,s]of Object.entries(n))await Nt("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import TE from"node:crypto";import za from"node:fs/promises";import Jh from"node:path";import AE from"node:os";var Xh=process.env.BOSTRAT_STATE_DIR||Jh.join(process.env.HOME||AE.tmpdir(),".bostrat"),Ul=Jh.join(Xh,"pr-links.json"),K=(t,e)=>`${t}#${Number(e)}`,Yh=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=TE.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},gn=null,Ll=null,Fl=Promise.resolve();async function kt(){return gn||(Ll??=za.readFile(Ul,"utf8").then(t=>({links:JSON.parse(t)?.links??{},existed:!0})).catch(()=>({links:{},existed:!1})).then(async({links:t,existed:e})=>{if(gn=t,!e&&ht()){let{records:n,source:r}=await Rt("prlinks").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.repo||o.data?.number==null)continue;let{id:s,updatedAt:a,...i}=o.data;gn[K(i.repo,i.number)]=i}Object.keys(gn).length&&await Hl().catch(()=>{})}}return gn}).finally(()=>Ll=null),Ll)}async function Hl(){let t=JSON.stringify({links:gn},null,2),e=`${Ul}.${process.pid}.tmp`;await za.mkdir(Xh,{recursive:!0}),await za.writeFile(e,t),await za.rename(e,Ul)}async function _t(t){Fl=Fl.then(Hl,Hl),await Fl.catch(()=>{});let e=gn?.[t];e&&Nt("prlinks",{id:Yh(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var EE=new Set(["api","observed","reconciled"]);async function Jt({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 kt(),c=K(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:EE.has(a),firstSeen:Date.now()},await _t(K(t,e)),{link:i[c],created:!0})}async function Zo(t,e){if(!t||e==null)return null;let n=(await kt())[K(t,e)];return!n||n.announced?null:(n.announced=!0,await _t(K(t,e)),n)}async function Pt(t,e){return!t||e==null?null:(await kt())[K(t,e)]??null}async function yn(t,e,n){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await _t(K(t,e)),r):null}async function Zh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[K(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await _t(K(t,e)),o):null}async function Qh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[K(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await _t(K(t,e)),o):null}async function Ja(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await _t(K(t,e)),r)}async function Xa(t,e){if(!t||e==null)return null;let n=(await kt())[K(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await _t(K(t,e)),n)}async function Ya(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await _t(K(t,e)),r)}async function Za(t,e){if(!t||e==null)return null;let n=(await kt())[K(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await _t(K(t,e)),n)}async function tw(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return r?(r.baseFoldAt=n,await _t(K(t,e)),r):null}async function ew(t,e){if(!t||e==null)return null;let n=(await kt())[K(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await _t(K(t,e)),n)}async function nw(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[K(t,e)];return r?(r.lastHeadSha=n,await _t(K(t,e)),r):null}async function Qo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await _t(K(t,e)),r):null}async function Yn(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await _t(K(t,e)),r):null}async function ts(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[K(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await _t(K(t,e)),r):null}async function rw(t,e,n){if(!t||e==null)return null;let r=(await kt())[K(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await _t(K(t,e)),r}async function es(t,e){if(!t||e==null)return null;let n=(await kt())[K(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await _t(K(t,e)),n)}var _E=new Set(["merged","closed","lost"]);async function ow(){return Object.values(await kt()).filter(t=>!_E.has(t.state))}async function Se(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function sw(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await kt(),r=0;for(let[o,s]of Object.entries(n))await Nt("prlinks",{id:Yh(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var iw=t=>Math.round(t*100)/100;function IE(t){return!t||t.kind==="setup"?[]:Wt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():aw.basename(e.base)}))}function cw(t){let e=new Map;for(let n of t||[])for(let r of IE(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Qa(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var Wl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function RE(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function ql(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,p]=f.split("#");return`${d.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Wl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Wl(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(f=>`${f.fromAgent}\u2192${f.toAgent}`).join(", ");n.push(`${Wl(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=me(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?RE(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function $E(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function lw(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let p=Qa(d);p&&(!i||p>i)&&(i=p),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:iw(l),firstActivityAt:c,lastActivityAt:i}}async function uw(t){let e=cw(t),n=await Se().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await L(i).catch(()=>[]);r.set(i.id,me(c).totalUsd)}let s=[...e.values()].map(a=>lw(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function dw(t,e){let r=cw(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Se().catch(()=>[]),qn().catch(()=>[]),Ba().catch(()=>[]),Fa().catch(()=>[]),Ga().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>(Qa(p)||p.createdAt||0)-(Qa(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await L(d).catch(()=>[]),m=me(p);l.set(d.id,m.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:$E(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Wt(d.workspace).map(y=>y.repo||aw.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:iw(m.totalUsd),lastActivityAt:Qa(d)})}return{...lw(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as OE}from"node:child_process";import{promisify as CE}from"node:util";import{randomUUID as ME}from"node:crypto";var jr=CE(OE),ti=t=>new Promise(e=>setTimeout(e,t)),NE=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,fw=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,PE=250,DE=400,pw={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 Kl(t,e){let n=Date.now()+e;for(;;){if(await vr(t))return!0;if(Date.now()>n)return!1;await ti(PE)}}async function BE(t,e){let n=pw[e]||pw.default;n.escape&&(await vt(t,["Escape"],{kind:"swap-exit"}),await ti(150)),n.wipe&&await ha(t,400,{kind:"swap-exit"}),await wa(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Ce(e).submitDelayMs||0;await ti(Math.max(r,250)),await vt(t,["Enter"],{kind:"swap-exit"})}async function jE(t,e){try{let{stdout:n}=await jr(C,N("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function mw(t){await jr(C,N("respawn-pane","-k","-t",t))}async function Gl(t,e={}){if(!yt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await tt(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||sn(n),a=Number(e.exitTimeoutMs)||NE;await q({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await vr(t)&&(await BE(t,o),!await Kl(t,a)&&(i=!0,await q({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await mw(t),!await Kl(t,fw))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Ln(n),l=c?Zs():null,u=await jE(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await jr(C,N("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await jr(C,N("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?ua(y,l):y}if(f&&!i&&(await mw(t),!await Kl(t,fw)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||ME(),m=d?n:jc(n,p),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await en(r.cwd),s==="codex"&&await mr(r.cwd),s==="cursor"&&await hr(r.cwd),await Un(t),await jr(C,N("set-option","-t",t,"@bostrat-sid",p)),await jr(C,N("set-option","-t",t,"@bostrat-agent",s)),await ti(DE),await Et(t,m,{kind:"swap-launch"}),await q({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:w,forced:i,envRespawned:f}}import g_ from"node:fs/promises";function LE(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===`
|
|
91
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function ns(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 rs(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 Fr=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Ot=t=>t&&!/[$`]/.test(t)?t:null,wt=t=>t.replace(/\/+$/,"").split("/").pop()||t,Lr=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},gw=t=>t.startsWith("-"),hw=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function ut(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(gw(o)){e.has(o)&&r++;continue}n.push(o)}return n}var FE={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 ${wt(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"},UE={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 yw(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 HE(t){let e=yw(t),n=Ot(ut(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${wt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${wt(n)}`:t.includes("-i")&&n?`Edit ${wt(n)}`:null}function Vl(t){if(t.includes("--files")){let s=Ot(ut(t,hw)[0]);return s&&s!=="."?`List files in ${Lr(s)}`:"List files"}let e=ut(t,hw),n=e[0];if(!n)return null;let r=Ot(e.slice(1).find(s=>s!==".")),o=Fr(n,40);return r?`Search for \u201C${o}\u201D in ${Lr(r)}`:`Search for \u201C${o}\u201D`}function zl(t){let e=ut(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 Jl(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=ut(t)[0];return n?`Run ${wt(n)}`:null}function WE(t){let e=ut(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 ww(t){return t.find(e=>/^https?:\/\//.test(e))||null}var qE={git:t=>{let e=ut(t,new Set(["-C","-c"])),n=FE[e[0]];return n?n(e,t):null},gh:t=>{let e=ut(t),n=UE[e[0]];return n?n(e):null},sed:HE,rg:Vl,grep:Vl,egrep:Vl,nl:t=>{let e=Ot(ut(t)[0]);return e?`Read ${wt(e)}`:null},cat:t=>{let e=Ot(ut(t)[0]);return e?`Read ${wt(e)}`:null},head:t=>{let e=Ot(ut(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${wt(e)}`:null},tail:t=>{let e=Ot(ut(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${wt(e)}`:null},wc:t=>{let e=Ot(ut(t)[0]);return e?`Count lines in ${wt(e)}`:"Count input lines"},ls:t=>{let e=Ot(ut(t)[0]);return e&&e!=="."?`List files in ${Lr(e)}`:"List files"},find:t=>Ot(t[1])&&!gw(t[1])&&t[1]!=="."?`Find files in ${Lr(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=Ot(ut(t)[0]);return e?`Create directory ${Lr(e)}`:null},touch:t=>{let e=Ot(ut(t)[0]);return e?`Create ${wt(e)}`:null},rm:t=>{let e=Ot(ut(t)[0]);return e?`Delete ${Lr(e)}`:null},mv:t=>{let e=Ot(ut(t)[0]);return e?`Move ${wt(e)}`:null},cp:t=>{let e=Ot(ut(t)[0]);return e?`Copy ${wt(e)}`:null},chmod:t=>{let e=Ot(ut(t)[1]);return e?`Change permissions on ${wt(e)}`:null},diff:t=>{let e=ut(t);return e.length>=2?`Compare ${wt(e[0])} and ${wt(e[1])}`:null},curl:t=>{let e=ww(t);return e?`Fetch ${Fr(e,56)}`:"Fetch a URL"},wget:t=>{let e=ww(t);return e?`Fetch ${Fr(e,56)}`:"Fetch a URL"},npm:zl,pnpm:zl,yarn:zl,npx:t=>{let e=ut(t)[0];return e?`Run ${e}`:null},node:t=>Jl(t,"Node"),python:t=>Jl(t,"Python"),python3:t=>Jl(t,"Python"),make:t=>{let e=ut(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:WE,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=ut(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=ut(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=ut(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},KE=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function GE(t){let e=rs(ns(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(wt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Xl(e[2]);let n=wt(e[0]);if(KE.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=qE[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Xl(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
92
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=LE(e);for(let i=0;i<r.length-1;i++){let c=rs(ns(r[i])),l=rs(ns(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(wt(c[0]))&&wt(l[0])==="sed"){let u=yw(l),f=ut(c)[0];if(u&&f)return Fr(u.from===u.to?`Read line ${u.from} of ${wt(f)}`:`Read lines ${u.from}\u2013${u.to} of ${wt(f)}`,88)}}let o=[];for(let i of r){let c=GE(i);c&&o.push({d:c,head:wt(rs(ns(i))[0]||""),sub:ut(rs(ns(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":Fr(s?s.d:o[0].d,88)}var Zl=new Set(["Bash"]);function Ur(t){let e=t.split(`
|
|
93
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function bn(t,e=""){let n=e.trim();return n||Xl(t)||Ur(t)}function Yl(t,e){if(t){if(Array.isArray(t)){for(let n of t)Yl(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&Zl.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))Yl(n,e)}}}async function bw(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Lt(t,e)}catch{return[]}let o=[];for(let s of r.split(`
|
|
94
|
+
`)){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(Yl(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:bn(f.command,f.description),ts:u})}return o.slice(-n)}function kw(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((m,w)=>w>=s&&(m.ts||0)>(l.ts||0)),f=u===-1?r.length:u,d=r.slice(s,f);if(s=f,!d.length)continue;let p=d.map(m=>({...m,n:i(m)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:p,ts:Math.max(...p.map(m=>m.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 VE=/^Async (?:agent|task) launched/i,zE=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 xw(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Lt(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
95
|
+
`)){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 f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let p=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let p=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(p?Ur(p):null),detail:p?Ur(p):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&VE.test(zE(d.content))&&s.add(d.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 os from"node:fs";import XE from"node:fs/promises";import Zn from"node:path";import YE from"node:os";import ZE from"node:crypto";import QE from"node:zlib";var ei=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),se=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Hr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function kn(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 Ql=t=>`"${String(t).replace(/"/g,'\\"')}"`;function JE(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 Sw(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:se(`read ${kn(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${Hr(r)}`:`Read ${Hr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?kn(e.path,n):null,s=e?.glob?` --glob ${Ql(e.glob)}`:"",a=se(String(r),40);return{command:se(`grep ${Ql(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?kn(e.target_directory,n):null;return{command:se(`glob ${Ql(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${se(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:se(`edit ${kn(e.path,n)}`,140),description:`Edit ${Hr(e.path)}`}:null;case"Write":return e?.path?{command:se(`write ${kn(e.path,n)}`,140),description:`Write ${Hr(e.path)}`}:null;case"Delete":return e?.path?{command:se(`delete ${kn(e.path,n)}`,140),description:`Delete ${Hr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>kn(s,n)).filter(Boolean);return{command:se(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=JE(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:se(`apply_patch ${kn(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${Hr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:se(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var t_=process.env.BOSTRAT_CURSOR_CHATS||Zn.join(YE.homedir(),".cursor","chats");function Aw(t){let e=ZE.createHash("md5").update(String(t)).digest("hex");return Zn.join(t_,e)}function Ew(t){try{let e=JSON.parse(os.readFileSync(Zn.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 e_(t){let e=Aw(t),n;try{n=os.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=Zn.join(e,o.name),a=Zn.join(s,"store.db");if(!os.existsSync(a))continue;let i=Ew(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 ni(t,e){if(!t)return null;if(e){let n=Zn.join(Aw(t),e),r=Zn.join(n,"store.db");if(os.existsSync(r))return{id:e,dir:n,store:r,...Ew(n)||{createdAtMs:null,updatedAtMs:null}}}return e_(t)}function vw(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function n_(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=QE.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function r_(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 o_(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!ei.has(r.toolName))continue;let o=Sw(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 s_=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(`
|
|
96
|
+
`).trim():"",a_=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Tw=process.getBuiltinModule?.("node:sqlite"),Wr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function tu(t,e){if(!Tw)return Wr;let{DatabaseSync:n}=Tw,r;try{r=new n(t,{readOnly:!0})}catch{return Wr}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 Wr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return Wr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?vw(r,s.latestRootBlobId):null;if(!i)return{...Wr,model:a};let c=[],l=[],u=[],f=null,d=[];for(let p of r_(i)){let m=n_(vw(r,p));if(!m)continue;let w=m.providerOptions?.cursor;if(w?.requestId&&d.push(w.requestId),w?.modelProviderMessageId&&d.push(w.modelProviderMessageId),m.role!=="assistant")continue;if(Array.isArray(m.content))for(let S of m.content){let _=S?.providerOptions?.cursor?.modelName;_&&(f=_)}l.push(...o_(m.content,e).map((S,_)=>({...S,id:S.id||`${p}:${_}`}))),u.push(...a_(m.content));let y=s_(m.content);if(!y)continue;let k=[...new Set(u)];u=[],c.push({id:p,text:y,tools:k.length?k:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return Wr}finally{try{r.close()}catch{}}}function _w(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(os.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 Iw(t,e,{limit:n=80}={}){let r=ni(t,e);if(!r)return[];let{turns:o,model:s}=await tu(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=_w(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 Rw(t,e){let n=ni(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await tu(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 $w(t,e,{limit:n=80}={}){let r=ni(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await tu(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=_w(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:bn(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:bn(u.command,u.description),ts:i+1});return l}async function Ow(t,e){let n=ni(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await XE.stat(n.store)).mtimeMs)}catch{return null}}import Cw from"node:fs";import nu from"node:fs/promises";import eu from"node:path";import i_ from"node:os";var c_=process.env.BOSTRAT_CODEX_SESSIONS||eu.join(process.env.CODEX_HOME||eu.join(i_.homedir(),".codex"),"sessions");async function Mw(t){try{return await nu.realpath(t)}catch{return t}}function l_(t=c_){let e=[],n=r=>{let o;try{o=Cw.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=eu.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:Cw.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function u_(t,e=1048576){let n;try{n=await nu.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(`
|
|
97
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function d_(t){let e=await u_(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 Qn(t,e){if(!t&&!e)return null;let n=l_();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Mw(t);for(let o of n){let s=await d_(o.file);if(s&&(s===t||await Mw(s)===r))return o}return null}var f_=new Set(["output_text","text"]),p_=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&f_.has(e.type)).map(e=>e.text).join(`
|
|
98
|
+
`).trim():"";async function Nw(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Qn(t,e);if(!o)return[];let s;try{s=await Lt(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,f=null;for(let d of s.split(`
|
|
99
|
+
`)){let p=d.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,f=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:ou,sourceId:m.payload?.turn_id||f||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i}));continue}if(m.type!=="response_item")continue;let w=m.payload;if(!w)continue;if((w.type==="function_call"||w.type==="custom_tool_call")&&w.name){u=!0,ru.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let y=p_(w.content),k=c.length?c.slice():null;c=[],!(!y&&!k)&&a.push({role:"assistant",text:y,tools:k||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i})}if(i)for(let d of a)d.model||(d.model=i);return a.slice(-n)}var ss=new Set(["exec_command","shell","exec"]),ru=new Set(["wait","write_stdin"]),ou="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 m_(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 h_(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+=`
|
|
100
|
+
`;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 w_(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=h_(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function Pw(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Qn(t,e);if(!o)return[];let s;try{s=await Lt(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
101
|
+
`)){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 f=[];if(u.type==="function_call"&&ss.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let m=m_(p);m&&(f=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=w_(u.input));if(!f.length)continue;let d=u.call_id||u.id||null;f.forEach((p,m)=>{a.push({id:d?m?`${d}:${m}`:d:null,tool:u.name,command:p,description:"",summary:bn(p),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function Dw(t,e){let n=await Qn(t,e);if(!n)return null;try{return Math.round((await nu.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var qr=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}},su=class extends qr{transcript(e){return!e?.cwd||!e?.sessionId?null:Oe(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Gc(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?bw(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?xw(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await g_.stat(n)).mtimeMs)}catch{return null}}},au=class extends qr{async readMessages(e,n){return Iw(e.cwd,e.sessionId,n)}async readCommands(e,n){return $w(e.cwd,e.sessionId,n)}async updatedAt(e){return Ow(e.cwd,e.sessionId)}},iu=class extends qr{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Nw(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return Pw(e.cwd,this.sessionId(e),n)}async updatedAt(e){return Dw(e.cwd,this.sessionId(e))}},y_=new qr("unknown"),Bw=new Map;function cu(t){return Bw.set(t.agent,t),t}function as(t){return Bw.get(t)||y_}cu(new su("claude",Zl));cu(new au("cursor",ei));cu(new iu("codex",ss));var lu=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function uu(t,e={}){return(await as(t.agent).readMessages(t,e)).map(lu(t))}async function jw(t,e={}){return(await as(t.agent).readCommands(t,e)).map(lu(t))}async function Lw(t,e={}){return(await as(t.agent).readJobs(t,e)).map(lu(t))}var Hw=/to navigate\b.*\bEsc to cancel|Enter to select\b/,pu=/Chat about this/i,tr=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Fw=/^(type something|chat about this|submit)\b/i,er=/^[─—-]{6,}\s*$/,mu=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function nr(t){let e=t.search(mu);return(e>=0?t.slice(0,e):t).trim()}function b_(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function k_(t){let e=String(t).trim();return!!(!e||er.test(t)||mu.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function x_(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=S_(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function S_(t,e){let n=String(e).toLowerCase(),r=t.findIndex(c=>!c.answered),o=r>=0?r:t.length-1;if(!n)return o;let s=t.map(c=>{let l=c.label.toLowerCase(),u=l.split(/\s+/).filter(d=>d.length>3&&n.includes(d)).length;if(u)return u;let f=l.slice(0,8).trim();return f.length>=4&&n.includes(f)?1:0}),a=Math.max(...s);if(a===0||s[o]===a)return o;let i=s.flatMap((c,l)=>c===a?[l]:[]);return i.length===1?i[0]:o}function Uw(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Ww=/^\s*Switch model\?\s*$/m;function v_(t,e=""){let n=String(t);if(!Ww.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
102
|
+
`),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 f=r[u].match(tr);if(f)s.push({cursor:!!f[1],label:f[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let d=r[u].trim();d&&!er.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:nr(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=["Switch model?",...a].join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:model-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Model",multiSelect:!1,options:i}]}}var qw=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,T_=/^\s*[─—▔-]{6,}\s*$/;function A_(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
103
|
+
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!qw.test(n[r]))return null;let o=r-1;for(;o>=0&&n[o].trim()==="";)o--;let s=[];for(;o>=0;o--){let c=n[o].match(tr);if(!c)break;s.unshift({cursor:!!c[1],n:Number(c[2]),label:c[4].trim()})}if(s.length<2||s.some((c,l)=>c.n!==l+1))return null;let a=[];for(let c=o;c>=0&&a.length<6&&!(T_.test(n[c])||er.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:nr(c.label),description:""}));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,s.findIndex(c=>c.cursor)),promptId:`claude:confirm-gate:${i.map(c=>c.label).join("|")}`.slice(0,240),questions:[{question:a.join(" ")||"Claude Code is asking you to confirm",header:"Confirm",multiSelect:!1,options:i}]}}function E_(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
104
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(tr);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 du=/Resume from summary/i,__=/^\s*This session is\b/;function I_(t,e=""){let n=String(t);if(!du.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
105
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(__.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 f=r[u].match(tr);if(f)s.push({cursor:!!f[1],label:f[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let d=r[u].trim();d&&!er.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>du.test(u.label)))return null;let i=s.map(u=>({label:nr(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 rr=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function hu(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(tr);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 Kw=/Choose the text style that looks best with your terminal/,Gw=/Let's get started\./;function R_(t,e=""){let n=String(t);if(!Kw.test(n)||!Gw.test(n)||rr.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=hu(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:nr(s.label),description:""}))}]}:null}var Vw=/Select login method:/;function $_(t,e=""){let n=String(t);if(!Vw.test(n)||rr.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=hu(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:nr(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 wu=/Paste code here if prompted/,O_=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function C_(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!O_.test(o)||wu.test(t[r]))break;n+=o}return n}function M_(t,e=""){let n=String(t);if(!wu.test(n)||rr.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
108
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=C_(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 fu=/OAuth error:/,N_=/Press Enter to retry\./;function P_(t,e=""){let n=String(t);if(!fu.test(n)||!N_.test(n)||rr.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
109
|
+
`)].reverse().find(a=>fu.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 zw=/Quick safety check: Is this a project you created or one you trust/;function D_(t,e=""){let n=String(t);if(!zw.test(n)||rr.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
110
|
+
`),o=hu(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:nr(s.label),description:""}))}]}:null}var Jw=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function B_(t,e=""){let n=String(t);if(rr.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
111
|
+
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(Jw);if(!s)return null;let a=[];for(let l=o-1;l>=0&&a.length<3;l--){let u=r[l].trim();if(!u){if(a.length)break;continue}if(er.test(r[l])||mu.test(u))break;a.unshift(u)}let i=s[1].trim();i&&a.push(i);let c=a.join(" ")||"Claude Code is waiting for you to continue.";return{provider:"claude",box:e,multiSelect:!1,cursorIndex:0,setup:"continue",promptId:`claude:setup:continue:${c}`.slice(0,200),questions:[{question:c,header:"Continue",multiSelect:!1,options:[{label:"Continue",description:""}]}]}}function j_(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(pu.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 L_(t,e=""){let n=String(t);if(!Hw.test(n)||!pu.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
112
|
+
`),o=j_(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let v=i;v<c;v++){let M=r[v].match(tr);M&&(u<0&&(u=v),l.push({line:v,cursor:!!M[1],checked:M[3]!=null&&M[3]!==" ",hasCheckbox:M[3]!=null,label:M[4].trim(),desc:""}))}if(!l.length)return null;for(let v of l)for(let M=v.line+1;M<Math.min(r.length,v.line+4)&&!(tr.test(r[M])||er.test(r[M]));M++){let D=r[M].trim();if(!(!D||k_(r[M])||Fw.test(D))){v.desc=b_(D);break}}let f=l.filter(v=>!Fw.test(v.label));if(!f.length)return null;let d=f.some(v=>v.hasCheckbox),p=Math.max(0,f.findIndex(v=>v.cursor)),m=s>=0?s+1:0,w=[];for(let v=m;v<u;v++){let M=r[v].trim();M&&!er.test(r[v])&&!/[☐☒]/.test(r[v])&&w.push(M)}let y=w.join(" ")||(s>=0?Uw(r[s]):"")||"The agent is asking a question",k=s>=0?x_(r[s],y):null,S=k?.currentTabLabel||(s>=0?Uw(r[s]):""),_=f.map(v=>({label:nr(v.label),description:v.desc})),$=_.map(v=>v.label).join("|"),O=`claude:${k?`q${k.questionIndex+1}/${k.questionTotal}`:"q1/1"}:${S}:${y}:${$}`.slice(0,240),P=k?`claude:ask:${k.tabs.map(v=>v.label).join("|")}`.slice(0,240):O;return{provider:"claude",box:e,multiSelect:d,cursorIndex:p,promptId:O,groupId:P,...k?{questionIndex:k.questionIndex,questionTotal:k.questionTotal,currentTabLabel:k.currentTabLabel,tabs:k.tabs}:{},questions:[{question:y,header:S,multiSelect:d,options:_}]}}function Kr(t,e){let n=String(e||"");return n?t==="claude"?Hw.test(n)&&pu.test(n)||Ww.test(n)||du.test(n)||qw.test(n.trimEnd().split(`
|
|
113
|
+
`).pop()||"")?!0:rr.test(n)?!1:Kw.test(n)&&Gw.test(n)||Vw.test(n)||wu.test(n)||fu.test(n)||zw.test(n)||Jw.test(n.trimEnd().split(`
|
|
114
|
+
`).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 or(t,e){return!t||!e?null:t.agent==="claude"?E_(e,t.name)||L_(e,t.name)||v_(e,t.name)||I_(e,t.name)||P_(e,t.name)||M_(e,t.name)||$_(e,t.name)||R_(e,t.name)||D_(e,t.name)||B_(e,t.name)||A_(e,t.name):t.agent==="cursor"||t.agent==="codex"?Bp(t.agent,e,t.name):null}async function gu(t){if(!t)return null;let e=await cn(t.name).catch(()=>"");return or(t,e)}function Xw(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 Yw(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 Gr(t){return typeof t=="string"?t.trim():""}var Zw=/^\s*─{8,}\s*$/,F_=/^❯[ ]?(.*)$/;function is(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
115
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(Zw.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(Zw.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(F_);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 xt(t){return String(t||"").replace(/\s+/g," ").trim()}function Qw(t,e,{slackMs:n=2e4}={}){let r=xt(e?.text);return!r||typeof e?.at!="number"?!1:(Array.isArray(t)?t:[]).some(o=>o?.role==="user"&&typeof o.ts=="number"&&o.ts>=e.at-n&&xt(o.text).includes(r))}function tg(t,e,n,{busy:r=!1}={}){let o=is(t,n);return o&&yu(e,o)?"stuck":r||o?"submitted":"unknown"}function yu(t,e){let n=xt(t),r=xt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import U_ from"node:crypto";var bu=new Set(["cursor","codex"]),ng=4,H_=4,W_=2,q_=24,eg=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,cs=t=>typeof t=="string"?t.trim():"";function K_(t){let e=Array.isArray(t)?t:Array.isArray(t?.questions)?t.questions:null;if(!e||!e.length)return null;let n=[];for(let r of e.slice(0,ng)){let o=cs(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,H_)){let c=cs(i?.label);if(!c)return null;let l=cs(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<W_)return null;n.push({question:o,header:cs(r?.header).slice(0,q_),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function ku(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;eg.lastIndex=0;let e=null;for(let o;o=eg.exec(t);)e=o;if(!e||e.index+e[0].length<t.trimEnd().length)return null;let n;try{n=JSON.parse(e[2])}catch{return null}let r=K_(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function xu(t,e,n){let r=U_.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function Su(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=ng}={}){if(!Array.isArray(t)||!e||!bu.has(n))return null;let s=null;for(let l=t.length-1;l>=0;l--){let u=t[l];if(u?.kind==="prompt")return null;if(u?.kind==="message"){if(u.role!=="assistant")return null;s=u;break}}if(!s||s.box!==e||s.agent!==n)return null;let a=s.ask?.questions||ku(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=xu(n,s.sourceId||s.id,a);return typeof r=="function"&&r(c)?null:{provider:n,box:e,promptId:c,groupId:c,answerMode:"say",source:"transcript",multiSelect:i[0].multiSelect,questionIndex:0,questionTotal:i.length,questions:i}}function rg(t,e,n){let r=[];(t?.questions||[]).forEach((a,i)=>{let c=(e?.[i]||[]).map(l=>a.options?.[l]?.label).filter(Boolean);c.length&&r.push({name:a.header||a.question,picks:c.join(", ")})});let o=cs(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
116
116
|
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
117
117
|
`)}`:s=o,r.length&&o&&(s+=`
|
|
118
|
-
${o}`),s||""}var
|
|
118
|
+
${o}`),s||""}var og=`
|
|
119
119
|
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
120
120
|
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
121
121
|
question in prose. End your message with exactly ONE fenced code block tagged
|
|
@@ -135,42 +135,43 @@ your turn. The operator sees the questions as an interactive card in their chat
|
|
|
135
135
|
answer. Never use the block for rhetorical questions, progress updates, or
|
|
136
136
|
anything you can decide yourself; keep asking in prose for questions without
|
|
137
137
|
enumerable options.
|
|
138
|
-
`;import{execFile as
|
|
139
|
-
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function
|
|
140
|
-
`).map(s=>s.trim()).filter(Boolean)}async function
|
|
141
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import
|
|
142
|
-
`),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
|
|
143
|
-
`),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
|
|
144
|
-
`);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
|
|
145
|
-
`),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 kg(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&&!gg.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=yg.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!qE.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function cs(t,e,{timeoutMs:n=HE}={}){let r=Date.now()+n;for(;;){let o=await mt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await ri(WE)}}async function VE(t,e){return await Et(t,`/model ${e.model}`,Ce("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function zE(t,e){await ca(t),await Et(t,"/model",{enter:!1}),await ri(250),await vt(t,["Enter"],{kind:"model-switch"});let n=await cs(t,pg);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await vt(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=GE(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await Et(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await cs(t,i=>mg(i)||!pg(i)&&o(i)&&{done:o(i)});if(!s)throw await vt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?gg.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 vt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await Et(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await cs(t,i=>!mg(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 JE(){let t=fg.join(process.env.CURSOR_CONFIG_DIR||fg.join(UE.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await FE.readFile(t,"utf8"))?.model||null}catch{return null}}async function XE(t,e){await ca(t),await ua(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await cs(t,hg),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 cs(t,hg,{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 vt(t,["Down"],{kind:"model-switch"}),await ri(250)}if(!n||!s||!o(s)){await vt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await vt(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 vt(t,["Enter"],{kind:"model-switch"}),await ri(800);let a=await JE();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 xg(t,e,n){return t==="codex"?zE(e,n):t==="cursor"?XE(e,n):VE(e,n)}var Yt=t=>t||null;function si(t){let e=t||{},n=YE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function YE(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Yt(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:Yt(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:Yt(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:Yt(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:Yt(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:Yt(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:Yt(t.agent),payload:{...Gr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Yt(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:Yt(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:Yt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Gr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var oi=Promise.resolve(),Eu=t=>(oi=oi.then(t).catch(()=>{}),oi),vg=()=>oi;function Tg(t,e){let n=si(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Eu(()=>I(r,n))}var Sg=new Set;async function Ag(t){if(!(!t?.id||Sg.has(t.id))){Sg.add(t.id);try{let e=await L(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=si(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await I(t,o)}}catch{}}}function Gr(t){let{type:e,at:n,...r}=t;return r}var _u=new Map;function _g(t,e,n){!n?.promptId||_u.get(t.id)===n.promptId||(_u.set(t.id,n.promptId),Eu(()=>I(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Yt(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 Iu(t,e,n,r={}){_u.delete(t.id),Eu(()=>I(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Yt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as RI}from"node:crypto";var Ig=/(?<!\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,Rg=/(?<!\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,$g=/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,Og=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,ZE=new RegExp(`\\b(?:${Ig}|${Rg}|${$g}|${Og})\\b`,"i"),QE=/\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,tI=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,eI=90;function nI(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Mg(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of nI(n).reverse()){if(!ZE.test(r))continue;let o=r.endsWith("?")&&r.length<=eI&&tI.test(r);if(QE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Cg=["commit","rebase","push","pr"],rI=new RegExp(`\\b(?:${Ig})\\b`,"i"),oI=new RegExp(`\\b(?:${Og})\\b`,"i"),sI=new RegExp(`\\b(?:${Rg})\\b`,"i"),aI=new RegExp(`\\b(?:${$g})\\b`,"i"),iI=/\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 Ng(t){let e=String(t||""),n=[];rI.test(e)&&n.push("commit"),oI.test(e)&&n.push("rebase"),sI.test(e)&&n.push("push");let r=iI.test(e);!r&&aI.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 ai(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var cI=3e4;function Pg(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<=cI&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Dg(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Eg={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Bg(t){let e=(Array.isArray(t)?t:[]).filter(r=>Eg[r]).map(r=>Eg[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as pI}from"node:child_process";import{promisify as mI}from"node:util";import lI from"node:fs";import Ru from"node:fs/promises";import Ug from"node:path";import uI from"node:os";var Hg=process.env.BOSTRAT_STATE_DIR||Ug.join(process.env.HOME||uI.tmpdir(),".bostrat"),ii=Ug.join(Hg,"github-metrics.json"),Wg=60*6e4,jg=5*6e4,dI=6e4,Vr=Date.now(),Ke=new Map,bn=null,or=0,Lg=0,ci=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(lI.readFileSync(ii,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Wg?(Vr=t.windowStart,Ke=new Map(Object.entries(t.byCaller||{})),bn=t.lastHour||null):ci(new Map(Object.entries(t.byCaller||{})))&&(bn={start:t.windowStart,total:ci(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var Fg=Promise.resolve();function qg(t,e=!1){if(!e&&t-Lg<dI)return;Lg=t;let n=JSON.stringify({windowStart:Vr,byCaller:Object.fromEntries(Ke),lastHour:bn});Fg=Fg.then(()=>Ru.mkdir(Hg,{recursive:!0})).then(()=>Ru.writeFile(`${ii}.tmp`,n)).then(()=>Ru.rename(`${ii}.tmp`,ii)).catch(()=>{})}function Kg(t){t-Vr<Wg||(bn={start:Vr,total:ci(Ke),byCaller:Object.fromEntries(Ke)},bn.total&&console.log(`[github] ${bn.total} calls last hour`,bn.byCaller),Vr=t,Ke=new Map,qg(t,!0))}function Ge(t="other",e=Date.now()){Kg(e),Ke.set(t,(Ke.get(t)||0)+1),qg(e)}function Gg(t=Date.now()){return Kg(t),{windowStart:Vr,total:ci(Ke),byCaller:Object.fromEntries(Ke),lastHour:bn,backoffUntil:or>t?or:null}}var fI=/rate limit|too quickly|abuse detection|HTTP 429/i;function kn(t){return fI.test(String(t?.message||t||""))}function xn(t,e=Date.now()){return or<=e&&console.warn(`[github] rate limited \u2014 backing off ${jg/6e4} min:`,String(t?.message||t||"").slice(0,200)),or=Math.max(or,e+jg),or}function li(t=Date.now()){return Math.max(0,or-t)}var Vg=()=>Number(process.env.BOSTRAT_NARROW_CARRY_MS)||9e4,ls=new Map;function ui(t,e,n){let{state:r,reason:o}=Hc(e,n);if(r!=="unknown"){if(t){let a=Date.now();for(let[i,c]of ls)a-c.at>=Vg()&&ls.delete(i);ls.set(t,{busy:r==="busy",at:a})}return r==="busy"}if(o!=="narrow"||!t)return!1;let s=ls.get(t);return s?Date.now()-s.at>=Vg()?(ls.delete(t),!1):s.busy:!1}async function $u(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await mt(t,{lines:_t}).catch(()=>"");if(!ui(t,e,r))return Op(e,r)?{working:!0,prompted:!1}:n;if(Ur(e,r)){let o=await an(t).catch(()=>r);if(nr({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function di(t,e){return(await $u(t,e)).working}var zg=mI(pI),Jg=[4e3,15e3,45e3],hI=2500,wI=5*6e4;async function Ou(t,e,{midTurn:n=di,pollMs:r=hI,capMs:o=wI}={}){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 gI=6e4,yI=9e4,bI=2e3,kI=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,xI=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,SI=/\s--web\b/,Xg=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Mu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function Yg(t){let e=String(t||""),n=e.match(kI);if(!n)return null;let r=Mu(e,n.index);return SI.test(r)?null:{repo:r.match(Xg)?.[1]??null}}var vI=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function Zg(t){let e=String(t||""),n=e.match(xI);if(!n)return null;let r=Mu(e,n.index),o=Mu(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("-")){vI.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Xg)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var TI="number,url,title,headRefName,createdAt";async function AI(t,e){let n=async a=>{Ge("observer");let{stdout:i}=await zg("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",TI],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw kn(c)&&xn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-gI),o=await de({slug:t}),s=r(await n(o));return!s.length&&o.GH_TOKEN&&(s=r(await n({}).catch(()=>[]))),s.sort((a,i)=>Date.parse(i.createdAt)-Date.parse(a.createdAt))}async function _I(t,e){if(e)return[e];let n=[];for(let r of Qg(t)){let o=await ot(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await Re().catch(()=>[])){let o=await ot(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function Qg(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function EI(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await _I(t,e)){let a;try{a=await AI(s,n)}catch{continue}for(let i of a){let c=await Bt(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 Sn=new Set;function ty(t,e,{delays:n=Jg,turnEnd:r}={}){let o=Yg(e.command);if(!o)return null;let s=!!Zg(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Sn.has(a))return null;Sn.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await EI(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await Xt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await zo(f,d.number))?null:(await Ou(e.box,e.agent,r),await I({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:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{Sn.delete(a)}})();return c.catch(()=>Sn.delete(a)),c}var II="number,url,title,headRefName,state";function ey(t,e,{delays:n=Jg,turnEnd:r,openedWait:o}={}){let s=Zg(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Sn.has(a))return null;Sn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let m=Qg(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of m){let y=await ot(w).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(m,w)=>{let y=await Bt(c,m.number);return y&&y.trackId!==t.id?null:(y||await Xt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Jo(c,m.number),w&&((await L(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===m.number)||(await Ou(e.box,e.agent,r),await I({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:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise(R=>setTimeout(R,m));let w=[];for(let R of f)try{Ge("observer");let{stdout:C}=await zg("gh",["pr","view",String(l),"--repo",R,"--json",II],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await de({slug:R})}}),v=JSON.parse(C||"null");v?.number&&w.push({slug:R,pr:v})}catch(C){kn(C)&&xn(C)}let y=w.find(R=>R.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(p=b,s.auto)return d(b,!0);continue}let S=await Bt(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Xt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await Ou(e.box,e.agent,r);let E=await L(t).catch(()=>[]);if(E.some(R=>R.type==="pr.state.changed"&&R.payload?.change==="merged"&&R.payload?.number===b.number))return null;let O=R=>R.some(C=>C.type==="pr.state.changed"&&C.payload?.change==="opened"&&C.payload?.number===b.number),nt=0;if(!O(E)){if(Yg(e.command)&&await zo(c,b.number)){let R=Date.now();nt=R+1,await I({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:R,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Bt(c,b.number))?.announced){let R=o?.capMs??yI,C=o?.pollMs??bI,v=Date.now()+R;for(;Date.now()<v&&(await new Promise(D=>setTimeout(D,C)),!O(await L(t).catch(()=>[]))););}}return await I({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(),nt),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await Tn(c,b.number).catch(()=>{}),await vn(c,b.number).catch(()=>{}),await wn(c,b.number,"merged"),b}return p?d(p,!1):null}finally{Sn.delete(a)}})();return i.catch(()=>Sn.delete(a)),i}function Nu(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function ny(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function ry(t){return`job|${t.box}|id:${t.id}`}var Cu=new Map,fi=new Map;async function Pu(t){return Cu.has(t.id)?Cu.get(t.id):(fi.has(t.id)||fi.set(t.id,L(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(Nu({role:o,sourceId:r.payload.sourceId})),n.keys.add(Nu({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(ny({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(ry({id:r.payload.id,box:r.boxId}));return Cu.set(t.id,n),n}).finally(()=>fi.delete(t.id))),fi.get(t.id))}async function oy(t,e){try{let n=(await Pu(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Nu(r);if(!n.has(o)&&(n.add(o),await I(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=Mg(r.text);if(s){let{offered:a,toPr:i}=Ng(s),c;if(t.workspace?.kind==="multi"){let l=await us(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await I(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:RI(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function sy(t,e){try{let n=await Pu(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=ny(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await I(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}}),ty(t,o),ey(t,o))}}catch{}}async function ay(t,e){try{let n=await Pu(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=ry(o);n.keys.has(s)||(n.keys.add(s),await I(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 $I=/^Base directory for this skill:\s*(\S+)/,OI=/^Run the "([^"]+)" workflow\./,MI=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,CI=/^<local-command-(?:stdout|caveat)>/i,NI=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,PI=/^Continue from where you left off\.$/,DI=/^<task-notification>/i,BI=/<summary>([\s\S]*?)<\/summary>/i,jI=/<task-id>([\s\S]*?)<\/task-id>/i,LI=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,FI=/<status>([\s\S]*?)<\/status>/i,UI=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Du=new Set(["model","clear","compact","login","exit","quit"]);function iy(t){return Du.has(String(t||"").replace(/^\//,"").toLowerCase())}var HI=/^\/([a-z0-9:_-]+)$/i;function pi(t){let e=String(t||"").trim();if(!e)return null;let n=e.match($I);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(OI);if(r)return{kind:"skill",name:r[1]};let o=e.match(MI);if(o){let a=o[1].toLowerCase();return Du.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(HI);if(s&&Du.has(s[1].toLowerCase()))return{kind:"suppress"};if(CI.test(e))return{kind:"suppress"};if(PI.test(e))return{kind:"suppress"};if(NI.test(e))return{kind:"suppress"};if(DI.test(e)){let a=c=>WI(e.match(c)?.[1].trim()||"")||null,i=a(UI);return{kind:"task",summary:a(BI)||"",taskId:a(jI),toolUseId:a(LI),status:a(FI)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function WI(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function cy(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 ly(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var ct=t=>t.payload?.ts??t.ts;function qI(t){return{id:t.id,kind:"prompt",groupId:t.groupId,provider:t.provider,box:t.box,agent:t.provider,questionTotal:t.questions.length,answeredVia:"chat",questions:t.questions.map((e,n)=>({index:n,header:e.header||"",question:e.question,multiSelect:!!e.multiSelect,chosen:[]})),ts:t.ts}}var KI=new Set(["opened","ci-fixed","reconciled","pushed"]),uy=t=>`${t.repo??""}#${t.number}`;function GI(t,e,n){let r=uy(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function ds(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$/,f=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let h=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(h))for(let A of h.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=A[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(g.payload?.ts??g.ts)}}let d=3*6e4,p=20*6e4,m=(g,h)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(_=>_>=h-d&&_<=h+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let h=St(g.payload?.text);h&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(h))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let h=St(g.payload?.text);h&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(h))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let h=g.payload.note,A={status:h.status||null,summary:h.summary||null,resultHead:h.resultHead??null,ts:ct(g)};h.toolUseId&&i.set(h.toolUseId,A);let _=String(h.summary||"").match(u);_&&l.set(_[1],A)}let w=new Map,y=new Map,b=null,S=new Map,E=new Map,O=new Map,nt=new Map,R=new Map,C=new Map,v=new Map,D=()=>{for(let g of v.values())g.questions.length&&e.push(g);v.clear()},B=null,lt=6e4,q=new Map,T=(g,h)=>{let A=o.get(g);if(!A?.length)return!1;let _=St(h),J=s.get(g);return A.some(W=>{if(!_.startsWith(W))return!1;let it=St(_.slice(W.length));return!!it&&!!J?.has(it)})};for(let g of t||[]){let h=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(h.text||"").trim()&&!h.images?.length)break;{let A=h.note||pi(h.text);if(A){if(A.kind==="task"){if(A.toolUseId&&a.has(A.toolUseId))break;let _=String(A.summary||"").match(u);if(!A.toolUseId&&_&&c.has(_[1]))break}if(A.kind==="command"&&iy(A.command))break;A.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:ly(A),name:A.name??null,command:A.command??null,summary:A.summary??null,ts:ct(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${ct(g)}`)||g.source==="transcript"&&T(g.boxId,h.text))break;if(h.kind==="merge-notify"||h.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:h.kind==="ship-notify"?"ship-notified":"merge-notified",text:h.text,ts:ct(g),box:g.boxId,agent:g.provider});break}if(h.kind==="mock-picked"||h.kind==="ci-fix"||h.kind==="reconcile"||h.kind==="pr-go"||h.kind==="push-go"||h.kind==="spin-up-view"||h.kind==="prompt-answer"){h.kind==="prompt-answer"&&(B=null);break}for(let A of S.values())A.status="stale";S.clear(),B&&B.box===g.boxId&&(e.push(qI(B)),B=null),e.push({id:g.id,kind:"message",role:"user",text:h.text,ts:ct(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...g.actor==="orchestrator"?{orchestration:h.kind||!0}:{},...h.sourceId?{sourceId:h.sourceId}:{},...h.attachments?.length?{attachments:h.attachments}:{},...h.images?.length?{images:h.images}:{}});break}case"turn.agent":{if(h.model==="<synthetic>"&&/^No response requested\.$/.test(String(h.text||"").trim()))break;D();let A=(h.images||[]).filter(it=>!m(it,ct(g)));if(!String(h.text||"").trim()&&!A.length)break;if(g.provider==="cursor"){let it=`${g.boxId}|${h.text}`;if(n.has(it))break;n.add(it)}(!B||B.box===g.boxId)&&(B=null);let _=g.provider==="codex"?(h.tools||[]).filter(it=>!zl.has(it)&&!es.has(it)):h.tools||[],J=h.text,W=null;if(uu.has(g.provider)){let it=du(String(h.text||""));it&&(J=it.text,W={questions:it.questions})}e.push({id:g.id,kind:"message",role:"assistant",text:J,ts:ct(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...W?{ask:W}:{},..._.length?{tools:_}:{},...h.model?{model:h.model}:{},...h.sourceId?{sourceId:h.sourceId}:{},...A.length?{images:A}:{}}),W&&(B={id:g.id,groupId:fu(g.provider,h.sourceId||g.id,W.questions),provider:g.provider,box:g.boxId,questions:W.questions,ts:ct(g)});break}case"job.started":{if(!h.id)break;let A=ct(g),_=i.get(h.id);if(!_&&(h.kind||"agent")==="agent"&&h.name){let K=l.get(h.name);K&&K.ts>=A&&(_=K)}let J={jobId:h.id,jobKind:h.kind||"agent",name:h.name||null,detail:h.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:A,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},W=q.get(g.boxId),it=W?.jobs[W.jobs.length-1]?.startedTs;if(W&&it!=null&&A-it<=lt)W.jobs.push(J);else{let K={id:g.id,kind:"job-dock",dockId:h.id,ts:A,box:g.boxId,agent:g.provider,jobs:[J]};q.set(g.boxId,K),e.push(K)}break}case"handoff.done":e.push(Te(g,"lead-handoff"));break;case"subtask.delegated":e.push({...Te(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(Te(g,"subtask-merged"));break;case"pr.state.changed":{if(!h.change)break;if(h.change==="opened"){for(let _ of S.values())_.status="stale";S.clear()}let A=Te(g,`pr-${h.change}`);if(KI.has(h.change)&&h.number!=null){let _=GI(w,y,h);if(w.size>=2){for(let W of y.values())for(let it of W)it.grouped=!0;A.groupPrs=[...w.values()];let J=[A];for(let W of w.keys())y.set(W,J)}else{let J=y.get(_)??[];J.push(A),y.set(_,J)}}else if(h.change==="merged"||h.change==="closed"||h.change==="lost"){let _=uy(h),J=b&&!b.complete||w.size>=2;if(w.has(_))w.delete(_),y.delete(_);else if(h.number!=null)for(let W of[...w.keys()])W.endsWith(`#${h.number}`)&&(w.delete(W),y.delete(W));if(J&&h.number!=null){(!b||b.complete)&&(b={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:ct(g),ts:ct(g),box:g.boxId,agent:g.provider},e.push(b)),b.prs.push({number:h.number,url:h.url??null,title:h.title??null,repo:h.repo??null,branch:h.branch??null,change:h.change,ts:ct(g)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(A);break}case"pr.offered":{if(!h.offerId)break;for(let _ of S.values())_.status="stale";S.clear();let A={id:g.id,kind:"pr-offer",offerId:h.offerId,excerpt:h.excerpt||null,branch:h.branch||null,base:h.base||null,offered:Array.isArray(h.offered)&&h.offered.length?h.offered:null,toPr:h.toPr&&typeof h.toPr=="object"?{number:h.toPr.number??null}:null,repos:Array.isArray(h.repos)&&h.repos.length?h.repos:null,status:"open",ts:ct(g),box:g.boxId,agent:g.provider};S.set(h.offerId,A),E.set(h.offerId,A),e.push(A);break}case"pr.offer.answered":{let A=E.get(h.offerId);A&&(A.status=h.action||"dismiss",A.actions=Array.isArray(h.actions)?h.actions:null,A.notify=h.notify===!0,S.delete(h.offerId));break}case"box.state.changed":{let A=h.change==="model"?"model-change":h.change;if(!A)break;let _=Te(g,A);A==="model-change"&&(_.to=h.model),e.push(_);break}case"view.state.changed":{if(h.change!=="created"||h.restarted)break;e.push({id:g.id,kind:"view-card",viewId:h.viewId,name:h.name,slug:h.slug,port:h.port,origin:h.origin,ts:ct(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:h.name||null,reason:h.reason||"no-view",ts:ct(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(h.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:h.mockId,name:h.name,group:h.group??null,version:h.version??null,siblings:h.siblings??[],ts:ct(g),box:g.boxId,agent:g.provider});break}if(h.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:h.mockId,name:h.name,group:h.group??null,ts:ct(g),box:g.boxId,agent:g.provider});break}case"flow.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"flow-card",flowId:h.flowId,name:h.name,stepCount:h.stepCount??null,mockIds:h.mockIds??[],ts:ct(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:h.runbookId,name:h.name,ts:ct(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:h.provider||"linear",key:h.key,url:h.url,title:h.title??null,linkSource:h.linkSource??null,ts:ct(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!h.offerId)break;for(let _ of O.values())_.key===h.key&&(_.status="stale",O.delete(_.offerId));let A={id:g.id,kind:"ticket-offer",offerId:h.offerId,provider:h.provider||"linear",key:h.key,url:h.url??null,phase:h.phase??null,from:h.from??null,to:h.to?.name??null,prNumber:h.prNumber??null,status:"open",ts:ct(g),box:g.boxId,agent:g.provider};O.set(h.offerId,A),nt.set(h.offerId,A),e.push(A);break}case"ticket.move.answered":{let A=nt.get(h.offerId);A&&(A.status=h.action==="moved"?"moved":"dismiss",O.delete(h.offerId));break}case"ticket.state.changed":{for(let A of O.values())A.key===h.key&&A.to===h.to&&(A.status="stale",O.delete(A.offerId));if(h.mode==="offer")break;e.push(Te(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:h.key,url:h.url??null,excerpt:h.excerpt??null,ts:ct(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(Te(g,"ticket-flow"));break;case"ticket.create.offered":{if(!h.proposalId)break;for(let _ of R.values())_.status="stale";R.clear();let A={id:g.id,kind:"ticket-create-offer",proposalId:h.proposalId,provider:h.provider||"linear",epic:h.epic??null,issues:Array.isArray(h.issues)?h.issues:[],team:h.team??null,needsTeam:!!h.needsTeam,linkCurrentTrack:!!h.linkCurrentTrack,status:"open",ts:ct(g),box:g.boxId,agent:g.provider};R.set(h.proposalId,A),C.set(h.proposalId,A),e.push(A);break}case"ticket.create.answered":{let A=C.get(h.proposalId);A&&(A.status=h.action==="created"?"created":"dismiss",A.createdCount=h.createdCount??null,A.failedCount=h.failedCount??null,R.delete(h.proposalId));break}case"ticket.created":{let A={items:Array.isArray(h.items)?h.items:[],failed:Array.isArray(h.failed)?h.failed:[],team:h.team??null},_=h.proposalId?C.get(h.proposalId):null;if(_){_.created=A;break}e.push({id:g.id,kind:"ticket-created",...A,ts:ct(g),box:g.boxId,agent:g.provider});break}case"error":{if(!h.kind)break;e.push(Te(g,h.kind));break}case"usage.cap.hit":e.push(Te(g,"usage-cap-hit"));break;case"usage.cap.warning":e.push(Te(g,"usage-cap-warning"));break;case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:h.provider||g.provider,ts:ct(g),at:ct(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(B=null,h.control||!h.question)break;let A=h.groupId||h.promptId;if(!A)break;let _=v.get(A);_||(_={id:g.id,kind:"prompt",groupId:A,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:h.questionTotal??1,questions:[],ts:ct(g)},v.set(A,_));let J=h.questionIndex??0,W={index:J,header:h.header||"",question:h.question,multiSelect:!!h.multiSelect,chosen:Array.isArray(h.chosen)?h.chosen:[]},it=_.questions.findIndex(K=>K.index===J&&St(K.question)===St(W.question));it>=0?_.questions[it]=W:_.questions.push(W),_.questions.sort((K,Y)=>K.index-Y.index),_.questionTotal=Math.max(_.questionTotal,h.questionTotal??1),_.questions.length>=_.questionTotal&&(e.push(_),v.delete(A));break}default:break}}return zI(e.sort((g,h)=>(g.ts??0)-(h.ts??0)))}var VI=2*6e4;function zI(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<=VI){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 Bu(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:JI(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??ds(t)).filter(o=>o.kind==="message");return lw(n,r)}function JI(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Br(t.command)?e:gn(t.command,t.description||"")}function Te(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:ct(t),ts:ct(t)}}var XI=12e4,YI=new Set(["archived","unarchived","opened"]);function dy(t){let e=[...t];for(let n=0;n<e.length;n++){let r=e[n];if(r?.kind!=="event"||r.type!=="archived")continue;let o=(r.ts??0)+XI,s=n;for(;s+1<e.length;){let a=e[s+1];if((a?.ts??0)>o||a?.kind==="event"&&YI.has(a.type))break;s++}s>n&&e.splice(s,0,...e.splice(n,1)),n=s}return e}function ZI(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
146
|
-
`)){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 ju=new Map;async function QI(t,e){let n=`${t.id}:${e.name}`;if(!ju.has(n)){let r=await L(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);ju.set(n,o)}return ju.get(n)}async function fy(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Yn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await QI(t,e),o=ZI(await Ft(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 I(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:me(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await I(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 mi from"node:fs";import Fu from"node:path";import tR from"node:os";var eR=process.env.BOSTRAT_STATE_DIR||Fu.join(process.env.HOME||tR.tmpdir(),".bostrat"),my=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,nR="https://cursor.com/api/dashboard/get-filtered-usage-events",py=100;async function rR({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(nR,{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:py})});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<py)break}return r}function oR(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 sR=5*6e4,An={at:0,rows:null,error:null},hi=()=>Fu.join(eR,"cursor-usage.json");function hy(){try{return JSON.parse(mi.readFileSync(hi(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function aR(t){try{mi.mkdirSync(Fu.dirname(hi()),{recursive:!0});let e=hi()+".tmp";mi.writeFileSync(e,JSON.stringify(t)),mi.renameSync(e,hi())}catch{}}var iR=5e3;async function cR({fetchImpl:t,now:e=Date.now()}={}){if(An.rows&&e-An.at<sR)return An.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=hy(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await rR({sinceMs:o,cookie:n,fetchImpl:t});An={at:e,rows:s.map(oR).filter(Boolean),error:null}}catch(s){An={at:e,rows:null,error:String(s?.message||s)}}return An.rows}var Uu=()=>({enabled:my(),lastPollAt:An.at||null,error:An.error}),Lu=new Map;async function lR(t,e){let n=`${t.id}:${e.name}`;if(!Lu.has(n)){let r=await L(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);Lu.set(n,o)}return Lu.get(n)}async function wy(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!my())return;let r=await cR({fetchImpl:n});if(!r||!r.length)return;let o=await yw(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await lR(t,e),a=hy(),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",f=l.tokens||null,d=l.vendorCents!=null?l.vendorCents/100:0;!d&&f&&(d=me(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await I(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:d,model:u,...f?{tokens:f}:{},...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}})}aR({sinceMs:c,seen:[...i].slice(-iR)})}catch{}}import{execFile as mR}from"node:child_process";import{promisify as hR}from"node:util";import gy from"node:fs/promises";import yy from"node:path";import uR from"node:os";var by=process.env.BOSTRAT_STATE_DIR||yy.join(process.env.HOME||uR.tmpdir(),".bostrat"),dR=yy.join(by,"github-latency.ndjson"),fR=100,wi=[],Hu=new Map;function gi({change:t,repo:e=null,number:n=null,githubAt:r=null,observedAt:o=Date.now(),lane:s=null,intervalMs:a=null,source:i=null}){let c=r?o-r:null,l={change:t,repo:e,number:n,githubAt:r,observedAt:o,latencyMs:c,lane:s,intervalMs:a,source:i};wi.push(l),wi.length>fR&&wi.shift();let u=Hu.get(t)||{count:0,measured:0,totalMs:0,maxMs:0};return u.count+=1,c!=null&&(u.measured+=1,u.totalMs+=c,u.maxMs=Math.max(u.maxMs,c)),Hu.set(t,u),pR(l),l}async function pR(t){try{await gy.mkdir(by,{recursive:!0}),await gy.appendFile(dR,JSON.stringify(t)+`
|
|
147
|
-
`)}catch{}}function ky(){let t={};for(let[e,n]of Hu)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:wi.slice(-20)}}async function wR(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([Jr("git",["-C",t,"rev-parse","HEAD"]),Jr("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await I({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var Jr=hR(mR),gR={timeout:3e4,maxBuffer:8*1024*1024};async function ne(t,e,n={}){let{caller:r="prs",...o}=n,s=await de({cwd:t});Ge(r);try{let{stdout:a}=await Jr("gh",e,{cwd:t,...gR,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw kn(a)&&xn(a),a}}var yi=null;async function Ku(){if(yi&&Date.now()-yi.at<6e4)return yi.data;let t;try{Ge("auth");let e=await Jr("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"||e?.code===127?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await xo();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return yi={at:Date.now(),data:t},t}async function yR(t){try{let{stdout:e}=await Jr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Sy(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function Wu(t){let e=(t||[]).map(Sy),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var Gu="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",vy=Gu.replace("statusCheckRollup,","");function Vu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Ty(t,e){if(!e)return null;let n=await ot(t);if(!n)return null;try{let r=await ne(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 Ay(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function _y(t,e){let n=await Ty(t,e);return n===null?{...Wu([]),unavailable:!0}:Wu(Ay(n))}function bi(t,e=Wu(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 bR=9e4,zr=new Map;function kR(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){zr.delete(s);continue}r.add(s),zr.has(s)||zr.set(s,n),o.mergeabilityPending=n-zr.get(s)<bR}for(let o of zr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&zr.delete(o);return e}async function Xr(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await ne(n.path,["pr","view",String(r),"--json",Gu]);return bi(JSON.parse(o))}catch(o){if(!Vu(o))throw o;let s=await ne(n.path,["pr","view",String(r),"--json",vy]),a=JSON.parse(s);return bi(a,await _y(n.path,a.headRefOid))}}var xy=(t,e)=>t.number-e.number;async function ps(t,{caller:e="prs"}={}){try{let n=await ne(t,["pr","list","--json",Gu,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>bi(r)).sort(xy)}catch(n){if(!Vu(n))throw n;let r=await ne(t,["pr","list","--json",vy,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>bi(o,await _y(t,o.headRefOid))))).sort(xy)}}var xR=3e4,SR=1500,fs=new Map;function _e(){fs.clear()}async function vR(t,e){if(!t)return e;for(let n of e){let r=await Bt(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await qa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ga(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Ey(){let t=await Ku();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await Re())await yR(a.path)&&e.push(a);let n=Date.now(),r=li(n)>0,o=await Promise.all(e.map(async a=>{let i=fs.get(a.path),c=i?.pending?SR:xR;if(i&&(r||n-i.at<c))return i.entry;try{let l=await ot(a.path).catch(()=>null),u=kR(a.path,await ps(a.path,{caller:"tab"}),n);if(await vR(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(w=>w.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&gi({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return fs.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:Ae(l)}}})),s=new Set(e.map(a=>a.path));for(let a of fs.keys())s.has(a)||fs.delete(a);return{auth:t,repos:o}}async function Ve(t){let e=(await Re()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function sr(t){return bo(t)}var ms=new Set(["squash","merge","rebase"]);async function Yr(t,e,{method:n="squash"}={}){let r=await Ve(t);if(!ms.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 de({cwd:r.path})).GH_TOKEN){let a=await uc().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await ne(r.path,s)}catch(a){throw new Error(Ae(a))}finally{_e()}return{ok:!0,number:o}}async function hs(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ne(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(Ae(s))}finally{_e()}let o=await ot(n.path).catch(()=>null);return o&&await Wh(o,r).catch(()=>{}),{ok:!0,number:r}}async function zu(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ne(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(Ae(o))}finally{_e()}return{ok:!0,number:r}}var TR=2500,AR=9e4,_R=t=>new Promise(e=>setTimeout(e,t));function Ju(t){return t.green&&!t.isDraft}function ER(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Iy(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Ju(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Ry(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var qu=new Map;function $y(){let t={};for(let[e,n]of qu)t[e]={...n};return t}async function Oy(t,{method:e="merge",onMerged:n=null}={}){let r=await Ve(t);if(!ms.has(e))throw new Error(`invalid merge method: ${e}`);if(qu.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await ps(r.path)).filter(Ju).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 qu.set(r.path,s),RR(s,o,{listPrs:()=>ps(r.path),merge:a=>Yr(r.path,a,{method:e}),sleep:_R,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function IR(t,e){let n=Date.now()+(e.settleMaxMs??AR),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(TR)}}async function RR(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 IR(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: ${Ae(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(!Ju(a)){t.skipped.push({number:a.number,title:a.title,reason:ER(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:Ae(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),_e()}}async function Xu(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Jr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${Ae(l)}`)}await wR(t);let o=n||"";if((await de({cwd:t})).GH_TOKEN){let l=await uc().catch(()=>null);l&&(o=`${o?`${o}
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
`;import{execFile as G_}from"node:child_process";import{promisify as V_}from"node:util";var Tu=V_(G_),sg=64*1024*1024;async function gt(t,e,{input:n}={}){let r=Tu("git",["-C",t,...e],{maxBuffer:sg});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function ri(t){if(!t)return!1;try{let{stdout:e}=await gt(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function ag(t){let{stdout:e}=await gt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function z_(t){try{return await gt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function ls(t,e="bostrat: subtask snapshot"){let{stdout:n}=await gt(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>Tu("git",["-C",t,...a],{maxBuffer:sg,env:o,...i});try{let a=await z_(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 Tu("rm",["-f",r])}catch{}}}async function Au(t,e){let{stdout:n}=await gt(t,["status","--porcelain"]);return n.trim()?(await gt(t,["add","-A"]),await gt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function Eu(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await gt(t,["status","--porcelain"]),r=n.split(`
|
|
139
|
+
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function _u(t,e){if(!e)return 0;try{let{stdout:n}=await gt(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function Vr(t,e,n,r){return await gt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function ig(t,e){try{return await gt(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function cg(t,e,n){return await gt(t,["worktree","add",e,n]),e}async function oi(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await gt(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 gt(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function lg(t){await gt(t,["init","-q"]),await gt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function te(t,e){try{await gt(t,["worktree","remove","--force",e])}catch{try{await gt(t,["worktree","prune"])}catch{}}}function J_(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 Iu(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Ru(t,e,n,r){let{stdout:o}=await gt(t,Iu(["diff","--shortstat",e,n],r));return J_(o)}async function $u(t,e,n,r){let{stdout:o}=await gt(t,Iu(["diff","--name-only",e,n],r));return o.split(`
|
|
140
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Ou(t,e,n,r){let{stdout:o}=await gt(t,Iu(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await vu(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await vu(t,o,["--3way","--whitespace=nowarn"]),a=await X_(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await vu(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function vu(t,e,n){try{return await gt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function X_(t){try{let{stdout:e}=await gt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
141
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import Cu from"node:fs/promises";import dg from"node:path";import Y_ from"node:os";var fg=process.env.BOSTRAT_STATE_DIR||dg.join(process.env.HOME||Y_.tmpdir(),".bostrat"),pg=()=>dg.join(fg,"usage-caps.json"),mg=.8;function hg(t){let e=new Date(t);return e.setHours(0,0,0,0),e.setDate(e.getDate()-(e.getDay()+6)%7),e.getTime()}function wg(t){let e=new Date(t);return e.setHours(0,0,0,0),e.setDate(1),e.getTime()}function gg(t){let e=new Date(hg(t));return e.setDate(e.getDate()+7),e.getTime()}function yg(t){let e=new Date(wg(t));return e.setMonth(e.getMonth()+1),e.getTime()}var zr={weekly:{startOf:hg,nextStart:gg,capKey:"weeklyUsd"},monthly:{startOf:wg,nextStart:yg,capKey:"monthlyUsd"}},ae=null,Nu=!1,qe={weekly:0,monthly:0},sr=new Map,Jr=new Set,Mu=null,Z_=t=>{let e=sr.get(t);return e||(e={weekly:0,monthly:0},sr.set(t,e)),e};function ai(t=Date.now()){for(let[e,n]of Object.entries(zr)){let r=n.startOf(t);if(r!==qe[e]){qe[e]=r;for(let o of sr.values())o[e]=0;for(let o of Jr)o.includes(`:${e}:`)&&!o.endsWith(`:${r}`)&&Jr.delete(o)}}}function bg(t,e){let n=ae?.caps?.[t]?.[zr[e].capKey];return Number.isFinite(n)&&n>0?n:null}function us(t,e=Date.now()){if(!t||!Nu||!ae?.caps)return null;ai(e);let n=sr.get(t);for(let r of Object.keys(zr)){let o=bg(t,r);if(o==null)continue;let s=n?.[r]||0;if(s>=o)return{provider:t,window:r,limitUsd:o,spentUsd:si(s),resetsAt:zr[r].nextStart(e)}}return null}function kg(t,e){let n=`hit:${t}:${e.provider}:${e.window}:${qe[e.window]}`;return Jr.has(n)?!1:(Jr.add(n),!0)}function ug(t,e,n,r=Date.now()){if(!t||!e)return;ai(r);let o=Z_(t);for(let[s,a]of Object.entries(zr))n>=qe[s]&&n<a.nextStart(r)&&(o[s]+=e)}async function Q_(t,e,n=Date.now()){if(!ae?.caps||!Mu)return;let r=sr.get(t);if(r)for(let[o,s]of Object.entries(zr)){let a=bg(t,o);if(a==null)continue;let i=r[o]||0;if(i<a*mg||i>=a)continue;let c=`warn:${t}:${o}:${qe[o]}`;if(!Jr.has(c)){Jr.add(c);try{let l=await Mu(e);if(!l)continue;await R(l,{type:"usage.cap.warning",actor:"system",provider:t,payload:{provider:t,window:o,limitUsd:a,spentUsd:si(i),pct:Math.round(i/a*100),resetsAt:s.nextStart(n)}})}catch{}}}}function xg(t){let e={};for(let[n,r]of Object.entries(t||{})){if(typeof n!="string"||!/^[a-z][a-z0-9-]{0,31}$/.test(n))throw new Error(`bad provider key "${n}"`);let o={};for(let s of["weeklyUsd","monthlyUsd"]){let a=r?.[s];if(a==null||a==="")continue;let i=Number(a);if(!Number.isFinite(i)||i<=0)throw new Error(`${n}.${s} must be a positive number or null`);o[s]=i}Object.keys(o).length&&(e[n]=o)}return e}async function ii(t,{source:e="local",updatedAt:n=Date.now()}={}){ae={caps:xg(t),updatedAt:n,source:e};try{await Cu.mkdir(fg,{recursive:!0}),await Cu.writeFile(pg(),JSON.stringify(ae,null,2))}catch{}return ds()}function ds(t=Date.now()){ai(t);let e=new Set([...sr.keys(),...Object.keys(ae?.caps||{})]),n={};for(let r of e){let o=sr.get(r);n[r]={weeklyUsd:si(o?.weekly||0),monthlyUsd:si(o?.monthly||0)}}return{caps:ae?.caps||{},configured:!!ae,seeded:Nu,updatedAt:ae?.updatedAt??null,spent:n,windows:{weekly:{start:qe.weekly,resetsAt:gg(t)},monthly:{start:qe.monthly,resetsAt:yg(t)}},warnPct:mg}}async function Sg({listTracks:t,getTrack:e}={}){Mu=e||null,ai();try{let o=JSON.parse(await Cu.readFile(pg(),"utf8"));ae={caps:xg(o?.caps),updatedAt:o?.updatedAt??null,source:o?.source??"file"}}catch{ae=null}let n=Date.now();Ft.on("event",o=>{if(o?.type!=="cost.recorded")return;let{usd:s}=Me(o);ug(o.provider||"unknown",s,o.ts??Date.now()),Q_(o.provider||"unknown",o.trackId).catch(()=>{})});let r=Math.min(qe.weekly,qe.monthly);for(let o of await t?.()||[]){let s;try{s=await L(o)}catch{continue}for(let a of s){if(a.type!=="cost.recorded")continue;let i=a.ts??0;i<r||i>=n||ug(a.provider||"unknown",Me(a).usd,i,n)}}return Nu=!0,ds()}var si=t=>Math.round(t*1e4)/1e4;import tI from"node:fs/promises";import eI from"node:os";import vg from"node:path";var ci=t=>new Promise(e=>setTimeout(e,t)),nI=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,rI=300,_g=[{alias:"default",label:"Default"},{alias:"opus",label:"Opus 5"},{alias:"sonnet",label:"Sonnet 5"},{alias:"haiku",label:"Haiku 4.5"},{alias:"fable",label:"Fable 5"},{alias:"opusplan",label:"Opus Plan"}],oI=new Set([..._g.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-5","claude-opus-4-8","claude-sonnet-5","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),sI=[{alias:"gpt-5.6-sol",label:"GPT-5.6 Sol"},{alias:"gpt-5.6-terra",label:"GPT-5.6 Terra"},{alias:"gpt-5.6-luna",label:"GPT-5.6 Luna"},{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.3-codex",label:"Codex 5.3"}],Ig=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Rg=[{alias:"auto",label:"Auto"},{alias:"composer-2.5",label:"Composer 2.5"},{alias:"claude-opus-5",label:"Opus 5"},{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.6-sol",label:"GPT-5.6 Sol"},{alias:"gpt-5.3-codex",label:"Codex 5.3"}];function $g(t){return t==="codex"?sI:t==="cursor"?Rg:_g}function Tg(t){let e=String(t||"").split(`
|
|
142
|
+
`),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 Ag(t){let e=String(t||"").split(`
|
|
143
|
+
`),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 aI(t){let e=String(t||"").split(`
|
|
144
|
+
`);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 Eg(t){let e=String(t||"").split(`
|
|
145
|
+
`),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 Og(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&&!Ig.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Rg.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!oI.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function fs(t,e,{timeoutMs:n=nI}={}){let r=Date.now()+n;for(;;){let o=await mt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await ci(rI)}}async function iI(t,e){return await Et(t,`/model ${e.model}`,Ce("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function cI(t,e){await ma(t),await Et(t,"/model",{enter:!1}),await ci(250),await vt(t,["Enter"],{kind:"model-switch"});let n=await fs(t,Tg);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await vt(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=aI(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await Et(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await fs(t,i=>Ag(i)||!Tg(i)&&o(i)&&{done:o(i)});if(!s)throw await vt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Ig.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 vt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await Et(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await fs(t,i=>!Ag(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 lI(){let t=vg.join(process.env.CURSOR_CONFIG_DIR||vg.join(eI.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await tI.readFile(t,"utf8"))?.model||null}catch{return null}}async function uI(t,e){await ma(t),await wa(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await fs(t,Eg),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 fs(t,Eg,{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 vt(t,["Down"],{kind:"model-switch"}),await ci(250)}if(!n||!s||!o(s)){await vt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await vt(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 vt(t,["Enter"],{kind:"model-switch"}),await ci(800);let a=await lI();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 Cg(t,e,n){return t==="codex"?cI(e,n):t==="cursor"?uI(e,n):iI(e,n)}var Xt=t=>t||null;function ui(t){let e=t||{},n=dI(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function dI(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Xt(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:Xt(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:Xt(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:Xt(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:Xt(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:Xt(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:Xt(t.agent),payload:{...Xr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Xt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...Xr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Xt(t.agent),payload:{change:"subtask-resumed",...Xr(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",...Xr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Xr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Xt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Xr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Xt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var li=Promise.resolve(),Du=t=>(li=li.then(t).catch(()=>{}),li),Ng=()=>li;function Pg(t,e){let n=ui(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Du(()=>R(r,n))}var Mg=new Set;async function Dg(t){if(!(!t?.id||Mg.has(t.id))){Mg.add(t.id);try{let e=await L(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=ui(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function Xr(t){let{type:e,at:n,...r}=t;return r}var Pu=new Map;function Bg(t,e,n){!n?.promptId||Pu.get(t.id)===n.promptId||(Pu.set(t.id,n.promptId),Du(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Xt(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 Bu(t,e,n,r={}){Pu.delete(t.id),Du(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Xt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as WI}from"node:crypto";var Lg=/(?<!\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,Fg=/(?<!\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,Ug=/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,Hg=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,fI=new RegExp(`\\b(?:${Lg}|${Fg}|${Ug}|${Hg})\\b`,"i"),pI=/\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,mI=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,hI=90;function wI(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Wg(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of wI(n).reverse()){if(!fI.test(r))continue;let o=r.endsWith("?")&&r.length<=hI&&mI.test(r);if(pI.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var qg=["commit","rebase","push","pr"],gI=new RegExp(`\\b(?:${Lg})\\b`,"i"),yI=new RegExp(`\\b(?:${Hg})\\b`,"i"),bI=new RegExp(`\\b(?:${Fg})\\b`,"i"),kI=new RegExp(`\\b(?:${Ug})\\b`,"i"),xI=/\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 Kg(t){let e=String(t||""),n=[];gI.test(e)&&n.push("commit"),yI.test(e)&&n.push("rebase"),bI.test(e)&&n.push("push");let r=xI.test(e);!r&&kI.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 di(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var SI=3e4;function Gg(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<=SI&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Vg(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var jg={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function zg(t){let e=(Array.isArray(t)?t:[]).filter(r=>jg[r]).map(r=>jg[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as _I}from"node:child_process";import{promisify as II}from"node:util";import vI from"node:fs";import ju from"node:fs/promises";import Zg from"node:path";import TI from"node:os";var Qg=process.env.BOSTRAT_STATE_DIR||Zg.join(process.env.HOME||TI.tmpdir(),".bostrat"),fi=Zg.join(Qg,"github-metrics.json"),ty=60*6e4,Jg=5*6e4,AI=6e4,Yr=Date.now(),Ke=new Map,xn=null,ar=0,Xg=0,pi=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(vI.readFileSync(fi,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<ty?(Yr=t.windowStart,Ke=new Map(Object.entries(t.byCaller||{})),xn=t.lastHour||null):pi(new Map(Object.entries(t.byCaller||{})))&&(xn={start:t.windowStart,total:pi(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var Yg=Promise.resolve();function ey(t,e=!1){if(!e&&t-Xg<AI)return;Xg=t;let n=JSON.stringify({windowStart:Yr,byCaller:Object.fromEntries(Ke),lastHour:xn});Yg=Yg.then(()=>ju.mkdir(Qg,{recursive:!0})).then(()=>ju.writeFile(`${fi}.tmp`,n)).then(()=>ju.rename(`${fi}.tmp`,fi)).catch(()=>{})}function ny(t){t-Yr<ty||(xn={start:Yr,total:pi(Ke),byCaller:Object.fromEntries(Ke)},xn.total&&console.log(`[github] ${xn.total} calls last hour`,xn.byCaller),Yr=t,Ke=new Map,ey(t,!0))}function Ge(t="other",e=Date.now()){ny(e),Ke.set(t,(Ke.get(t)||0)+1),ey(e)}function ry(t=Date.now()){return ny(t),{windowStart:Yr,total:pi(Ke),byCaller:Object.fromEntries(Ke),lastHour:xn,backoffUntil:ar>t?ar:null}}var EI=/rate limit|too quickly|abuse detection|HTTP 429/i;function Sn(t){return EI.test(String(t?.message||t||""))}function vn(t,e=Date.now()){return ar<=e&&console.warn(`[github] rate limited \u2014 backing off ${Jg/6e4} min:`,String(t?.message||t||"").slice(0,200)),ar=Math.max(ar,e+Jg),ar}function mi(t=Date.now()){return Math.max(0,ar-t)}var oy=()=>Number(process.env.BOSTRAT_NARROW_CARRY_MS)||9e4,ps=new Map;function hi(t,e,n){let{state:r,reason:o}=Yc(e,n);if(r!=="unknown"){if(t){let a=Date.now();for(let[i,c]of ps)a-c.at>=oy()&&ps.delete(i);ps.set(t,{busy:r==="busy",at:a})}return r==="busy"}if(o!=="narrow"||!t)return!1;let s=ps.get(t);return s?Date.now()-s.at>=oy()?(ps.delete(t),!1):s.busy:!1}async function Lu(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await mt(t,{lines:At}).catch(()=>"");if(!hi(t,e,r))return Up(e,r)?{working:!0,prompted:!1}:n;if(Kr(e,r)){let o=await cn(t).catch(()=>r);if(or({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function wi(t,e){return(await Lu(t,e)).working}var sy=II(_I),ay=[4e3,15e3,45e3],RI=2500,$I=5*6e4;async function Fu(t,e,{midTurn:n=wi,pollMs:r=RI,capMs:o=$I}={}){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 OI=6e4,CI=9e4,MI=2e3,NI=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,PI=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,DI=/\s--web\b/,iy=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Uu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function cy(t){let e=String(t||""),n=e.match(NI);if(!n)return null;let r=Uu(e,n.index);return DI.test(r)?null:{repo:r.match(iy)?.[1]??null}}var BI=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function ly(t){let e=String(t||""),n=e.match(PI);if(!n)return null;let r=Uu(e,n.index),o=Uu(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("-")){BI.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(iy)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var jI="number,url,title,headRefName,createdAt";async function LI(t,e){let n=async a=>{Ge("observer");let{stdout:i}=await sy("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",jI],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw Sn(c)&&vn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-OI),o=await de({slug:t}),s=r(await n(o));return!s.length&&o.GH_TOKEN&&(s=r(await n({}).catch(()=>[]))),s.sort((a,i)=>Date.parse(i.createdAt)-Date.parse(a.createdAt))}async function FI(t,e){if(e)return[e];let n=[];for(let r of uy(t)){let o=await et(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await Ie().catch(()=>[])){let o=await et(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function uy(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function UI(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await FI(t,e)){let a;try{a=await LI(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 Tn=new Set;function dy(t,e,{delays:n=ay,turnEnd:r}={}){let o=cy(e.command);if(!o)return null;let s=!!ly(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Tn.has(a))return null;Tn.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await UI(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await Jt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Zo(f,d.number))?null:(await Fu(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:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{Tn.delete(a)}})();return c.catch(()=>Tn.delete(a)),c}var HI="number,url,title,headRefName,state";function fy(t,e,{delays:n=ay,turnEnd:r,openedWait:o}={}){let s=ly(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Tn.has(a))return null;Tn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let m=uy(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of m){let y=await et(w).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(m,w)=>{let y=await Pt(c,m.number);return y&&y.trackId!==t.id?null:(y||await Jt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Qo(c,m.number),w&&((await L(t).catch(()=>[])).some(_=>_.type==="pr.state.changed"&&(_.payload?.change==="auto-armed"||_.payload?.change==="merged")&&_.payload?.number===m.number)||(await Fu(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:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise(O=>setTimeout(O,m));let w=[];for(let O of f)try{Ge("observer");let{stdout:P}=await sy("gh",["pr","view",String(l),"--repo",O,"--json",HI],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await de({slug:O})}}),v=JSON.parse(P||"null");v?.number&&w.push({slug:O,pr:v})}catch(P){Sn(P)&&vn(P)}let y=w.find(O=>O.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!y)continue;c=y.slug;let k=y.pr;if(!k?.number)continue;if(k.state==="CLOSED")return null;if(k.state!=="MERGED"){if(p=k,s.auto)return d(k,!0);continue}let S=await Pt(c,k.number);if(S&&S.trackId!==t.id)return null;S||await Jt({repo:c,number:k.number,trackId:t.id,url:k.url,headRef:k.headRefName,title:k.title,source:"merge-observed"}),await Fu(e.box,e.agent,r);let _=await L(t).catch(()=>[]);if(_.some(O=>O.type==="pr.state.changed"&&O.payload?.change==="merged"&&O.payload?.number===k.number))return null;let $=O=>O.some(P=>P.type==="pr.state.changed"&&P.payload?.change==="opened"&&P.payload?.number===k.number),z=0;if(!$(_)){if(cy(e.command)&&await Zo(c,k.number)){let O=Date.now();z=O+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:O,change:"opened",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c,selfMerge:!0}})}else if((await Pt(c,k.number))?.announced){let O=o?.capMs??CI,P=o?.pollMs??MI,v=Date.now()+O;for(;Date.now()<v&&(await new Promise(M=>setTimeout(M,P)),!$(await L(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(),z),change:"merged",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c}}),await En(c,k.number).catch(()=>{}),await An(c,k.number).catch(()=>{}),await yn(c,k.number,"merged"),k}return p?d(p,!1):null}finally{Tn.delete(a)}})();return i.catch(()=>Tn.delete(a)),i}function Wu(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function py(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function my(t){return`job|${t.box}|id:${t.id}`}var Hu=new Map,gi=new Map;async function qu(t){return Hu.has(t.id)?Hu.get(t.id):(gi.has(t.id)||gi.set(t.id,L(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(Wu({role:o,sourceId:r.payload.sourceId})),n.keys.add(Wu({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(py({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(my({id:r.payload.id,box:r.boxId}));return Hu.set(t.id,n),n}).finally(()=>gi.delete(t.id))),gi.get(t.id))}async function hy(t,e){try{let n=(await qu(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Wu(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=Wg(r.text);if(s){let{offered:a,toPr:i}=Kg(s),c;if(t.workspace?.kind==="multi"){let l=await ms(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:WI(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function wy(t,e){try{let n=await qu(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=py(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}}),dy(t,o),fy(t,o))}}catch{}}async function gy(t,e){try{let n=await qu(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=my(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 qI=/^This session is being continued from a previous conversation that ran out of context\./,by=["Primary Request and Intent","Key Technical Concepts","Files and Code Sections","Errors and Fixes","Problem Solving","All user messages","Pending Tasks","Current Work","Optional Next Step"],Ku=new RegExp(String.raw`^[ \t]*#{0,4}[ \t]*(?:\d+[.)][ \t]*)?\*{0,2}(${by.join("|")})[:*\t ]*$`,"gim"),KI=/\n+If you need specific details from before compaction[\s\S]*$|\n+Continue the conversation from where it left off[\s\S]*$/,GI=420,VI=[{h:"What we were doing",from:["Primary Request and Intent"]},{h:"Key decisions",from:["Key Technical Concepts"]},{h:"Where it got to",from:["Current Work","Problem Solving"]},{h:"Left to do",from:["Pending Tasks","Optional Next Step"]}],zI=116,Gu=168;function JI(t){return qI.test(String(t||"").trimStart())}function ky(t){let e=String(t||"");if(!JI(e.trimStart()))return null;let n=e.replace(KI,"").trim(),r=XI(n),o=[];for(let u of VI){let f=u.from.map(d=>Zr(r.get(d))).find(d=>d&&!yy(d));f&&o.push({h:u.h,text:hs(f,GI)})}let s=Zr(r.get("Current Work"))||Zr(r.get("Primary Request and Intent"))||ZI(n),a=bi(s),i=hs(yi(Vu(a[0]||"")),zI)||"Context resumed from summary",c=[],l=a.slice(1).join(" ").trim();l&&c.push(hs(yi(Vu(l)),Gu));for(let u of["Pending Tasks","Optional Next Step"]){let f=bi(Zr(r.get(u)))[0]||"";if(!(!f||yy(f))){c.push(hs(`Left to do: ${yi(Vu(f))}`,Gu));break}}if(!c.length){let u=bi(Zr(r.get("Problem Solving"))||"")[0];u&&c.push(hs(yi(u),Gu))}return{kind:"resume-summary",title:i,bullets:c.slice(0,2),sections:o,rawChars:e.length}}function XI(t){let e=new Map,n=[];Ku.lastIndex=0;for(let r=Ku.exec(t);r;r=Ku.exec(t))n.push({name:YI(r[1]),start:r.index,end:r.index+r[0].length});for(let r=0;r<n.length;r++){let o=n[r+1]?.start??t.length;e.has(n[r].name)||e.set(n[r].name,t.slice(n[r].end,o).trim())}return e}function YI(t){return by.find(n=>n.toLowerCase()===t.toLowerCase())||t}function Zr(t){return t?String(t).replace(/```[\s\S]*?(?:```|$)/g," ").split(`
|
|
146
|
+
`).map(e=>e.replace(/^[ \t]*(?:[-*•]|\d+[.)]|[a-z][.)])[ \t]+/i,"").trim()).filter(e=>e&&!/^#{1,6}[ \t]/.test(e)).join(" ").replace(/\*\*(.+?)\*\*/g,"$1").replace(/`([^`]+)`/g,"$1").replace(/\s+/g," ").trim():""}function ZI(t){let e=t.replace(/^[\s\S]*?\bSummary:\s*/i,"");return Zr(e.split(/\n{2,}/).slice(0,2).join(" "))}function bi(t){let e=String(t||"").trim();if(!e)return[];let r=e.replace(/\b([A-Za-z0-9_-]+)\.(js|ts|tsx|mjs|json|md|css|jsonl|sh|yml|yaml|py|html)\b/g,"$1<DOT>$2").replace(/\b(e\.g|i\.e|etc|vs|approx|no|fig|Dr|Mr|Ms|St)\./gi,"$1<DOT>").split(/(?<=[.!?])["')\]]?\s+(?=[A-Z"“'(\[]|\d)/).map(s=>s.replace(/<DOT>/g,".").trim()).filter(Boolean),o=[];for(let s of r){let a=o[o.length-1];a&&(a.match(/["“”]/g)?.length??0)%2===1?o[o.length-1]=`${a} ${s}`:o.push(s)}return o}function Vu(t){return String(t||"").replace(/^(?:Immediately before (?:this|the) summary(?: request)?[,:]?\s*|At the (?:time|point) of (?:this )?compaction[,:]?\s*|Currently[,:]?\s*|The (?:most recent|immediately preceding|current) work (?:was|is)\s*)/i,"").replace(/^(?:I(?:'m|'d| am| was| had| have)?\s+(?:currently\s+|just\s+)?(?:been\s+)?(?:working on|in the middle of|completing|finishing)\s+|The user(?:'s)?\s+(?:most recent\s+)?(?:request|ask) (?:was|is)\s+(?:to\s+)?)/i,"").replace(/^I(?:'m|'d| am| was| had| have)?\s+(?:just\s+|then\s+)?(?=(?:[a-z]+(?:ed|ing)|ran|wrote|built|made|began|found|left|sent|took|went|did|got|kept|held|brought|caught|chose|came|saw|said|gave|told|set|put|read)\b)/,"").replace(/^["“]|["”]$/g,"").replace(/^(\w)/,e=>e.toUpperCase()).trim()}function yi(t){return String(t||"").replace(/(?<![:\w/])(?:\/[\w.@+-]+){4,}/g,e=>`\u2026/${e.split("/").slice(-2).join("/")}`)}function yy(t){return/^(none|nothing|n\/a|no pending|there are no)\b/i.test(String(t||"").trim())}function hs(t,e){let n=String(t||"").trim();if(n.length<=e)return n;let r=bi(n),o="";for(let i of r){let c=o?`${o} ${i}`:i;if(c.length>e)break;o=c}if(o.length>=Math.min(60,e*.4))return o;let s=n.slice(0,e),a=s.lastIndexOf(" ");return`${(a>e*.6?s.slice(0,a):s).replace(/[,;:.\s]+$/,"")}\u2026`}var QI=/^Base directory for this skill:\s*(\S+)/,tR=/^Run the "([^"]+)" workflow\./,eR=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,nR=/^<local-command-(?:stdout|caveat)>/i,rR=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,oR=/^Continue from where you left off\.$/,sR=/^<task-notification>/i,aR=/<summary>([\s\S]*?)<\/summary>/i,iR=/<task-id>([\s\S]*?)<\/task-id>/i,cR=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,lR=/<status>([\s\S]*?)<\/status>/i,uR=/<result>([\s\S]*?)(?:<\/result>|$)/i,xy=600,zu=new Set(["model","clear","compact","login","exit","quit"]);function Sy(t){return zu.has(String(t||"").replace(/^\//,"").toLowerCase())}var dR=/^\/([a-z0-9:_-]+)$/i;function ki(t){let e=String(t||"").trim();if(!e)return null;let n=ky(e);if(n)return n;let r=e.match(QI);if(r)return{kind:"skill",name:r[1].split("/").filter(Boolean).pop()||"skill"};let o=e.match(tR);if(o)return{kind:"skill",name:o[1]};let s=e.match(eR);if(s){let i=s[1].toLowerCase();return zu.has(i)?{kind:"suppress"}:{kind:"command",command:`/${i}`}}let a=e.match(dR);if(a&&zu.has(a[1].toLowerCase()))return{kind:"suppress"};if(nR.test(e))return{kind:"suppress"};if(oR.test(e))return{kind:"suppress"};if(rR.test(e))return{kind:"suppress"};if(sR.test(e)){let i=l=>fR(e.match(l)?.[1].trim()||"")||null,c=i(uR);return{kind:"task",summary:i(aR)||"",taskId:i(iR),toolUseId:i(cR),status:i(lR)?.toLowerCase()||null,resultHead:c?c.length>xy?`${c.slice(0,xy).trimEnd()}\u2026`:c:null}}return null}function fR(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function vy(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":t?.kind==="resume-summary"?t.title?`Context resumed from summary: ${t.title}`:"Context resumed from summary":""}function Ty(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":t?.kind==="resume-summary"?"context-resumed":"command-run"}var ot=t=>t.payload?.ts??t.ts;function pR(t){return{id:t.id,kind:"prompt",groupId:t.groupId,provider:t.provider,box:t.box,agent:t.provider,questionTotal:t.questions.length,answeredVia:"chat",questions:t.questions.map((e,n)=>({index:n,header:e.header||"",question:e.question,multiSelect:!!e.multiSelect,chosen:[]})),ts:t.ts}}var mR=new Set(["opened","ci-fixed","reconciled","pushed"]),Ay=t=>`${t.repo??""}#${t.number}`;function hR(t,e,n){let r=Ay(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function ws(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$/,f=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let h=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(h))for(let A of h.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let I=A[0].split("/").pop().toLowerCase();f.has(I)||f.set(I,[]),f.get(I).push(g.payload?.ts??g.ts)}}let d=3*6e4,p=20*6e4,m=(g,h)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(I=>I>=h-d&&I<=h+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let h=xt(g.payload?.text);h&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(h))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let h=xt(g.payload?.text);h&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(h))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let h=g.payload.note,A={status:h.status||null,summary:h.summary||null,resultHead:h.resultHead??null,ts:ot(g)};h.toolUseId&&i.set(h.toolUseId,A);let I=String(h.summary||"").match(u);I&&l.set(I[1],A)}let w=new Map,y=new Map,k=null,S=new Map,_=new Map,$=new Map,z=new Map,O=new Map,P=new Map,v=new Map,M=()=>{for(let g of v.values())g.questions.length&&e.push(g);v.clear()},D=null,it=6e4,ct=new Map,T=(g,h)=>{let A=o.get(g);if(!A?.length)return!1;let I=xt(h),J=s.get(g);return A.some(W=>{if(!I.startsWith(W))return!1;let at=xt(I.slice(W.length));return!!at&&!!J?.has(at)})};for(let g of t||[]){let h=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(h.text||"").trim()&&!h.images?.length)break;{let A=h.note||ki(h.text);if(A){if(A.kind==="task"){if(A.toolUseId&&a.has(A.toolUseId))break;let I=String(A.summary||"").match(u);if(!A.toolUseId&&I&&c.has(I[1]))break}if(A.kind==="command"&&Sy(A.command))break;if(A.kind==="resume-summary"){e.push({id:g.id,kind:"resume-summary",title:A.title||"Context resumed from summary",bullets:A.bullets||[],sections:A.sections||[],rawChars:A.rawChars??null,ts:ot(g),box:g.boxId,agent:g.provider});break}A.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:Ty(A),name:A.name??null,command:A.command??null,summary:A.summary??null,ts:ot(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${ot(g)}`)||g.source==="transcript"&&T(g.boxId,h.text))break;if(h.kind==="merge-notify"||h.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:h.kind==="ship-notify"?"ship-notified":"merge-notified",text:h.text,ts:ot(g),box:g.boxId,agent:g.provider});break}if(h.kind==="mock-picked"||h.kind==="ci-fix"||h.kind==="reconcile"||h.kind==="pr-go"||h.kind==="push-go"||h.kind==="spin-up-view"||h.kind==="prompt-answer"){h.kind==="prompt-answer"&&(D=null);break}for(let A of S.values())A.status="stale";S.clear(),D&&D.box===g.boxId&&(e.push(pR(D)),D=null),e.push({id:g.id,kind:"message",role:"user",text:h.text,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...g.actor==="orchestrator"?{orchestration:h.kind||!0}:{},...h.sourceId?{sourceId:h.sourceId}:{},...h.attachments?.length?{attachments:h.attachments}:{},...h.images?.length?{images:h.images}:{}});break}case"turn.agent":{if(h.model==="<synthetic>"&&/^No response requested\.$/.test(String(h.text||"").trim()))break;M();let A=(h.images||[]).filter(at=>!m(at,ot(g)));if(!String(h.text||"").trim()&&!A.length)break;if(g.provider==="cursor"){let at=`${g.boxId}|${h.text}`;if(n.has(at))break;n.add(at)}(!D||D.box===g.boxId)&&(D=null);let I=g.provider==="codex"?(h.tools||[]).filter(at=>!ru.has(at)&&!ss.has(at)):h.tools||[],J=h.text,W=null;if(bu.has(g.provider)){let at=ku(String(h.text||""));at&&(J=at.text,W={questions:at.questions})}e.push({id:g.id,kind:"message",role:"assistant",text:J,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...W?{ask:W}:{},...I.length?{tools:I}:{},...h.model?{model:h.model}:{},...h.sourceId?{sourceId:h.sourceId}:{},...A.length?{images:A}:{}}),W&&(D={id:g.id,groupId:xu(g.provider,h.sourceId||g.id,W.questions),provider:g.provider,box:g.boxId,questions:W.questions,ts:ot(g)});break}case"job.started":{if(!h.id)break;let A=ot(g),I=i.get(h.id);if(!I&&(h.kind||"agent")==="agent"&&h.name){let St=l.get(h.name);St&&St.ts>=A&&(I=St)}let J={jobId:h.id,jobKind:h.kind||"agent",name:h.name||null,detail:h.detail||null,status:I?I.status&&I.status!=="completed"?"failed":"done":"running",startedTs:A,finishedTs:I?.ts??null,summary:I?.summary??null,resultHead:I?.resultHead??null},W=ct.get(g.boxId),at=W?.jobs[W.jobs.length-1]?.startedTs;if(W&&at!=null&&A-at<=it)W.jobs.push(J);else{let St={id:g.id,kind:"job-dock",dockId:h.id,ts:A,box:g.boxId,agent:g.provider,jobs:[J]};ct.set(g.boxId,St),e.push(St)}break}case"handoff.done":e.push(ve(g,"lead-handoff"));break;case"subtask.delegated":e.push({...ve(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(ve(g,"subtask-merged"));break;case"pr.state.changed":{if(!h.change)break;if(h.change==="opened"){for(let I of S.values())I.status="stale";S.clear()}let A=ve(g,`pr-${h.change}`);if(mR.has(h.change)&&h.number!=null){let I=hR(w,y,h);if(w.size>=2){for(let W of y.values())for(let at of W)at.grouped=!0;A.groupPrs=[...w.values()];let J=[A];for(let W of w.keys())y.set(W,J)}else{let J=y.get(I)??[];J.push(A),y.set(I,J)}}else if(h.change==="merged"||h.change==="closed"||h.change==="lost"){let I=Ay(h),J=k&&!k.complete||w.size>=2;if(w.has(I))w.delete(I),y.delete(I);else if(h.number!=null)for(let W of[...w.keys()])W.endsWith(`#${h.number}`)&&(w.delete(W),y.delete(W));if(J&&h.number!=null){(!k||k.complete)&&(k={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:ot(g),ts:ot(g),box:g.boxId,agent:g.provider},e.push(k)),k.prs.push({number:h.number,url:h.url??null,title:h.title??null,repo:h.repo??null,branch:h.branch??null,change:h.change,ts:ot(g)}),k.remaining=[...w.values()],k.complete=w.size===0;break}}e.push(A);break}case"pr.offered":{if(!h.offerId)break;for(let I of S.values())I.status="stale";S.clear();let A={id:g.id,kind:"pr-offer",offerId:h.offerId,excerpt:h.excerpt||null,branch:h.branch||null,base:h.base||null,offered:Array.isArray(h.offered)&&h.offered.length?h.offered:null,toPr:h.toPr&&typeof h.toPr=="object"?{number:h.toPr.number??null}:null,repos:Array.isArray(h.repos)&&h.repos.length?h.repos:null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};S.set(h.offerId,A),_.set(h.offerId,A),e.push(A);break}case"pr.offer.answered":{let A=_.get(h.offerId);A&&(A.status=h.action||"dismiss",A.actions=Array.isArray(h.actions)?h.actions:null,A.notify=h.notify===!0,S.delete(h.offerId));break}case"box.state.changed":{let A=h.change==="model"?"model-change":h.change;if(!A)break;let I=ve(g,A);A==="model-change"&&(I.to=h.model),e.push(I);break}case"view.state.changed":{if(h.change!=="created"||h.restarted)break;e.push({id:g.id,kind:"view-card",viewId:h.viewId,name:h.name,slug:h.slug,port:h.port,origin:h.origin,ts:ot(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:h.name||null,reason:h.reason||"no-view",ts:ot(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(h.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:h.mockId,name:h.name,group:h.group??null,version:h.version??null,siblings:h.siblings??[],ts:ot(g),box:g.boxId,agent:g.provider});break}if(h.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:h.mockId,name:h.name,group:h.group??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"flow.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"flow-card",flowId:h.flowId,name:h.name,stepCount:h.stepCount??null,mockIds:h.mockIds??[],ts:ot(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:h.runbookId,name:h.name,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:h.provider||"linear",key:h.key,url:h.url,title:h.title??null,linkSource:h.linkSource??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!h.offerId)break;for(let I of $.values())I.key===h.key&&(I.status="stale",$.delete(I.offerId));let A={id:g.id,kind:"ticket-offer",offerId:h.offerId,provider:h.provider||"linear",key:h.key,url:h.url??null,phase:h.phase??null,from:h.from??null,to:h.to?.name??null,prNumber:h.prNumber??null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};$.set(h.offerId,A),z.set(h.offerId,A),e.push(A);break}case"ticket.move.answered":{let A=z.get(h.offerId);A&&(A.status=h.action==="moved"?"moved":"dismiss",$.delete(h.offerId));break}case"ticket.state.changed":{for(let A of $.values())A.key===h.key&&A.to===h.to&&(A.status="stale",$.delete(A.offerId));if(h.mode==="offer")break;e.push(ve(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:h.key,url:h.url??null,excerpt:h.excerpt??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(ve(g,"ticket-flow"));break;case"ticket.create.offered":{if(!h.proposalId)break;for(let I of O.values())I.status="stale";O.clear();let A={id:g.id,kind:"ticket-create-offer",proposalId:h.proposalId,provider:h.provider||"linear",epic:h.epic??null,issues:Array.isArray(h.issues)?h.issues:[],team:h.team??null,needsTeam:!!h.needsTeam,linkCurrentTrack:!!h.linkCurrentTrack,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};O.set(h.proposalId,A),P.set(h.proposalId,A),e.push(A);break}case"ticket.create.answered":{let A=P.get(h.proposalId);A&&(A.status=h.action==="created"?"created":"dismiss",A.createdCount=h.createdCount??null,A.failedCount=h.failedCount??null,O.delete(h.proposalId));break}case"ticket.created":{let A={items:Array.isArray(h.items)?h.items:[],failed:Array.isArray(h.failed)?h.failed:[],team:h.team??null},I=h.proposalId?P.get(h.proposalId):null;if(I){I.created=A;break}e.push({id:g.id,kind:"ticket-created",...A,ts:ot(g),box:g.boxId,agent:g.provider});break}case"error":{if(!h.kind)break;e.push(ve(g,h.kind));break}case"usage.cap.hit":e.push(ve(g,"usage-cap-hit"));break;case"usage.cap.warning":e.push(ve(g,"usage-cap-warning"));break;case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:h.provider||g.provider,ts:ot(g),at:ot(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(D=null,h.control||!h.question)break;let A=h.groupId||h.promptId;if(!A)break;let I=v.get(A);I||(I={id:g.id,kind:"prompt",groupId:A,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:h.questionTotal??1,questions:[],ts:ot(g)},v.set(A,I));let J=h.questionIndex??0,W={index:J,header:h.header||"",question:h.question,multiSelect:!!h.multiSelect,chosen:Array.isArray(h.chosen)?h.chosen:[]},at=I.questions.findIndex(St=>St.index===J&&xt(St.question)===xt(W.question));at>=0?I.questions[at]=W:I.questions.push(W),I.questions.sort((St,Vs)=>St.index-Vs.index),I.questionTotal=Math.max(I.questionTotal,h.questionTotal??1),I.questions.length>=I.questionTotal&&(e.push(I),v.delete(A));break}default:break}}return gR(e.sort((g,h)=>(g.ts??0)-(h.ts??0)))}var wR=2*6e4;function gR(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<=wR){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 Ju(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:yR(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??ws(t)).filter(o=>o.kind==="message");return kw(n,r)}function yR(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Ur(t.command)?e:bn(t.command,t.description||"")}function ve(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:ot(t),ts:ot(t)}}var bR=12e4,kR=new Set(["archived","unarchived","opened"]);function Ey(t){let e=[...t];for(let n=0;n<e.length;n++){let r=e[n];if(r?.kind!=="event"||r.type!=="archived")continue;let o=(r.ts??0)+bR,s=n;for(;s+1<e.length;){let a=e[s+1];if((a?.ts??0)>o||a?.kind==="event"&&kR.has(a.type))break;s++}s>n&&e.splice(s,0,...e.splice(n,1)),n=s}return e}function xR(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
147
|
+
`)){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 Xu=new Map;async function SR(t,e){let n=`${t.id}:${e.name}`;if(!Xu.has(n)){let r=await L(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);Xu.set(n,o)}return Xu.get(n)}async function _y(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Qn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await SR(t,e),o=xR(await Lt(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:dn(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 xi from"node:fs";import Zu from"node:path";import vR from"node:os";var TR=process.env.BOSTRAT_STATE_DIR||Zu.join(process.env.HOME||vR.tmpdir(),".bostrat"),Ry=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,AR="https://cursor.com/api/dashboard/get-filtered-usage-events",Iy=100;async function ER({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(AR,{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:Iy})});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<Iy)break}return r}function _R(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 IR=5*6e4,_n={at:0,rows:null,error:null},Si=()=>Zu.join(TR,"cursor-usage.json");function $y(){try{return JSON.parse(xi.readFileSync(Si(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function RR(t){try{xi.mkdirSync(Zu.dirname(Si()),{recursive:!0});let e=Si()+".tmp";xi.writeFileSync(e,JSON.stringify(t)),xi.renameSync(e,Si())}catch{}}var $R=5e3;async function OR({fetchImpl:t,now:e=Date.now()}={}){if(_n.rows&&e-_n.at<IR)return _n.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=$y(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await ER({sinceMs:o,cookie:n,fetchImpl:t});_n={at:e,rows:s.map(_R).filter(Boolean),error:null}}catch(s){_n={at:e,rows:null,error:String(s?.message||s)}}return _n.rows}var Qu=()=>({enabled:Ry(),lastPollAt:_n.at||null,error:_n.error}),Yu=new Map;async function CR(t,e){let n=`${t.id}:${e.name}`;if(!Yu.has(n)){let r=await L(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);Yu.set(n,o)}return Yu.get(n)}async function Oy(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Ry())return;let r=await OR({fetchImpl:n});if(!r||!r.length)return;let o=await Rw(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await CR(t,e),a=$y(),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",f=l.tokens||null,d=l.vendorCents!=null?l.vendorCents/100:0;!d&&f&&(d=dn(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await R(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:d,model:u,...f?{tokens:f}:{},...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}})}RR({sinceMs:c,seen:[...i].slice(-$R)})}catch{}}import{execFile as BR}from"node:child_process";import{promisify as jR}from"node:util";import Cy from"node:fs/promises";import My from"node:path";import MR from"node:os";var Ny=process.env.BOSTRAT_STATE_DIR||My.join(process.env.HOME||MR.tmpdir(),".bostrat"),NR=My.join(Ny,"github-latency.ndjson"),PR=100,vi=[],td=new Map;function Ti({change:t,repo:e=null,number:n=null,githubAt:r=null,observedAt:o=Date.now(),lane:s=null,intervalMs:a=null,source:i=null}){let c=r?o-r:null,l={change:t,repo:e,number:n,githubAt:r,observedAt:o,latencyMs:c,lane:s,intervalMs:a,source:i};vi.push(l),vi.length>PR&&vi.shift();let u=td.get(t)||{count:0,measured:0,totalMs:0,maxMs:0};return u.count+=1,c!=null&&(u.measured+=1,u.totalMs+=c,u.maxMs=Math.max(u.maxMs,c)),td.set(t,u),DR(l),l}async function DR(t){try{await Cy.mkdir(Ny,{recursive:!0}),await Cy.appendFile(NR,JSON.stringify(t)+`
|
|
148
|
+
`)}catch{}}function Py(){let t={};for(let[e,n]of td)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:vi.slice(-20)}}async function LR(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([to("git",["-C",t,"rev-parse","HEAD"]),to("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await R({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var to=jR(BR),FR={timeout:3e4,maxBuffer:8*1024*1024};async function ee(t,e,n={}){let{caller:r="prs",...o}=n,s=await de({cwd:t});Ge(r);try{let{stdout:a}=await to("gh",e,{cwd:t,...FR,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Sn(a)&&vn(a),a}}var Ai=null;async function rd(){if(Ai&&Date.now()-Ai.at<6e4)return Ai.data;let t;try{Ge("auth");let e=await to("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"||e?.code===127?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Eo();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ai={at:Date.now(),data:t},t}async function UR(t){try{let{stdout:e}=await to("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function By(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function ed(t){let e=(t||[]).map(By),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var od="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",jy=od.replace("statusCheckRollup,","");function sd(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Ly(t,e){if(!e)return null;let n=await et(t);if(!n)return null;try{let r=await ee(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 Fy(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function Uy(t,e){let n=await Ly(t,e);return n===null?{...ed([]),unavailable:!0}:ed(Fy(n))}function Ei(t,e=ed(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 HR=9e4,Qr=new Map;function WR(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Qr.delete(s);continue}r.add(s),Qr.has(s)||Qr.set(s,n),o.mergeabilityPending=n-Qr.get(s)<HR}for(let o of Qr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Qr.delete(o);return e}async function eo(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await ee(n.path,["pr","view",String(r),"--json",od]);return Ei(JSON.parse(o))}catch(o){if(!sd(o))throw o;let s=await ee(n.path,["pr","view",String(r),"--json",jy]),a=JSON.parse(s);return Ei(a,await Uy(n.path,a.headRefOid))}}var Dy=(t,e)=>t.number-e.number;async function ys(t,{caller:e="prs"}={}){try{let n=await ee(t,["pr","list","--json",od,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ei(r)).sort(Dy)}catch(n){if(!sd(n))throw n;let r=await ee(t,["pr","list","--json",jy,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ei(o,await Uy(t,o.headRefOid))))).sort(Dy)}}var qR=3e4,KR=1500,gs=new Map;function Ae(){gs.clear()}async function GR(t,e){if(!t)return e;for(let n of e){let r=await Pt(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await Ja(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ya(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Hy(){let t=await rd();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await Ie())await UR(a.path)&&e.push(a);let n=Date.now(),r=mi(n)>0,o=await Promise.all(e.map(async a=>{let i=gs.get(a.path),c=i?.pending?KR:qR;if(i&&(r||n-i.at<c))return i.entry;try{let l=await et(a.path).catch(()=>null),u=WR(a.path,await ys(a.path,{caller:"tab"}),n);if(await GR(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(w=>w.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&Ti({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return gs.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:Te(l)}}})),s=new Set(e.map(a=>a.path));for(let a of gs.keys())s.has(a)||gs.delete(a);return{auth:t,repos:o}}async function Ve(t){let e=(await Ie()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function ir(t){return To(t)}var bs=new Set(["squash","merge","rebase"]);async function no(t,e,{method:n="squash"}={}){let r=await Ve(t);if(!bs.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 de({cwd:r.path})).GH_TOKEN){let a=await bc().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await ee(r.path,s)}catch(a){throw new Error(Te(a))}finally{Ae()}return{ok:!0,number:o}}async function ks(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ee(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(Te(s))}finally{Ae()}let o=await et(n.path).catch(()=>null);return o&&await tw(o,r).catch(()=>{}),{ok:!0,number:r}}async function ad(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ee(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(Te(o))}finally{Ae()}return{ok:!0,number:r}}var VR=2500,zR=9e4,JR=t=>new Promise(e=>setTimeout(e,t));function id(t){return t.green&&!t.isDraft}function XR(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Wy(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:id(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function qy(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var nd=new Map;function Ky(){let t={};for(let[e,n]of nd)t[e]={...n};return t}async function Gy(t,{method:e="merge",onMerged:n=null}={}){let r=await Ve(t);if(!bs.has(e))throw new Error(`invalid merge method: ${e}`);if(nd.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await ys(r.path)).filter(id).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 nd.set(r.path,s),ZR(s,o,{listPrs:()=>ys(r.path),merge:a=>no(r.path,a,{method:e}),sleep:JR,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function YR(t,e){let n=Date.now()+(e.settleMaxMs??zR),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(VR)}}async function ZR(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 YR(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: ${Te(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(!id(a)){t.skipped.push({number:a.number,title:a.title,reason:XR(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:Te(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),Ae()}}async function cd(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await to("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${Te(l)}`)}await LR(t);let o=n||"";if((await de({cwd:t})).GH_TOKEN){let l=await bc().catch(()=>null);l&&(o=`${o?`${o}
|
|
149
|
+
|
|
150
|
+
`:""}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 ee(t,s)}catch(l){throw new Error(Te(l))}finally{Ae()}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 Vy(t,e){let n=await Ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await ee(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(Te(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!sd(i))throw i;let c=await ee(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Ly(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(Te(i));o=Fy(l).map(By).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=QR(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await ee(n.path,["run","view",c,"--log-failed"]).then(u=>t$(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function QR(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function t$(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
150
151
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
151
152
|
`+o.slice(-e).join(`
|
|
152
153
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
153
|
-
`+r.slice(-n)),r}function
|
|
154
|
-
`).find(n=>n.trim())||"gh failed"}var MR=6e3,ki=new Map;function Yu(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 Cy(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(Yu(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await di(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(ki.get(t.id)||0)<MR)return;ki.set(t.id,o);let s;try{s=await ps(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await ot(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Xt({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 zo(i,a.number))){ki.delete(t.id);return}}await I(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,...i?{repo:i}:{}}}).catch(()=>{}),ki.delete(t.id)}import CR from"node:fs/promises";import jy from"node:path";import NR from"node:os";import{randomUUID as Ly}from"node:crypto";var PR=process.env.BOSTRAT_STATE_DIR||jy.join(process.env.HOME||NR.tmpdir(),".bostrat"),Fy=jy.join(PR,"flows.json"),Ny=20,Py=80,Dy=500,By=80,ze=null,Zu=null;async function Je(){return ze||(Zu||=(async()=>{let t=!0;try{ze=JSON.parse(await CR.readFile(Fy,"utf8"))}catch{ze={flows:[]},t=!1}finally{Zu=null}if(ze.flows||=[],!t&&wt()){let{records:e,source:n}=await Ot("flows").catch(()=>({records:[],source:"error"}));if(n==="plane"){for(let r of e)!r.deletedAt&&r.data?.id&&ze.flows.push(r.data);ze.flows.length&&await Zr().catch(()=>{})}}return ze})(),Zu)}async function Zr(){await Lt(Fy,ze)}var xi=t=>Dt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function Qu(t,e,n={}){return I({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function Uy({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Ny)throw new Error(`steps must be an array of 1-${Ny}`);let r=await Hn({trackId:t}),o=[...n],s=[];for(let i=0;i<e.length;i++){let c=e[i]||{},l=`step ${i+1}`,u=typeof c.caption=="string"?c.caption.trim():"";if(!u)throw new Error(`${l}: caption (string) required`);if(u.length>Dy)throw new Error(`${l}: caption over ${Dy} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>By)throw new Error(`${l}: title over ${By} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let p=r.filter(S=>S.id===d);if(p.length||(p=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!p.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(p.length>1)throw new Error(`${l}: "${d}" is ambiguous (${p.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let m=p[0],w=null;if(c.v!=null&&(w=Number(c.v),!Number.isInteger(w)||!(m.versions||[]).some(S=>S.v===w)))throw new Error(`${l}: "${m.name}" has no captured v${c.v} to pin`);let y=o.findIndex(S=>S.mockId===m.id),b=y>=0?o.splice(y,1)[0].id:Ly().slice(0,8);s.push({id:b,mockId:m.id,caption:u,title:f,v:w})}let a={};for(let i of s)i.mockId in a||(a[i.mockId]=r.find(c=>c.id===i.mockId)?.readyVersion??null);return{steps:s,resolved:a}}function DR(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Py)throw new Error(`name over ${Py} chars`);return t.trim()}async function Hy({trackId:t}={}){let e=await Je();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function BR(t){let n=(await Je()).flows.find(r=>r.id===t);return n?{...n}:null}async function Wy(t){let e=await BR(t);if(!e)return null;let n=new Map((await Hn({trackId:e.trackId})).map(r=>[r.id,r]));return e.steps=e.steps.map(r=>{let o=n.get(r.mockId);if(!o)return{...r,missing:!0,mockName:null,mockState:null,servedV:null,width:null,height:null};let s=Ne(o,r.v);return{...r,missing:!1,mockName:o.name,mockState:o.state,servedV:s?.v??null,width:s?.width??null,height:s?.height??null}}),e}async function qy({trackId:t,name:e,steps:n,cwd:r,box:o,agent:s}){if(!t)throw new Error("trackId required");if(!r)throw new Error("cwd required");let a=DR(e),i=await Je(),c=i.flows.find(p=>p.trackId===t&&p.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await Uy({trackId:t,steps:n}),f=Date.now(),d={id:Ly().slice(0,8),trackId:t,box:o||null,agent:s||null,name:a,cwd:r,version:1,versions:[{v:1,steps:l,resolved:u,note:null,createdAt:f}],steps:l,createdAt:f,updatedAt:f};return i.flows.push(d),await Zr(),await Qu(d,"created"),xi(d),{...d}}async function Ky(t,{steps:e,note:n}={}){let o=(await Je()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await Uy({trackId:o.trackId,steps:e,prevSteps:o.steps});return o.version+=1,o.versions.push({v:o.version,steps:s,resolved:a,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),o.steps=s,o.updatedAt=Date.now(),await Zr(),await Qu(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),xi(o),{...o}}async function Gy(t){let e=await Je(),n=e.flows.findIndex(o=>o.id===t);if(n===-1)throw new Error("flow not found");let r=e.flows[n];return e.flows.splice(n,1),await Zr(),await Qu(r,"deleted"),re("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Vy(t){return(await Je()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function zy(t){let n=(await Je()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id,r.updatedAt=Date.now(),xi(r);return n.length&&await Zr(),n.length}async function Jy(t){let e=await Je(),n=e.flows.filter(r=>r.trackId===t);if(n.length){e.flows=e.flows.filter(r=>r.trackId!==t),await Zr();for(let r of n)re("flows",r.id).catch(()=>{})}return n.length}async function Xy(){if(!wt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Ot("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Je();for(let r of n.flows)await xi(r);return{pushed:n.flows.length,source:"plane"}}import td from"node:fs/promises";import ws from"node:path";import{randomUUID as jR}from"node:crypto";var Zy=".bostrat/uploads",ed=t=>ws.join(t,Zy),Yy=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,LR=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),FR=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 nd(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return LR.has(e)?"image":FR.has(e)?"doc":null}function UR(t){let r=ws.basename(String(t||"")).replace(/[\x00-\x1f]/g,"").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"").slice(-80)||"file";return r.startsWith(".")?`f${r}`:r}async function Qy(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>Yy)throw new Error(`file too large \u2014 max ${Math.floor(Yy/(1024*1024))} MB`);let r=UR(e),o=ws.extname(r).slice(1),s=nd(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=jR().slice(0,8),i=ed(t);await td.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await td.writeFile(ws.join(i,c),n,{mode:384}),await td.chmod(ws.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${Zy}/${c}`,kind:s,size:n.length}}function tb(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var HR=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function rd(t){return t.role==="user"&&HR.test((t.text||"").trim())}function ar(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var KR=qR(WR),Xe=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||"/tmp",".bostrat"),cb=U.join(Xe,"tracks.json"),GR=U.join(Xe,"events.jsonl"),ud=U.join(Xe,"workspaces"),VR=process.env.BOSTRAT_DEFAULT_REPO||null,Ai={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"}},_i=Object.keys(Ai);function zR(t,{skipPermissions:e=!0}={}){let n=Ai[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 lb(t,e,{skipPermissions:n=!0}={}){let r=Ai[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 As(t){let e=Ai[t];if(e)return process.env[e.envVar]||e.base}var _n={at:0,value:null};async function pd({maxAgeMs:t=6e4}={}){if(_n.value&&Date.now()-_n.at<t)return _n.value;let e=_n.value,n=await Promise.all(_i.map(async r=>{let o=await ia(As(r),{retries:[]});return o===yr?[r,e?.[r]??!0]:[r,o===aa]}));return _n.value=Object.fromEntries(n),_n.at=Date.now(),_n.value}async function Ei(t,e=!0){let n=zR(t,{skipPermissions:e});if(await ia(n)===gr){_n.at=0;let r=n.split(/\s+/)[0],o=process.env.FLY_APP_NAME?"This Fly node's image predates CLI parity \u2014 update the node image (Links page \u2192 Fly card \u2192 Update) or re-provision.":$c[r]?`Install it on this node: ${$c[r]}`:"Its CLI must be installed and on PATH first.";throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). ${o}`)}return n}var Ii=".bostrat/handoff/latest.md";function JR(t,e=Ii){return`${t.trim()}
|
|
154
|
+
`+r.slice(-n)),r}function Te(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
155
|
+
`).find(n=>n.trim())||"gh failed"}var e$=6e3,_i=new Map;function ld(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 zy(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(ld(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await wi(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(_i.get(t.id)||0)<e$)return;_i.set(t.id,o);let s;try{s=await ys(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await et(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Jt({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 Zo(i,a.number))){_i.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,...i?{repo:i}:{}}}).catch(()=>{}),_i.delete(t.id)}import n$ from"node:fs/promises";import Qy from"node:path";import r$ from"node:os";import{randomUUID as tb}from"node:crypto";var o$=process.env.BOSTRAT_STATE_DIR||Qy.join(process.env.HOME||r$.tmpdir(),".bostrat"),eb=Qy.join(o$,"flows.json"),Jy=20,Xy=80,Yy=500,Zy=80,ze=null,ud=null;async function Je(){return ze||(ud||=(async()=>{let t=!0;try{ze=JSON.parse(await n$.readFile(eb,"utf8"))}catch{ze={flows:[]},t=!1}finally{ud=null}if(ze.flows||=[],!t&&ht()){let{records:e,source:n}=await Rt("flows").catch(()=>({records:[],source:"error"}));if(n==="plane"){for(let r of e)!r.deletedAt&&r.data?.id&&ze.flows.push(r.data);ze.flows.length&&await ro().catch(()=>{})}}return ze})(),ud)}async function ro(){await jt(eb,ze)}var Ii=t=>Nt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function dd(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function nb({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Jy)throw new Error(`steps must be an array of 1-${Jy}`);let r=await qn({trackId:t}),o=[...n],s=[];for(let i=0;i<e.length;i++){let c=e[i]||{},l=`step ${i+1}`,u=typeof c.caption=="string"?c.caption.trim():"";if(!u)throw new Error(`${l}: caption (string) required`);if(u.length>Yy)throw new Error(`${l}: caption over ${Yy} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Zy)throw new Error(`${l}: title over ${Zy} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let p=r.filter(S=>S.id===d);if(p.length||(p=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!p.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(p.length>1)throw new Error(`${l}: "${d}" is ambiguous (${p.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let m=p[0],w=null;if(c.v!=null&&(w=Number(c.v),!Number.isInteger(w)||!(m.versions||[]).some(S=>S.v===w)))throw new Error(`${l}: "${m.name}" has no captured v${c.v} to pin`);let y=o.findIndex(S=>S.mockId===m.id),k=y>=0?o.splice(y,1)[0].id:tb().slice(0,8);s.push({id:k,mockId:m.id,caption:u,title:f,v:w})}let a={};for(let i of s)i.mockId in a||(a[i.mockId]=r.find(c=>c.id===i.mockId)?.readyVersion??null);return{steps:s,resolved:a}}function s$(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Xy)throw new Error(`name over ${Xy} chars`);return t.trim()}async function rb({trackId:t}={}){let e=await Je();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function a$(t){let n=(await Je()).flows.find(r=>r.id===t);return n?{...n}:null}async function ob(t){let e=await a$(t);if(!e)return null;let n=new Map((await qn({trackId:e.trackId})).map(r=>[r.id,r]));return e.steps=e.steps.map(r=>{let o=n.get(r.mockId);if(!o)return{...r,missing:!0,mockName:null,mockState:null,servedV:null,width:null,height:null};let s=Ne(o,r.v);return{...r,missing:!1,mockName:o.name,mockState:o.state,servedV:s?.v??null,width:s?.width??null,height:s?.height??null}}),e}async function sb({trackId:t,name:e,steps:n,cwd:r,box:o,agent:s}){if(!t)throw new Error("trackId required");if(!r)throw new Error("cwd required");let a=s$(e),i=await Je(),c=i.flows.find(p=>p.trackId===t&&p.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await nb({trackId:t,steps:n}),f=Date.now(),d={id:tb().slice(0,8),trackId:t,box:o||null,agent:s||null,name:a,cwd:r,version:1,versions:[{v:1,steps:l,resolved:u,note:null,createdAt:f}],steps:l,createdAt:f,updatedAt:f};return i.flows.push(d),await ro(),await dd(d,"created"),Ii(d),{...d}}async function ab(t,{steps:e,note:n}={}){let o=(await Je()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await nb({trackId:o.trackId,steps:e,prevSteps:o.steps});return o.version+=1,o.versions.push({v:o.version,steps:s,resolved:a,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),o.steps=s,o.updatedAt=Date.now(),await ro(),await dd(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),Ii(o),{...o}}async function ib(t){let e=await Je(),n=e.flows.findIndex(o=>o.id===t);if(n===-1)throw new Error("flow not found");let r=e.flows[n];return e.flows.splice(n,1),await ro(),await dd(r,"deleted"),ne("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function cb(t){return(await Je()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function lb(t){let n=(await Je()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id,r.updatedAt=Date.now(),Ii(r);return n.length&&await ro(),n.length}async function ub(t){let e=await Je(),n=e.flows.filter(r=>r.trackId===t);if(n.length){e.flows=e.flows.filter(r=>r.trackId!==t),await ro();for(let r of n)ne("flows",r.id).catch(()=>{})}return n.length}async function db(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Je();for(let r of n.flows)await Ii(r);return{pushed:n.flows.length,source:"plane"}}import fd from"node:fs/promises";import xs from"node:path";import{randomUUID as i$}from"node:crypto";var pb=".bostrat/uploads",pd=t=>xs.join(t,pb),fb=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,c$=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),l$=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 md(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return c$.has(e)?"image":l$.has(e)?"doc":null}function u$(t){let r=xs.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 mb(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>fb)throw new Error(`file too large \u2014 max ${Math.floor(fb/(1024*1024))} MB`);let r=u$(e),o=xs.extname(r).slice(1),s=md(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=i$().slice(0,8),i=pd(t);await fd.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await fd.writeFile(xs.join(i,c),n,{mode:384}),await fd.chmod(xs.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${pb}/${c}`,kind:s,size:n.length}}function hb(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var d$=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function hd(t){return t.role==="user"&&d$.test((t.text||"").trim())}function cr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var m$=p$(f$),Xe=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||"/tmp",".bostrat"),vb=U.join(Xe,"tracks.json"),h$=U.join(Xe,"events.jsonl"),Sd=U.join(Xe,"workspaces"),w$=process.env.BOSTRAT_DEFAULT_REPO||null,Ci={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"}},Mi=Object.keys(Ci);function g$(t,{skipPermissions:e=!0}={}){let n=Ci[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 Tb(t,e,{skipPermissions:n=!0}={}){let r=Ci[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 $s(t){let e=Ci[t];if(e)return process.env[e.envVar]||e.base}var In={at:0,value:null};async function Ad({maxAgeMs:t=6e4}={}){if(In.value&&Date.now()-In.at<t)return In.value;let e=In.value,n=await Promise.all(Mi.map(async r=>{let o=await pa($s(r),{retries:[]});return o===xr?[r,e?.[r]??!0]:[r,o===fa]}));return In.value=Object.fromEntries(n),In.at=Date.now(),In.value}async function Ni(t,e=!0){let n=g$(t,{skipPermissions:e});if(await pa(n)===kr){In.at=0;let r=n.split(/\s+/)[0],o=process.env.FLY_APP_NAME?"This Fly node's image predates CLI parity \u2014 update the node image (Links page \u2192 Fly card \u2192 Update) or re-provision.":Lc[r]?`Install it on this node: ${Lc[r]}`:"Its CLI must be installed and on PATH first.";throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). ${o}`)}return n}var Pi=".bostrat/handoff/latest.md";function y$(t,e=Pi){return`${t.trim()}
|
|
155
156
|
|
|
156
|
-
(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
|
|
157
|
+
(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 b$=/\(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 wb(t){return t.role==="user"&&b$.test((t.text||"").trim())}var Ri=".bostrat/handoff/subtask-result.md",vs=".bostrat/handoff/lead-context.md",k$=(t,e=!1)=>e?`You are a SUBTASK agent working in an isolated multi-repo workspace, delegated by a lead agent on a shared workstream. This directory is NOT itself a git repo: each subdirectory is an isolated git worktree of one of the workstream's repos, already seeded with the lead's full working tree for that repo. The conversation so far is summarized in ${vs} \u2014 read that first for context. Your scoped task:
|
|
157
158
|
|
|
158
159
|
${t}
|
|
159
160
|
|
|
160
|
-
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${
|
|
161
|
+
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${Ri} (at this directory's root) with sections \u2014 Result (what you did), Files (paths you changed, prefixed with the repo directory), Notes (anything the lead must know). Writing that file is your DONE signal: do it last, then stop and take no further action.`:`You are a SUBTASK agent working in an isolated git worktree, delegated by a lead agent on a shared workstream. The lead's full working tree is already checked out here, and the conversation so far is summarized in ${vs} \u2014 read that first for context. Your scoped task:
|
|
161
162
|
|
|
162
163
|
${t}
|
|
163
164
|
|
|
164
|
-
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 ${
|
|
165
|
-
`;return sd=sd.then(()=>F.mkdir(Xe,{recursive:!0})).then(()=>F.appendFile(GR,n)).catch(()=>{}),sd}var dt=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),QR(t.id,r),Tg(t,r),r};xp(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(dt(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await X().catch(()=>{}))});var ad=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),t$=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function ub(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=>t$.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 db(t,e,n,{kind:r}={}){let o=await rt(e);if(!o)throw new Error(`box "${e}" is not running`);let s=As(o.agent);await ln({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await Et(e,n,{...Ce(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=tt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);dt(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await X()}var cr=new Set(["merged","conflict","canceled","failed","handed-off"]),nb=new Map;async function e$(t){let e=U.join(ka(t),`${t.id}.ndjson`),n;try{n=await F.stat(e)}catch{return null}let r=nb.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await L(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return nb.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function n$(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:$u(n.name,n.agent)}async function Ye(t,e=null){let n=e??new Map((await Oe()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=cr.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([e$(t),n$(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a,archiving:vi.has(t.id)}}var r$=5e3;function o$(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),f=l.createdAt-r$,d=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=f&&m.birthMs<d);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function fb(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(d=>d.box.sessionId),a=hp(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=o$(u,a,d=>i.has(d));for(let d of f){let p=u.find(m=>m.name===d.box);p&&(p.sessionId=d.sessionId),await ap(d.box,d.sessionId),n=!0}}return n}async function at(){let t=await H();await fb(t)&&await X();let e=new Map((await Oe()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>Ye(n,e)))}async function pb(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function mb(t){let e=go(t);if(!e)return null;let n=await H();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&df(o.hookTokenHash,e))return{track:r,box:o};return null}async function et(t){let e=await H(),n=tt(e,t);return n?(await fb(e)&&await X(),Ye(n)):null}var s$={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function rb(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(s$[t.agent]||[]).some(n=>n.test(e))}var a$=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ob(t){let e=(t.text||"").trim();return e?a$.some(n=>n.test(e)):!1}var i$=10*6e4;async function md(t,e={}){let n=await et(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=_s(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=sl(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...Wm(n),...l],d={windows:[...rm(n),...om(n,va),...a,...u]},m=(await Promise.all(n.boxes.map(k=>eu(k,e)))).flat(),w=m.map(k=>{let P={kind:"message",...k};if(P.role==="assistant"&&(P.text||"").trim()&&P.tools?.length){let $t=ns(P.agent).cliTools,Qt=P.tools.filter(Tt=>!$t.has(Tt));return{...P,tools:Qt.length?Qt:void 0}}return P}).map(k=>{if(k.role!=="user")return k;let P=pi(k.text);return P?P.kind==="suppress"?null:{...k,note:P,text:cy(P)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!rb(k)).filter(k=>!ob(k)).filter(k=>!eb(k)).filter(k=>!rd(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!sm(k,d)),y=w.filter(k=>k.failed&&k.text===Jl);if(y.length){let k=new Map;for(let P of y){if(!k.has(P.box)){let Tt=[...(await mt(P.box,{lines:_t}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(cf=>cf[1]);k.set(P.box,Tt.length?Tt[Tt.length-1]:null)}let $t=k.get(P.box);$t&&(P.text=`\u25A0 ${$t}`)}}let b=await Promise.all(n.boxes.map(k=>Iw(k,e)));await ay(n,b.flat()),await oy(n,w),await Ag(n),await Xm(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await fy(n,k),k.agent==="cursor"&&k.state==="live"&&await wy(n,k);let S=await Promise.all(n.boxes.map(k=>Ew(k,e)));await sy(n,S.flat().filter(k=>!ad(k.box,k.ts,u)));let E=await L(n),O=c&&i.viewId?i.viewId:null,nt=Pg(E),R=ds(E).filter(k=>!(k.kind==="message"&&(rb(k)||ob(k)||eb(k)||rd(k)))&&!(k.kind==="view-card"&&O&&k.viewId===O)),C=Dg(R,nt),v=R.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!C.has(k)&&ad(k.box,k.ts,nt))),D=Bu(E,v).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!ad(k.anchorBox,k.anchorTs??k.ts,u)),B=dy([...v,...D].filter(k=>k.ts!=null).sort((k,P)=>k.ts-P.ts)),lt=c$(n),q=lt.size?B.filter(k=>k.kind==="event"||!lt.has(k.box??k.anchorBox)):B,T=n.boxes.find(k=>k.name===n.leadBox)||null,g=T?await Oo(T.cwd,T.sessionId,{fallbackNewest:!1}):null;if(!g&&T){let k=[...E].reverse().find(P=>P.type==="context.usage"&&P.boxId===T.name);k?.payload?.window&&(g={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let h=[...m].reverse().find(k=>k.role==="assistant"&&k.model),A=h?.model||null,_=[...n.events||[]].reverse().find(k=>k.type==="model-change"),J=g?.model||A;_&&(!h?.ts||_.at>=h.ts)&&(J=_.to);let W=T?await mt(T.name,{lines:_t}).catch(()=>""):"",it=T&&Ur(T.agent,W)?await an(T.name).catch(()=>W):W,Pt=T?nr(T,it):null;!Pt&&T&&!Co(T.agent,W)&&!dd(n.id).length&&(Pt=pu(q,{leadBoxId:T.name,provider:T.agent,isAnswered:k=>E.some(P=>P.type==="prompt.answered"&&P.payload?.promptId===k)})),Pt&&_g(n,T,Pt);let K=q.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),$=!!T&&ui(T.name,T.agent,W)&&!Pt;T&&!T.readyAt&&($||Pt||W&&ha(T.agent,W))&&(T.readyAt=Date.now(),await X());let z=!!T&&!T.readyAt&&Date.now()-(n.createdAt||0)<i$;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&T&&ha(T.agent,W)&&!Pt){let k=await H(),P=tt(k,n.id);P?.setup&&!P.setup.connectedAt&&(P.setup.connectedAt=Date.now(),n.setup.connectedAt=P.setup.connectedAt,await I(P,{type:"provider.connected",actor:"system",source:"pane",boxId:T.name,provider:T.agent,payload:{provider:T.agent,ts:P.setup.connectedAt}}).catch(()=>{}),await X())}let xt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,ht=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!$){let P=$a(n.id);P&&(dt(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:P.finishedAt}),await X())}else if(Pt||k>Um&&!$&&!i.viewId||k>ol){let P=Pt?"needs-input":k>ol?"timeout":"no-view";$a(n.id,{error:P})&&await I(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:T?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:P}}).catch(()=>{})}else ht={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:K,context:g?{pct:g.pct,occupancy:g.occupancy,window:g.window,windowKnown:g.windowKnown,model:g.model}:null,models:{current:J,options:bg(T?.agent)},pendingPrompt:Pt,working:$,stopping:g$(n.id),leadStarting:z,handoff:xt,view:ht,queued:dd(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer"),draft:n.draft||null,draftRev:n.draftRev||0}}function c$(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 hb(t,e){let n=await et(t);if(!n)return null;let r=ao(t).find(f=>f.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 L(n),a=ds(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Bu(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await mt(o,{lines:_t}).catch(()=>"");u=ui(o,l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function hd(t,{promptId:e,answers:n,text:r}={}){let o=tt(await H(),t);if(!o)throw new Error(`track "${t}" not found`);$i(t,"prompt-answer");let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await rt(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await cu(s);if(i||(i=await l$(o,s)),!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 d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await Et(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await vt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=Gw(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await le(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=Lw(i,n||[]);await vt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Ip(i,n||[]);await Et(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let p=0;p<d.length;p++){let m=d[p],w=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));Iu(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:p,questionTotal:i.questionTotal??d.length,header:m.header||"",question:m.question||"",multiSelect:!!m.multiSelect,chosen:w})}return await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=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,...f?{control:f}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Iu(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function l$(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return pu(ds(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function wb(t,{promptId:e,questionIndex:n}={}){let r=tt(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await rt(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await cu(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=Fw(a,n);return c.length?(await vt(r.leadBox,c,{kind:"navigate-prompt"}),await G({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var u$=1500,bs=new Map;function wd(t){let e=bs.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},bs.set(t,e)),e}function dd(t){return(bs.get(t)?.items??[]).map(e=>({...e}))}function Ri(t){let e=bs.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}async function gb(t){let e=Ri(t),n=e.filter(o=>o.text&&!o.kind).map(o=>o.text),r=null;if(n.length){let o=tt(await H(),t);if(o)return r=no(o,n),await X(),{removed:e,restored:r,draftRev:o.draftRev||0}}return{removed:e,restored:r,draftRev:null}}function yb(t){let e=wd(t);e.timer||(e.timer=setInterval(()=>d$(t).catch(()=>{}),u$))}async function d$(t){let e=bs.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=tt(await H(),t);if(!n||n.archivedAt){Ri(t);return}let r=_s(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){ar(e,!1);return}let o=await rt(n.leadBox);if(!o){ar(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox);if(as(s?.agent)){ar(e,!1);return}let a=await mt(n.leadBox,{lines:_t}).catch(()=>"");if(ma(s?.agent,a)){ar(e,!1);return}if(Ur(s?.agent,a)){let c=await an(n.leadBox).catch(()=>a);if(nr(s,c)){ar(e,!1);return}}if(!ar(e,!0))return;let i=e.items.shift();e.idleSince=null,await kb(n,s,o,i.text,i.kind??null,i.attachments??null),await X()}finally{e.flushing=!1}}}async function bb(t,e,n,r){let o=as(e?.agent);if(!o)return;if(!r&&typeof n=="string"&&n.trim()&&(no(t,[n]),await X()),lg(t.id,o))try{await I(t,{type:"usage.cap.hit",actor:"system",provider:e?.agent??null,boxId:t.leadBox,payload:{...o,blockedKind:r??null}})}catch{}let s=new Error(`${e?.agent??"the lead provider"} is paused \u2014 its ${o.window} spend limit is reached ($${o.spentUsd.toFixed(2)} of $${o.limitUsd.toFixed(2)})`);throw s.usageCap=o,s}async function kb(t,e,n,r,o=null,s=null){await bb(t,e,r,o);let a=ks.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=JR(r,t.pendingHandoffHydrate.artifact||Ii),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=tb(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
165
|
+
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 ${Ri} 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.`,x$=(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."),Ee=null,wd=null;async function H(){return Ee||(wd||=(async()=>{try{Ee=JSON.parse(await F.readFile(vb,"utf8"))}catch{Ee={tracks:[]}}finally{wd=null}return Ee})(),wd)}async function V(){await jt(vb,Ee)}var Z=(t,e)=>t.tracks.find(n=>n.id===e)||null,gd=Promise.resolve();function S$(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
166
|
+
`;return gd=gd.then(()=>F.mkdir(Xe,{recursive:!0})).then(()=>F.appendFile(h$,n)).catch(()=>{}),gd}var dt=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),S$(t.id,r),Pg(t,r),r};Op(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(dt(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var yd=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),v$=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Ab(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=>v$.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 Eb(t,e,n,{kind:r}={}){let o=await tt(e);if(!o)throw new Error(`box "${e}" is not running`);let s=$s(o.agent);await un({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await Et(e,n,{...Ce(o.agent),kind:r||"orchestration"}),await q({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=Z(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);dt(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var ur=new Set(["merged","conflict","canceled","failed","handed-off"]),gb=new Map;async function T$(t){let e=U.join(Ea(t),`${t.id}.ndjson`),n;try{n=await F.stat(e)}catch{return null}let r=gb.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await L(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return gb.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function A$(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Lu(n.name,n.agent)}async function Ye(t,e=null){let n=e??new Map((await $e()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=ur.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([T$(t),A$(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a,archiving:$i.has(t.id)}}var E$=5e3;function _$(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),f=l.createdAt-E$,d=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=f&&m.birthMs<d);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function _b(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(d=>d.box.sessionId),a=Ap(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=_$(u,a,d=>i.has(d));for(let d of f){let p=u.find(m=>m.name===d.box);p&&(p.sessionId=d.sessionId),await wp(d.box,d.sessionId),n=!0}}return n}async function st(){let t=await H();await _b(t)&&await V();let e=new Map((await $e()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>Ye(n,e)))}async function Ib(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function Rb(t){let e=So(t);if(!e)return null;let n=await H();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&xf(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await H(),n=Z(e,t);return n?(await _b(e)&&await V(),Ye(n)):null}var I$={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function yb(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(I$[t.agent]||[]).some(n=>n.test(e))}var R$=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function bb(t){let e=(t.text||"").trim();return e?R$.some(n=>n.test(e)):!1}var $$=10*6e4;async function Ed(t,e={}){let n=await Q(t);if(!n)return null;let r=new Set([...n.events.filter(b=>b.type==="orchestration-prompt"&&b.prompt).map(b=>String(b.prompt).trim()),...n.events.filter(b=>b.type==="said"&&b.text).map(b=>String(b.text).trim())]),o=Os(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=ml(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...th(n),...l],d={windows:[...pm(n),...mm(n,Ra),...a,...u]},m=(await Promise.all(n.boxes.map(b=>uu(b,e)))).flat(),w=m.map(b=>{let E={kind:"message",...b};if(E.role==="assistant"&&(E.text||"").trim()&&E.tools?.length){let lt=as(E.agent).cliTools,nt=E.tools.filter(bt=>!lt.has(bt));return{...E,tools:nt.length?nt:void 0}}return E}).map(b=>{if(b.role!=="user")return b;let E=ki(b.text);return E?E.kind==="suppress"?null:{...b,note:E,text:vy(E)}:b}).filter(Boolean).filter(b=>!(b.role==="user"&&r.has((b.text||"").trim()))).filter(b=>!yb(b)).filter(b=>!bb(b)).filter(b=>!wb(b)).filter(b=>!hd(b)).filter(b=>b.role!=="assistant"||!!(b.text||"").trim()||!!b.images?.length).filter(b=>!hm(b,d)),y=w.filter(b=>b.failed&&b.text===ou);if(y.length){let b=new Map;for(let E of y){if(!b.has(E.box)){let bt=[...(await mt(E.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Dt=>Dt[1]);b.set(E.box,bt.length?bt[bt.length-1]:null)}let lt=b.get(E.box);lt&&(E.text=`\u25A0 ${lt}`)}}let k=await Promise.all(n.boxes.map(b=>Lw(b,e)));await gy(n,k.flat()),await hy(n,w),await Dg(n),await ih(n,w).catch(()=>{});for(let b of n.boxes)b.agent==="codex"&&b.state==="live"&&await _y(n,b),b.agent==="cursor"&&b.state==="live"&&await Oy(n,b);let S=await Promise.all(n.boxes.map(b=>jw(b,e)));await wy(n,S.flat().filter(b=>!yd(b.box,b.ts,u)));let _=await L(n),$=c&&i.viewId?i.viewId:null,z=Gg(_),O=ws(_).filter(b=>!(b.kind==="message"&&(yb(b)||bb(b)||wb(b)||hd(b)))&&!(b.kind==="view-card"&&$&&b.viewId===$)),P=Vg(O,z),v=O.filter(b=>!(b.kind==="message"&&b.role==="assistant"&&!P.has(b)&&yd(b.box,b.ts,z))),M=Ju(_,v).map(b=>({kind:"command-batch",id:b.id,ts:b.pending?b.ts:(b.anchorTs??b.ts)-1,anchorTs:b.anchorTs,anchorBox:b.anchorBox,agent:b.agent,boxRole:b.boxRole,commands:b.commands,pending:b.pending})).filter(b=>!yd(b.anchorBox,b.anchorTs??b.ts,u)),D=Ey([...v,...M].filter(b=>b.ts!=null).sort((b,E)=>b.ts-E.ts)),it=O$(n),ct=it.size?D.filter(b=>b.kind==="event"||!it.has(b.box??b.anchorBox)):D,T=n.boxes.find(b=>b.name===n.leadBox)||null,g=T?await Do(T.cwd,T.sessionId,{fallbackNewest:!1}):null;if(!g&&T){let b=[..._].reverse().find(E=>E.type==="context.usage"&&E.boxId===T.name);b?.payload?.window&&(g={pct:b.payload.pct??(b.payload.occupancy!=null?b.payload.occupancy/b.payload.window:null),occupancy:b.payload.occupancy??null,window:b.payload.window,windowKnown:!0,model:b.payload.model||null})}let h=[...m].reverse().find(b=>b.role==="assistant"&&b.model),A=h?.model||null,I=[...n.events||[]].reverse().find(b=>b.type==="model-change"),J=g?.model||A;I&&(!h?.ts||I.at>=h.ts)&&(J=I.to);let W=T?await mt(T.name,{lines:At}).catch(()=>""):"",at=T&&Kr(T.agent,W)?await cn(T.name).catch(()=>W):W,Mt=T?or(T,at):null;!Mt&&T&&!jo(T.agent,W)&&!vd(n.id).length&&(Mt=Su(ct,{leadBoxId:T.name,provider:T.agent,isAnswered:b=>_.some(E=>E.type==="prompt.answered"&&E.payload?.promptId===b)})),Mt&&Bg(n,T,Mt);let St=ct.filter(b=>!(b.kind==="message"&&b.role==="assistant"&&b.ask&&!String(b.text||"").trim()&&!b.images?.length)),pr=!!T&&hi(T.name,T.agent,W)&&!Mt;T&&!T.readyAt&&(pr||Mt||W&&xa(T.agent,W))&&(T.readyAt=Date.now(),await V());let zs=!!T&&!T.readyAt&&Date.now()-(n.createdAt||0)<$$;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&T&&xa(T.agent,W)&&!Mt){let b=await H(),E=Z(b,n.id);E?.setup&&!E.setup.connectedAt&&(E.setup.connectedAt=Date.now(),n.setup.connectedAt=E.setup.connectedAt,await R(E,{type:"provider.connected",actor:"system",source:"pane",boxId:T.name,provider:T.agent,payload:{provider:T.agent,ts:E.setup.connectedAt}}).catch(()=>{}),await V())}let G=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,B=null;if(c){let b=Date.now()-i.startedAt;if(i.viewId&&!pr){let E=Pa(n.id);E&&(dt(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:E.finishedAt}),await V())}else if(Mt||b>Zm&&!pr&&!i.viewId||b>pl){let E=Mt?"needs-input":b>pl?"timeout":"no-view";Pa(n.id,{error:E})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:T?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:E}}).catch(()=>{})}else B={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:St,context:g?{pct:g.pct,occupancy:g.occupancy,window:g.window,windowKnown:g.windowKnown,model:g.model}:null,models:{current:J,options:$g(T?.agent)},pendingPrompt:Mt,working:pr,stopping:F$(n.id),leadStarting:zs,handoff:G,view:B,queued:vd(n.id).filter(b=>b.kind!=="ship-notify"&&b.kind!=="prompt-answer"),draft:n.draft||null,draftRev:n.draftRev||0}}function O$(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 $b(t,e){let n=await Q(t);if(!n)return null;let r=fo(t).find(f=>f.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 L(n),a=ws(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Ju(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await mt(o,{lines:At}).catch(()=>"");u=hi(o,l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function _d(t,{promptId:e,answers:n,text:r}={}){let o=Z(await H(),t);if(!o)throw new Error(`track "${t}" not found`);Bi(t,"prompt-answer");let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await tt(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await gu(s);if(i||(i=await C$(o,s)),!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 d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await Et(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await vt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=rg(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await ce(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=Xw(i,n||[]);await vt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=jp(i,n||[]);await Et(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let p=0;p<d.length;p++){let m=d[p],w=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));Bu(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:p,questionTotal:i.questionTotal??d.length,header:m.header||"",question:m.question||"",multiSelect:!!m.multiSelect,chosen:w})}return await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=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,...f?{control:f}:{}},await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Bu(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function C$(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return Su(ws(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Ob(t,{promptId:e,questionIndex:n}={}){let r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await tt(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await gu(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=Yw(a,n);return c.length?(await vt(r.leadBox,c,{kind:"navigate-prompt"}),await q({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 M$=1500,Ts=new Map;function Id(t){let e=Ts.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Ts.set(t,e)),e}function vd(t){return(Ts.get(t)?.items??[]).map(e=>({...e}))}function Di(t){let e=Ts.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}async function Cb(t){let e=Di(t),n=e.filter(o=>o.text&&!o.kind).map(o=>o.text),r=null;if(n.length){let o=Z(await H(),t);if(o)return r=io(o,n),await V(),{removed:e,restored:r,draftRev:o.draftRev||0}}return{removed:e,restored:r,draftRev:null}}function Mb(t){let e=Id(t);e.timer||(e.timer=setInterval(()=>N$(t).catch(()=>{}),M$))}async function N$(t){let e=Ts.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=Z(await H(),t);if(!n||n.archivedAt){Di(t);return}let r=Os(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){cr(e,!1);return}let o=await tt(n.leadBox);if(!o){cr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox);if(us(s?.agent)){cr(e,!1);return}let a=await mt(n.leadBox,{lines:At}).catch(()=>"");if(ka(s?.agent,a)){cr(e,!1);return}if(Kr(s?.agent,a)){let c=await cn(n.leadBox).catch(()=>a);if(or(s,c)){cr(e,!1);return}}if(!cr(e,!0))return;let i=e.items.shift();e.idleSince=null,await Pb(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function Nb(t,e,n,r){let o=us(e?.agent);if(!o)return;if(!r&&typeof n=="string"&&n.trim()&&(io(t,[n]),await V()),kg(t.id,o))try{await R(t,{type:"usage.cap.hit",actor:"system",provider:e?.agent??null,boxId:t.leadBox,payload:{...o,blockedKind:r??null}})}catch{}let s=new Error(`${e?.agent??"the lead provider"} is paused \u2014 its ${o.window} spend limit is reached ($${o.spentUsd.toFixed(2)} of $${o.limitUsd.toFixed(2)})`);throw s.usageCap=o,s}async function Pb(t,e,n,r,o=null,s=null){await Nb(t,e,r,o);let a=As.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=y$(r,t.pendingHandoffHydrate.artifact||Pi),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=hb(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
166
167
|
|
|
167
|
-
${f}`:f}let u=dt(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...l.length?{attachments:l}:{}});
|
|
168
|
+
${f}`:f}let u=dt(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...l.length?{attachments:l}:{}});lr(t,null);try{if(await un({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:$s(n.agent)}),(As.get(t.id)||0)!==a){!o&&r.trim()&&(io(t,[r]),await V());let f=new Error("delivery stopped \u2014 the message was handed back as the draft");throw f.interrupted=!0,f}await Et(t.leadBox,i,{...Ce(e?.agent),kind:c?"lead-handoff-first-prompt":"say"})}catch(f){throw dt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,...f?.interrupted?{reason:"interrupted"}:{}}),f}await X$(t,e,i)||(await Lb(t.leadBox,e?.agent,i.length+8).catch(()=>{}),dt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,reason:"unsubmitted"}),o||io(t,[r])),c&&await q({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function Db(t,{actions:e}={}){Id(t).items.push({id:Rs(),text:zg(e),ts:Date.now(),kind:"ship-notify"}),Mb(t)}async function ce(t,e,{kind:n=null,attachments:r=null}={}){let o=Array.isArray(r)?r.filter(d=>d&&typeof d.rel=="string"):[];if((typeof e!="string"||!Gr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=s.boxes.find(d=>d.name===s.leadBox),i=Gr(e);await Nb(s,a,i,n),Bi(t,"say");let c=await tt(s.leadBox);if(!c&&await Cd(s)&&(await V(),c=await tt(s.leadBox)),!c)throw new Error(`lead box "${s.leadBox}" is not running`);let l=Id(t),u=await mt(s.leadBox,{lines:At}).catch(()=>""),f=l.items.length>0||ka(a?.agent,u);if(!f&&Kr(a?.agent,u)){let d=await cn(s.leadBox).catch(()=>u);f=!!or(a,d)}if(f){let d={id:Rs(),text:i,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),Mb(t),lr(s,null),await V(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await Pb(s,a,c,i,n,o),await V(),{ok:!0,leadBox:s.leadBox}}var kb="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 Bb(t,{hint:e}={}){let n=Z(await H(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=ml(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";Qm(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${kb}
|
|
168
169
|
|
|
169
|
-
What to preview: ${String(e).trim()}`:
|
|
170
|
+
What to preview: ${String(e).trim()}`:kb;try{await ce(t,a,{kind:"spin-up-view"})}catch(i){throw Pa(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Rd(t){let e=Z(await H(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);As.set(t,(As.get(t)||0)+1);let n=Di(t),r=n.filter(i=>i.text&&!i.kind).map(i=>i.text),o=ao.get(t);if(o&&!o.canceled){o.clicks++;let i=null;return r.length&&(i=io(e,r),await V()),{ok:!0,interrupted:!0,stopped:!1,stopping:!0,dropped:n.length,restored:i,draftRev:e.draftRev||0}}let s=U$(t,{droppedCount:n.length,droppedTexts:r});await Promise.race([s.settled,Rn(P$)]);let a=Z(await H(),t)||e;return{ok:!0,interrupted:s.result!=="no-session",stopped:s.result==="stopped"||s.result==="stopped-late",stopping:!s.result,dropped:n.length,restored:s.restored,draftRev:a.draftRev||0}}var P$=4e3,D$=3,B$=[300,350,400],j$=1e3,L$=12,ao=new Map,As=new Map;function F$(t){let e=ao.get(t);return!!e&&!e.canceled}function Bi(t,e){let n=ao.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function jb(t){Bi(t,"track-ended"),As.delete(t),Di(t)}function U$(t,{droppedCount:e,droppedTexts:n}){let r={startedAt:Date.now(),clicks:1,escSends:0,bgSends:0,queuedTexts:[...n],canceled:!1,cancelReason:null,result:null,restored:null},o;return r.settled=new Promise(s=>{o=s}),ao.set(t,r),(async()=>{let s=Z(await H(),t),a=s?.boxes.find(y=>y.name===s.leadBox),i=s?await tt(s.leadBox):null,c=!1,l=!1;if(i){t:for(let y=0;y<D$&&!r.canceled;y++){await vt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.escSends++;for(let k of B$){await Rn(k);let S=await mt(s.leadBox,{lines:At}).catch(()=>"");if(!jo(a?.agent,S)){c=!0;break t}}}if(!c){l=!0;for(let y=0;y<L$&&!r.canceled&&(await Rn(j$),!r.canceled);y++){let k=await mt(s.leadBox,{lines:At}).catch(()=>"");if(!jo(a?.agent,k)){c=!0;break}await vt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.bgSends++}}}let u=Z(await H(),t)||s,f=u?.boxes.find(y=>y.name===u.leadBox)||a,d=null,p=null;u&&f&&i&&c&&!r.canceled&&(d=await K$(u,f).catch(()=>null),d||(p=await z$(u,f).catch(()=>null)));let m=u&&i&&!r.canceled?await W$(u).catch(()=>null):null,w=[d||p,...r.queuedTexts].filter(Boolean);u&&w.length&&(r.restored=io(u,w),await V()),r.result=i?r.canceled?"canceled":c?l?"stopped-late":"stopped":"gave-up":"no-session",await H$(u||s,f,{ts:r.startedAt,result:r.result,dropped:e,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m,durationMs:Date.now()-r.startedAt,...r.cancelReason?{cancelReason:r.cancelReason}:{}}),await q({flow:"interrupt",phase:r.result==="gave-up"?"failed":"done",trackId:t,sessionName:u?.leadBox,agent:f?.agent,result:r.result,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m}).catch(()=>{})})().catch(()=>{}).finally(()=>{r.result||(r.result=r.canceled?"canceled":"gave-up"),ao.get(t)===r&&ao.delete(t),o()}),r}async function H$(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}async function W$(t){let e=ld(await L(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 q$=[250,400,500,600];async function K$(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 q$){await Rn(r);let o=await mt(t.leadBox,{lines:At}).catch(()=>""),s=is(e.agent,o);if(s&&yu(n.text,s))return await Lb(t.leadBox,e.agent,n.text.length+8),dt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),n.text}return null}var G$=3e4,V$=[350,650,900];async function z$(t,e){if(e?.agent!=="claude")return null;let n=t.events||[],r=[...n].reverse().find(s=>s.type==="said"&&s.text);if(!r||r.kind||Date.now()-r.at>G$||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!xt(r.text))return null;for(let s of V$){await Rn(s);let a;try{a=await uu(e,{})}catch{return null}if(Qw(a,r))return null;let i=await mt(t.leadBox,{lines:At}).catch(()=>"");if(is(e.agent,i))return null}return dt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),r.text}var J$=[250,450,700],bd=2;async function X$(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=bd;r++){let o=!1;for(let s of J$){await Rn(s);let a=await mt(t.leadBox,{lines:At}).catch(()=>""),i=tg(e.agent,n,a,{busy:jo(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<bd&&(await vt(t.leadBox,["Enter"],{kind:"submit-retry"}),q({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return q({flow:"say",phase:"submit-failed",trackId:t.id,sessionName:t.leadBox,agent:e.agent,retries:bd,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Lb(t,e,n){for(let r=0;r<3;r++){await ha(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Rn(200);let o=await mt(t,{lines:At}).catch(()=>""),s=is(e,o);if(!s)return!0;n=s.length+8}return!1}function lr(t,e,{source:n}={}){t.draftRev=(t.draftRev||0)+1;let r=typeof e=="string"?e:"";return r.trim()?t.draft={text:r,updatedAt:Date.now(),...n?{source:n}:{}}:delete t.draft,t.draft||null}function io(t,e){let n=[],r=o=>n.some(s=>xt(s).includes(xt(o)));for(let o of[t.draft?.text,...e])o&&o.trim()&&!r(o)&&n.push(o);return n.length?lr(t,n.join(`
|
|
170
171
|
|
|
171
|
-
`),{source:"restore"})?.text??null:null}var
|
|
172
|
+
`),{source:"restore"})?.text??null:null}var Y$=12e4;function Z$(t,e){let n=xt(e);if(!n)return!1;if(vd(t.id).some(o=>xt(o.text)===n))return!0;let r=t.events||[];for(let o=r.length-1;o>=0;o--){let s=r[o];if(typeof s.at=="number"&&Date.now()-s.at>Y$)break;if(!(s.type!=="said"||!s.text||xt(s.text)!==n))return!r.some(a=>a.type==="unsaid"&&a.saidTs===s.at)}return!1}async function Fb(t,e,{rev:n=null}={}){if(typeof e!="string")throw new Error("text (string) required");let r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=e,s=Gr(e);if(typeof n=="number"&&n<(r.draftRev||0)&&s&&Z$(r,s))return{ok:!0,draft:r.draft||null,rev:null,ignored:"already-sent"};if(typeof n=="number"&&n<(r.draftRev||0)&&r.draft?.source==="restore"){let i=r.draft.text;o.trim()?xt(o).includes(xt(i))?lr(r,o):lr(r,`${i}
|
|
172
173
|
|
|
173
|
-
${o}`,{source:"restore"}):
|
|
174
|
+
${o}`,{source:"restore"}):lr(r,i,{source:"restore"})}else lr(r,o);return await V(),{ok:!0,draft:r.draft||null,rev:r.draftRev||0}}async function Ub(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await tt(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(f=>f.name===r.leadBox)?.agent||o.agent||"claude",i=Og(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await Do(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await un({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:$s(o.agent)});let u=await Cg(a,r.leadBox,i);return dt(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await V(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Hb(t){for(let e of t){if(!e||!await ri(e))continue;let n=await ag(e).catch(()=>null);if(!n)continue;if((await F.stat(U.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Wt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:U.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function ms(t){let e=[];for(let n of Wt(t?.workspace)){if(!n?.dir)continue;let r=await Eu(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await _u(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Wb(t){let e=t;typeof e=="string"&&!U.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await To(e)??await xc(e));let n=await Hb([e]);if(!n)throw new Error(`tagged repo "${t}" is not a usable base (need a primary checkout or a synced owner/name)`);return n}async function Q$(t,e,n,r){let o=`track/${Pd(e)}-${t}`,s=[];try{await F.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Wb(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await et(c).catch(()=>null),u=U.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${U.basename(c)}-${p}`;a.add(u);let f=U.join(n,u),d=await oi(c);await Vr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ji(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await te(i.base,i.dir).catch(()=>{});throw await F.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var qb=t=>({branch:t.branch,kind:t.kind,repos:(t.repos??[]).filter(Boolean).map(({name:e,base:n,baseRef:r,repo:o})=>({name:e,base:n,baseRef:r,repo:o}))});async function Kb(t,e){let n=await H(),r=Z(n,t);if(!r)throw new Error(`track "${t}" not found`);if(r.archivedAt)throw new Error("track is archived \u2014 unarchive it first");let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track \u2014 a single-repo Track's workspace IS its repo checkout; create the Track with multiple repos tagged instead");let s=await Wb(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await et(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=U.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${U.basename(s)}-${d}`;let l=U.join(o.dir,c),u=await oi(s);await ig(s,o.branch)?await cg(s,l,o.branch):await Vr(s,l,o.branch,u);let f={name:c,dir:l,base:s,baseRef:u,repo:a};return o.repos=[...(o.repos??[]).filter(Boolean),f],await ji(o.dir,o.repos,o.branch),dt(r,"retagged",{action:"add",repo:a??s,name:c,workspace:qb(o)}),await V(),f}async function Gb(t,e){let n=await H(),r=Z(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of ie.values())if(c.trackId===t&&!c.finishedAt)throw new Error("a subtask is still running on this Track \u2014 wait for it to merge back before untagging");let a=await Eu(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await _u(s.dir,s.baseRef).catch(()=>0);if(a||i>0)throw new Error(`"${s.name}" has work (${a?"uncommitted changes":`${i} commit${i===1?"":"s"} ahead of ${s.baseRef??"base"}`}) \u2014 ship or discard it first`);return await te(s.base,s.dir).catch(()=>{}),await F.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await ji(o.dir,o.repos,o.branch),dt(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:qb(o)}),await V(),{removed:s.name}}async function tO(t,e,{repo:n,repos:r,cwd:o}={}){await F.mkdir(Sd,{recursive:!0});let s=U.join(Sd,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return Q$(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!U.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await To(n)??await xc(n));let i=await Hb([n,o,w$]);if(i){let c=`track/${Pd(e)}-${t}`;try{let l=await oi(i);await Vr(i,s,c,l),await Td(s);let u=await et(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await F.mkdir(s,{recursive:!0}),await lg(s),await Td(s),{dir:s,base:null,branch:null,kind:"repo"}}var Es="# bostrat workspace",Vb=`${Es}
|
|
174
175
|
|
|
175
176
|
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
176
177
|
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
@@ -339,9 +340,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
339
340
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
340
341
|
implementation turn this way, every time. When the operator has already asked you
|
|
341
342
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
342
|
-
`,
|
|
343
|
-
`)),
|
|
344
|
-
`);return`${
|
|
343
|
+
`,eO=Vb,Ss=Vb+og;async function Td(t){try{await F.writeFile(U.join(t,"AGENTS.md"),Ss,{flag:"wx"})}catch{return}await oO(t,["/AGENTS.md"])}var kd=t=>typeof t=="string"&&(t===Es||t.startsWith(`${Es}
|
|
344
|
+
`)),nO="# bostrat multi-repo workspace";function rO(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
345
|
+
`);return`${nO}
|
|
345
346
|
|
|
346
347
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
347
348
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -353,25 +354,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
353
354
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
354
355
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
355
356
|
or preview an app, run it from that app's own repo directory.
|
|
356
|
-
`}async function
|
|
357
|
-
`,`${
|
|
357
|
+
`}async function ji(t,e,n){let r=rO(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await F.writeFile(U.join(t,o),r).catch(()=>{})}var xd=Ss.replace(`${Es}
|
|
358
|
+
`,`${Es}
|
|
358
359
|
|
|
359
360
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
360
361
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
361
362
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
362
363
|
When working anywhere else, ignore everything below.
|
|
363
|
-
`);async function
|
|
364
|
+
`);async function zb(t){let{stdout:e}=await m$("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return U.resolve(t,e.trim())}async function oO(t,e){try{let n=await zb(t);await F.mkdir(U.dirname(n),{recursive:!0});let r=await F.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
364
365
|
`).includes(s));o.length&&await F.appendFile(n,(r.endsWith(`
|
|
365
366
|
`)||!r?"":`
|
|
366
367
|
`)+o.join(`
|
|
367
368
|
`)+`
|
|
368
|
-
`)}catch{}}async function
|
|
369
|
+
`)}catch{}}async function sO(t,e){try{let n=await zb(t),r=await F.readFile(n,"utf8"),o=r.split(`
|
|
369
370
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
370
371
|
`).length&&await F.writeFile(n,o.join(`
|
|
371
|
-
`))}catch{}}async function
|
|
372
|
+
`))}catch{}}async function Jb(){let t={refreshed:0,removed:0,codexGlobal:null};try{await F.mkdir(Xe,{recursive:!0}),await F.writeFile(U.join(Xe,"CLAUDE.md"),eO).catch(()=>{}),await F.writeFile(U.join(Xe,"AGENTS.md"),Ss).catch(()=>{});let e=process.env.CODEX_HOME||U.join(process.env.HOME||"/tmp",".codex");if((await F.stat(e).catch(()=>null))?.isDirectory()){let r=U.join(e,"AGENTS.md"),o=await F.readFile(r,"utf8").catch(()=>null);o===null?(await F.writeFile(r,xd,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):kd(o)&&o!==xd&&(await F.writeFile(r,xd).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Sd,co]){let o=await F.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=U.join(r,s.name),i=U.join(a,"AGENTS.md"),c=await F.readFile(i,"utf8").catch(()=>null);c!==null&&kd(c)&&c!==Ss&&(await F.writeFile(i,Ss).catch(()=>{}),t.refreshed++);let l=U.join(a,"CLAUDE.md"),u=await F.readFile(l,"utf8").catch(()=>null);u!==null&&kd(u)&&(await F.rm(l,{force:!0}).catch(()=>{}),await sO(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function aO(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Wt(e))n.base&&n.dir&&await te(n.base,n.dir).catch(()=>{});await F.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await te(e.base,e.dir):await F.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function $d({name:t,cwd:e,repo:n,repos:r,agent:o="claude",skipPermissions:s=!0}){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");let a=await Ni(o,s),i=await H(),c=Rs().slice(0,8),l=await tO(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(w=>w.name))),f=`${t.trim()} lead`;u.has(Fc(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await Fn(f,l.dir,{agentCmd:a,agent:o});Sa({cwd:d.cwd,sessionName:d.name,agent:o,agentCmd:a});let p={id:c,name:t.trim(),cwd:d.cwd,workspace:l,leadBox:d.name,boxes:[sk(d,"lead","live")],events:[],createdAt:Date.now()};return dt(p,"opened",{agent:o,box:d.name,skipPermissions:s,name:p.name,cwd:p.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:m,base:w,baseRef:y,repo:k})=>({name:m,base:w,baseRef:y,repo:k}))}:{}}}),i.tracks.push(p),await V(),Ye(p)}var iO=new Set(["claude","codex","cursor"]);async function Xb({provider:t="claude",fresh:e=!1}={}){if(!iO.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await H()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await Ye(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await uo(r.id).catch(()=>{})}let o=(await Ni(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=Rs().slice(0,8),a=U.join(Xe,"setup",s);await F.mkdir(a,{recursive:!0});let i=await Fn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&Sa({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:[sk(i,"lead","live")],events:[],createdAt:Date.now()};return dt(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await H()).tracks.push(c),await V(),Ye(c)}async function Yb(t,e,n){await ah(t).catch(()=>{});let r=_s.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),_s.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of fo(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await Bd(a).catch(()=>{}),ie.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await H(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await pe(a.name).catch(()=>{})}async function Zb(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await H(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await Um(s),jb(t),await Yb(t,s,"track deleted"),n?await gh(t).catch(()=>{}):await wh(t).catch(()=>{}),await Ng(),await aO(s),e&&await Hm(t).catch(()=>{}),e?await ub(t).catch(()=>{}):await lb(s).catch(()=>{}),dt(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await V(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}var $i=new Set;async function uo(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);return n.archivedAt=Date.now(),jb(t),dt(n,"archived",{boxes:n.boxes.length}),await V(),n.kind==="setup"?(await Qb(t),Ye(n)):($i.add(t),lO(t).catch(()=>{}).finally(()=>$i.delete(t)),{...await Ye(n),archiving:!0})}var cO=9e4;async function lO(t){try{await uO(t)}catch{}await Qb(t).catch(()=>{})}async function Qb(t){let e=await H(),n=Z(e,t);if(!(!n||!n.archivedAt)){await Yb(t,n,"track archived");for(let r of n.boxes)ur.has(r.state)||(r.state="stopped");await V()}}async function uO(t){let e=await H(),n=Z(e,t);if(!n||!n.archivedAt)return;let r=null,o=[],s="derived",a=(n.events||[]).some(l=>l.type==="said"),i=n.leadBox?await tt(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await dm(n.leadBox,i.cwd,{timeoutMs:cO})),s="lead-cli"}catch{}if(r||(r=ql(n,await L(n).catch(()=>[]))),!r)return;let c={text:r,source:s,at:Date.now()};o.length&&(c.items=o),n.beat=c,await R(n,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",boxId:n.leadBox||null,provider:i?.agent||null,payload:o.length?{text:r,items:o,source:s}:{text:r,source:s}}).catch(()=>{}),await V()}async function tk(){let t=await H(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=ql(o,await L(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await R(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await V(),{recorded:e,skipped:n,empty:r}}async function An(t,e){let n=await Pt(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await ek(n);if(r.length){for(let o of r)await Yn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await uo(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function ek(t){return(await Se().catch(()=>[])).filter(n=>n.trackId===t.trackId&&n.state!=="merged"&&n.state!=="closed"&&n.state!=="lost"&&!(n.repo===t.repo&&n.number===t.number))}async function En(t,e){return Od(t,e,"merged")}async function Od(t,e,n="merged"){let r=await Pt(t,e).catch(()=>null);if(!r?.notifyOnMerge||r.archiveOnMerge||!r.trackId)return!1;let o={repo:r.repo??null,number:r.number,title:r.title??null,url:r.url??null,headRef:r.headRef??null,outcome:n},s=await ek(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await rw(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await ce(r.trackId,dO(a),{kind:"merge-notify"}),!0}catch{return!1}}var xb={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function dO(t){let e=i=>`${i.repo?`${i.repo}`:""}#${i.number}${i.title?` "${i.title}"`:""}${i.url?` (${i.url})`:""}`;if(t.length===1){let i=t[0],c=`#${i.number}${i.title?` "${i.title}"`:""}`;return i.outcome!=="merged"?`PR ${c}${i.url?` (${i.url})`:""}${i.headRef?` \u2014 branch ${i.headRef} \u2014`:""} was ${xb[i.outcome]??i.outcome}. This is an automated notification: the work did NOT land \u2014 check whether it still needs to ship another way. If not, say so briefly.`:`PR ${c}${i.url?` (${i.url})`:""}${i.headRef?` \u2014 branch ${i.headRef} \u2014`:""} has merged. This is an automated merge notification: pick up whatever follow-up work this merge unblocks (dependent tasks, cleanup, next steps that were waiting on it). If nothing needs doing, say so briefly.`}let n=t.filter(i=>i.outcome==="merged"),r=t.filter(i=>i.outcome!=="merged"),o=r.length?`${n.length} of the ${t.length} PRs on this Track merged; ${r.length} did not land:`:`All ${t.length} PRs on this Track have merged:`,s=t.map(i=>`- ${e(i)} \u2014 ${xb[i.outcome]??i.outcome}`).join(`
|
|
372
373
|
`),a=n.length?r.length?`This is an automated merge notification: pick up whatever follow-up work the merged PR${n.length===1?"":"s"} unblock${n.length===1?"s":""} \u2014 and note the unmerged one${r.length===1?"":"s"}: that work did NOT land, so check whether it still needs to ship. If nothing needs doing, say so briefly.`:"This is an automated merge notification: pick up whatever follow-up work these merges unblock (dependent tasks, cleanup, next steps that were waiting on them). If nothing needs doing, say so briefly.":"This is an automated notification: NONE of the PRs landed \u2014 the work did not ship. Check whether it still needs to go out another way. If not, say so briefly.";return`${o}
|
|
373
374
|
${s}
|
|
374
|
-
${a}`}async function kd(t){if(t.archivedAt)return!1;let e=t.boxes.find(o=>o.name===t.leadBox);if(!e||cr.has(e.state)||await rt(e.name))return!1;let n=lb(e.agent,e.sessionId),r=await ia(n);if(r===gr)return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" is not installed on this box`}),!1;r===yr&&console.warn(`[track] ${e.name}: could not confirm "${e.agent}" on PATH \u2014 relaunching anyway (launch verifier will catch a real miss)`);try{let o=await Bn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=go(o.hookToken),wa({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",dt(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(o){return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(o?.message||o)}),!1}}async function Ub(t){let e=await H(),n=tt(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,vi.delete(t),dt(n,"unarchived",{}),await kd(n),await X(),Ye(n)}async function Hb(){let t=await H(),e=new Set((await Oe()).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 pe(a.name).catch(()=>{}),dt(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!cr.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await X(),r}async function Wb(t){let e=await H(),n=tt(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(cr.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await rt(n.leadBox)){let a=await mt(n.leadBox,{lines:_t}).catch(()=>"");if(ma(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await pe(n.leadBox),r.state="stopped"}let s=await kd(n);if(await X(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ye(n)}var qb=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:go(t.hookToken),createdAt:Date.now()});function Kb(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 Ss=new Map,_s=t=>Ss.get(t)||(Ee?tt(Ee,t)?.handoff:null)||null;async function Ti(t,e){let n=await H(),r=tt(n,t);r&&(r.handoff={...r.handoff||{},...e},await X())}function W$(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 Gb(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=W$(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await rt(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(),f=Kb(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:Ii,at:u},dt(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await pe(i).catch(()=>{}),a&&await pe(a).catch(()=>{})}return e&&await X(),e}async function xd(t,e,n=!0,r=null){let o=Ss.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=tt(await H(),t);if(!s)throw new Error(`track "${t}" not found`);$i(t,"handoff");let a=await Ei(e,n),i=as(e);if(i){let u=new Error(`can't hand off to ${e} \u2014 its ${i.window} spend limit is reached ($${i.spentUsd.toFixed(2)} of $${i.limitUsd.toFixed(2)})`);throw u.usageCap=i,u}let c=await rt(s.leadBox);if(!c)throw new Error(`lead box "${s.leadBox}" is not running`);let l={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:ub(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return Ss.set(t,l),await Ti(t,{step:l.step,fromBox:l.fromBox,toAgent:e,toBox:l.toBox,skipPermissions:n,startedAt:l.startedAt,finishedAt:null,error:null}),q$(l,s,c,a,e).catch(async u=>{l.error=u?.message||String(u),l.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${l.step}": ${l.error}`),await Ti(t,{step:l.step,error:l.error,finishedAt:l.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:l.step,error:l.error,fromBox:l.fromBox,toAgent:l.toAgent,toBox:l.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:l.fromBox,toAgent:e,cwd:c.cwd}).catch(()=>{}),l}async function q$(t,e,n,r,o){let s=async(E,O={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await Ti(t.trackId,{step:E,toBox:t.toBox}),await G({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:O.sessionName,...O})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=tt(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await cm(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await Bl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await ln({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:As(o)});let d=await mt(t.fromBox,{lines:_t}).catch(()=>"");if(!ha(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=lb(c,l,{skipPermissions:t.skipPermissions});await Bl(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let O=await H(),nt=tt(O,t.trackId);if(nt){let R=nt.boxes.find(C=>C.name===t.fromBox);R&&(R.state="live"),dt(nt,"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 X()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),m=tt(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=m.boxes.find(E=>E.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Kb(w),S=b[b.length-1];S.endedAt=y,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",m.pendingHandoffHydrate={artifact:Ii,at:y},dt(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await X(),await s("done",{sessionName:t.fromBox}),await Ti(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var K$=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,G$=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,ro=U.join(Xe,"worktrees"),Qr=[ys,Si],ce=new Map;var ao=t=>{let e=new Map([...ce.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Ee?tt(Ee,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)},Sd=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function V$(t,e,n){let r=await H(),o=tt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await X())}var oo=t=>V$(t.trackId,t.id,Sd(t));async function to(t,e){t.step=e,await oo(t)}function z$(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 J$(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function X$(t,e,n){if(!J$(n)||ce.has(e)||!await rt(n.box).catch(()=>null))return!1;try{await F.access(n.dir)}catch{return!1}let o=await rt(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return ce.set(e,s),Yb(s,o).catch(a=>Jb(s,a)),!0}async function Vb(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(ce.has(r))continue;if(await X$(n,r,o)){e=!0,dt(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=z$(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!cr.has(i.state)&&(i.state="canceled"),dt(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await ib(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||cr.has(r.state)||ce.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?U.join(ro,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await ib(n,r.name,o)}}return e=await Y$(t)||e,e&&await X(),e}async function ib(t,e,n){if(e&&await pe(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of qt(t.workspace)){let s=U.join(n,o.name);await ee(o.base,s).then(()=>!0,()=>!1)||await ee(o.dir,s).catch(()=>{})}await F.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await rt(t.leadBox).catch(()=>null);r?.cwd&&await ee(r.cwd,n).catch(()=>{})}async function Y$(t){let e;try{e=await F.readdir(ro)}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 ce.values())o.dir&&n.add(U.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=U.join(ro,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of qt(a.workspace))await ee(i.base,U.join(s,i.name)).catch(()=>{});await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await rt(a.leadBox).catch(()=>null):null;i?.cwd?await ee(i.cwd,s).catch(()=>{}):await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var vd=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",vs=class extends Error{};async function zb(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Hr(n))throw new Error("task (string) required");let s=await Ei(e,r),a=as(e);if(a){let p=new Error(`can't delegate to ${e} \u2014 its ${a.window} spend limit is reached ($${a.spentUsd.toFixed(2)} of $${a.limitUsd.toFixed(2)})`);throw p.usageCap=a,p}let i=tt(await H(),t);if(!i)throw new Error(`track "${t}" not found`);let c=await rt(i.leadBox);if(!c)throw new Error(`lead box "${i.leadBox}" is not running`);if(!c.cwd)throw new Error("can't resolve the lead's working directory");let l=i.workspace?.kind==="multi",u=null;if(l){let p=qt(i.workspace);if(!p.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of p)if(!m?.dir||!await Za(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);u=p.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await Za(c.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${c.cwd})`);let f=Ts().slice(0,8),d={id:f,trackId:t,trackName:i.name,agent:e,task:Hr(n),skipPermissions:r,reason:ub(o),step:"snapshot",leadBox:i.leadBox,box:null,branch:`bostrat/${vd(i.name)}-${f}`,base:null,repos:u,dir:U.join(ro,`${t}-${f}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return ce.set(f,d),await oo(d),Z$(d,c,s).catch(p=>Jb(d,p)),d}async function Jb(t,e){t.failedStep=t.step,t.error=e instanceof vs?"canceled":e?.message||String(e),t.step=e instanceof vs?"canceled":"error",t.finishedAt=Date.now(),await Ad(t).catch(()=>{}),await oo(t).catch(()=>{}),await nO(t).catch(()=>{})}function Td(t){let e=ce.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 Xb(t){let e=ce.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 Z$(t,e,n){if(await to(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await ss(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await oo(t)}else t.base=await ss(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await to(t,"spawn"),await F.mkdir(ro,{recursive:!0}),t.repos){await F.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Wr(a.leadDir,U.join(t.dir,a.name),t.branch,a.base);let s=qt((tt(await H(),t.trackId)||{}).workspace);await Oi(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await Wr(e.cwd,t.dir,t.branch,t.base),await fd(t.dir);await Q$(t,e);let r=t.trackName.slice(0,38).trim(),o=await Bn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await tO(t,o),await db(t.trackId,o.name,YR(t.task,!!t.repos),{kind:"subtask"}),await Yb(t,e)}async function Yb(t,e){await to(t,"working");let n=U.join(t.dir,Si);if(await oO(n,t),t.canceled)throw new vs;if(await to(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=U.join(t.dir,c.name);await wu(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await ku(c.leadDir,c.base,t.branch,Qr),c.files=await xu(c.leadDir,c.base,t.branch,Qr);let u=await Su(c.leadDir,c.base,t.branch,Qr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>U.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:U.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await wu(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await ku(e.cwd,t.base,t.branch,Qr),t.files=await xu(e.cwd,t.base,t.branch,Qr);let o=await Su(e.cwd,t.base,t.branch,Qr);t.applied=o.applied,t.conflicts=o.conflicts}await oo(t);let r=await rO(t,e,n);await to(t,"resume"),await db(t.trackId,t.leadBox,ZR(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await to(t,"reap"),await Ad(t),t.step="done",t.finishedAt=Date.now(),await eO(t),await oo(t)}async function Q$(t,e){let n=U.join(t.dir,U.dirname(ys));await F.mkdir(n,{recursive:!0});let r="";try{r=await gp(e.cwd,e.sessionId)}catch{}if(!r)try{r=await F.readFile(U.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
375
|
+
${a}`}async function Cd(t){if(t.archivedAt)return!1;let e=t.boxes.find(o=>o.name===t.leadBox);if(!e||ur.has(e.state)||await tt(e.name))return!1;let n=Tb(e.agent,e.sessionId),r=await pa(n);if(r===kr)return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" is not installed on this box`}),!1;r===xr&&console.warn(`[track] ${e.name}: could not confirm "${e.agent}" on PATH \u2014 relaunching anyway (launch verifier will catch a real miss)`);try{let o=await Fn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=So(o.hookToken),Sa({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",dt(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(o){return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(o?.message||o)}),!1}}async function nk(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,$i.delete(t),dt(n,"unarchived",{}),await Cd(n),await V(),Ye(n)}async function rk(){let t=await H(),e=new Set((await $e()).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 pe(a.name).catch(()=>{}),dt(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!ur.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function ok(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(ur.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await tt(n.leadBox)){let a=await mt(n.leadBox,{lines:At}).catch(()=>"");if(ka(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await pe(n.leadBox),r.state="stopped"}let s=await Cd(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ye(n)}var sk=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:So(t.hookToken),createdAt:Date.now()});function ak(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 _s=new Map,Os=t=>_s.get(t)||(Ee?Z(Ee,t)?.handoff:null)||null;async function Oi(t,e){let n=await H(),r=Z(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function fO(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 ik(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=fO(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await tt(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(),f=ak(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:Pi,at:u},dt(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.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 q({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 q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await pe(i).catch(()=>{}),a&&await pe(a).catch(()=>{})}return e&&await V(),e}async function Md(t,e,n=!0,r=null){let o=_s.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);Bi(t,"handoff");let a=await Ni(e,n),i=us(e);if(i){let u=new Error(`can't hand off to ${e} \u2014 its ${i.window} spend limit is reached ($${i.spentUsd.toFixed(2)} of $${i.limitUsd.toFixed(2)})`);throw u.usageCap=i,u}let c=await tt(s.leadBox);if(!c)throw new Error(`lead box "${s.leadBox}" is not running`);let l={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Ab(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return _s.set(t,l),await Oi(t,{step:l.step,fromBox:l.fromBox,toAgent:e,toBox:l.toBox,skipPermissions:n,startedAt:l.startedAt,finishedAt:null,error:null}),pO(l,s,c,a,e).catch(async u=>{l.error=u?.message||String(u),l.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${l.step}": ${l.error}`),await Oi(t,{step:l.step,error:l.error,finishedAt:l.finishedAt}).catch(()=>{}),q({flow:"lead-handoff",phase:"failed",trackId:t,step:l.step,error:l.error,fromBox:l.fromBox,toAgent:l.toAgent,toBox:l.toBox}).catch(()=>{})}),q({flow:"lead-handoff",phase:"started",trackId:t,fromBox:l.fromBox,toAgent:e,cwd:c.cwd}).catch(()=>{}),l}async function pO(t,e,n,r,o){let s=async(_,$={})=>{t.step=_,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${_}`),await Oi(t.trackId,{step:_,toBox:t.toBox}),await q({flow:"lead-handoff",phase:_,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:$.sessionName,...$})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=Z(a,t.trackId)?.boxes.find(_=>_.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 ym(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await Gl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await un({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:$s(o)});let d=await mt(t.fromBox,{lines:At}).catch(()=>"");if(!xa(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let _=Tb(c,l,{skipPermissions:t.skipPermissions});await Gl(t.fromBox,{agentCmd:_,agent:c,resumeSessionId:l,pinned:u});let $=await H(),z=Z($,t.trackId);if(z){let O=z.boxes.find(P=>P.name===t.fromBox);O&&(O.state="live"),dt(z,"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 V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),m=Z(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=m.boxes.find(_=>_.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),k=ak(w),S=k[k.length-1];S.endedAt=y,t.reason&&(S.endReason=t.reason),k.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",m.pendingHandoffHydrate={artifact:Pi,at:y},dt(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:k.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await Oi(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var mO=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,hO=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,co=U.join(Xe,"worktrees"),oo=[vs,Ri],ie=new Map;var fo=t=>{let e=new Map([...ie.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Ee?Z(Ee,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)},Nd=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function wO(t,e,n){let r=await H(),o=Z(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var lo=t=>wO(t.trackId,t.id,Nd(t));async function so(t,e){t.step=e,await lo(t)}function gO(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 yO(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function bO(t,e,n){if(!yO(n)||ie.has(e)||!await tt(n.box).catch(()=>null))return!1;try{await F.access(n.dir)}catch{return!1}let o=await tt(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return ie.set(e,s),fk(s,o).catch(a=>uk(s,a)),!0}async function ck(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(ie.has(r))continue;if(await bO(n,r,o)){e=!0,dt(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await q({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=gO(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!ur.has(i.state)&&(i.state="canceled"),dt(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 q({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Sb(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||ur.has(r.state)||ie.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?U.join(co,`${n.id}-${r.subtaskId}`):null;await q({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Sb(n,r.name,o)}}return e=await kO(t)||e,e&&await V(),e}async function Sb(t,e,n){if(e&&await pe(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Wt(t.workspace)){let s=U.join(n,o.name);await te(o.base,s).then(()=>!0,()=>!1)||await te(o.dir,s).catch(()=>{})}await F.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await tt(t.leadBox).catch(()=>null);r?.cwd&&await te(r.cwd,n).catch(()=>{})}async function kO(t){let e;try{e=await F.readdir(co)}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 ie.values())o.dir&&n.add(U.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=U.join(co,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Wt(a.workspace))await te(i.base,U.join(s,i.name)).catch(()=>{});await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await tt(a.leadBox).catch(()=>null):null;i?.cwd?await te(i.cwd,s).catch(()=>{}):await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await q({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Pd=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Is=class extends Error{};async function lk(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Gr(n))throw new Error("task (string) required");let s=await Ni(e,r),a=us(e);if(a){let p=new Error(`can't delegate to ${e} \u2014 its ${a.window} spend limit is reached ($${a.spentUsd.toFixed(2)} of $${a.limitUsd.toFixed(2)})`);throw p.usageCap=a,p}let i=Z(await H(),t);if(!i)throw new Error(`track "${t}" not found`);let c=await tt(i.leadBox);if(!c)throw new Error(`lead box "${i.leadBox}" is not running`);if(!c.cwd)throw new Error("can't resolve the lead's working directory");let l=i.workspace?.kind==="multi",u=null;if(l){let p=Wt(i.workspace);if(!p.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of p)if(!m?.dir||!await ri(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);u=p.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await ri(c.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${c.cwd})`);let f=Rs().slice(0,8),d={id:f,trackId:t,trackName:i.name,agent:e,task:Gr(n),skipPermissions:r,reason:Ab(o),step:"snapshot",leadBox:i.leadBox,box:null,branch:`bostrat/${Pd(i.name)}-${f}`,base:null,repos:u,dir:U.join(co,`${t}-${f}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return ie.set(f,d),await lo(d),xO(d,c,s).catch(p=>uk(d,p)),d}async function uk(t,e){t.failedStep=t.step,t.error=e instanceof Is?"canceled":e?.message||String(e),t.step=e instanceof Is?"canceled":"error",t.finishedAt=Date.now(),await Bd(t).catch(()=>{}),await lo(t).catch(()=>{}),await AO(t).catch(()=>{})}function Dd(t){let e=ie.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 dk(t){let e=ie.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 xO(t,e,n){if(await so(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await ls(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await lo(t)}else t.base=await ls(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await so(t,"spawn"),await F.mkdir(co,{recursive:!0}),t.repos){await F.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Vr(a.leadDir,U.join(t.dir,a.name),t.branch,a.base);let s=Wt((Z(await H(),t.trackId)||{}).workspace);await ji(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await Vr(e.cwd,t.dir,t.branch,t.base),await Td(t.dir);await SO(t,e);let r=t.trackName.slice(0,38).trim(),o=await Fn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await vO(t,o),await Eb(t.trackId,o.name,k$(t.task,!!t.repos),{kind:"subtask"}),await fk(t,e)}async function fk(t,e){await so(t,"working");let n=U.join(t.dir,Ri);if(await _O(n,t),t.canceled)throw new Is;if(await so(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=U.join(t.dir,c.name);await Au(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Ru(c.leadDir,c.base,t.branch,oo),c.files=await $u(c.leadDir,c.base,t.branch,oo);let u=await Ou(c.leadDir,c.base,t.branch,oo);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>U.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:U.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await Au(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Ru(e.cwd,t.base,t.branch,oo),t.files=await $u(e.cwd,t.base,t.branch,oo);let o=await Ou(e.cwd,t.base,t.branch,oo);t.applied=o.applied,t.conflicts=o.conflicts}await lo(t);let r=await EO(t,e,n);await so(t,"resume"),await Eb(t.trackId,t.leadBox,x$(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await so(t,"reap"),await Bd(t),t.step="done",t.finishedAt=Date.now(),await TO(t),await lo(t)}async function SO(t,e){let n=U.join(t.dir,U.dirname(vs));await F.mkdir(n,{recursive:!0});let r="";try{r=await _p(e.cwd,e.sessionId)}catch{}if(!r)try{r=await F.readFile(U.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
375
376
|
|
|
376
377
|
**Delegated task:** ${t.task}
|
|
377
378
|
|
|
@@ -382,50 +383,50 @@ ${a}`}async function kd(t){if(t.archivedAt)return!1;let e=t.boxes.find(o=>o.name
|
|
|
382
383
|
## Conversation so far (lead \u2192 you)
|
|
383
384
|
|
|
384
385
|
${r}
|
|
385
|
-
`;await F.writeFile(U.join(t.dir,
|
|
386
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
386
|
+
`;await F.writeFile(U.join(t.dir,vs),o)}async function vO(t,e){let n=await H(),r=Z(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:So(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),dt(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]||{},...Nd(t)},await V())}async function TO(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),dt(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 V()}async function AO(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),dt(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]||{},...Nd(t)},await V()}async function EO(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=U.join(e.cwd,r);await F.mkdir(U.dirname(o),{recursive:!0}),await F.copyFile(n,o)}catch{}return r}async function Bd(t){if(t.box&&await pe(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await te(e.leadDir,U.join(t.dir,e.name)).catch(()=>{});await F.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await tt(t.leadBox).catch(()=>null))?.cwd;n&&await te(n,t.dir).catch(()=>{})}}async function _O(t,e){let n=(e.startedAt||Date.now())+mO;for(;;){if(e.canceled||e.forceMerge)return;try{await F.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Rn(hO)}}function Rn(t){return new Promise(e=>setTimeout(e,t))}import Cs from"node:fs/promises";import Ui from"node:path";import CO from"node:os";import IO from"ws";var pk=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function Li(t,e,n={}){let r=await fetch(`${pk()}/${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 jd=(t,e)=>Li("chat.postMessage",t,e),po=(t,e)=>Li("chat.update",t,e);async function qt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function RO(t,e,n={}){let r=await fetch(`${pk()}/${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 mk(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await RO("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 Li("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var $O=2200;function hk({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 Li("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new IO(l),o.on("message",u=>{let f;try{f=JSON.parse(u)}catch{return}if(f.type==="hello"){a=1e3,n("socket connected");return}if(f.type==="disconnect"){try{o.close()}catch{}return}if(!f.envelope_id)return;let d=w=>{try{o.send(JSON.stringify(w?{envelope_id:f.envelope_id,payload:w}:{envelope_id:f.envelope_id}))}catch{}},p=!1,m=setTimeout(()=>{p=!0,d()},$O);Promise.resolve().then(()=>e(f)).then(w=>{clearTimeout(m),!p&&(p=!0,d(f.accepts_response_payload?w:void 0))},w=>{clearTimeout(m),p||(p=!0,d()),n(`envelope handler failed: ${w?.message||w}`)})}),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 yk=()=>process.env.BOSTRAT_STATE_DIR||Ui.join(process.env.HOME||CO.tmpdir(),".bostrat"),Wd=()=>Ui.join(yk(),"slack.json"),Fi=null,Ld=null,Fd=Promise.resolve();async function Kd(){return Fi||(Ld??=Cs.readFile(Wd(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>Fi=t).finally(()=>Ld=null),Ld)}async function wk(){let t=JSON.stringify({threads:Fi},null,2),e=`${Wd()}.${process.pid}.tmp`;await Cs.mkdir(yk(),{recursive:!0}),await Cs.writeFile(e,t),await Cs.rename(e,Wd())}async function Ze(){Fd=Fd.then(wk,wk),await Fd.catch(()=>{})}async function Gd(t){return(await Kd())[t]??null}async function MO(t,e){let n=await Kd();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var Y=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function Ms(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function NO(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Ms(n.replace(/\n+/g," "),e)}function bk(t){let e=`*${Y(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${Y(t.repoLabel)} \xB7 \`${Y(t.branch||"?")}\` \xB7 lead *${Y(t.lead)}*`,r=t.kickoff?`> ${Y(Ms(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
387
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Ka(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function qd(t,e){await po(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:bk(e)})}async function fr(t,e,n,r=void 0){return jd(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var gk=new Set;function dr(t){gk.add(t),t.catch(()=>{}).finally(()=>gk.delete(t))}var pt=t=>({response_type:"ephemeral",text:t}),PO="*/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._",DO=["claude","cursor","codex"];function Ud(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 Hd=(t,e)=>pt(`No live Track matches \u201C${Y(e)}\u201D. Live: ${t.map(n=>`\`${Y(n.name)}\``).join(" \xB7 ")||"none"}`);async function kk(t,e,{name:n,repo:r,responseUrl:o}){let s=t.deps;try{let a=(await s.listTracks()).filter(f=>!f.archivedAt);if(a.length>=e.maxConcurrent){await qt(o,{...pt(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await qt(o,{...pt("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?Ui.basename(c.base):"fresh workspace"),lead:"claude"},u=await jd(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:bk(l),unfurl_links:!1});l.ts=u.ts,(await Kd())[i.id]=l,await Ze(),await qt(o,{...pt(`\u2713 *${Y(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await qt(o,{...pt(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function BO(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await Gd(n.id);s&&(s.archivedAt=Date.now(),await Ze(),await qd(e,s).catch(()=>{})),await qt(r,{...pt(`\u2713 *${Y(n.name)}* archived.`),replace_original:!0})}catch(s){await qt(r,{...pt(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function jO(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await qt(o,{...pt(`\u2713 Handing *${Y(n.name)}* to ${Y(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await qt(o,{...pt(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function LO(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 qt(r,{...pt(`\u2713 Stopped *${Y(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 qt(r,{...pt(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function FO(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Ms(`${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 *${Y(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function UO(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?Ui.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 *${Y(s.name)}* \u2014 ${Y(i)}${a.branch?` \`${Y(a.branch)}\``:""} \xB7 lead ${Y(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
387
388
|
`)+(o?`
|
|
388
|
-
_${o} archived_`:"")}async function
|
|
389
|
-
\u2713 *${
|
|
390
|
-
\u2713 _answered_`}}]})}async function
|
|
391
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
392
|
-
`)){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 DO(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 BO(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Me(e.cwd,e.sessionId):e?.cwd?Pc(e.cwd):null}async function ik(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 I(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 jO(r,o,t,i);break;default:break}return a}async function jO(t,e,n,r){let o=[],s=BO(n,e);if(s)try{o=PO(await NO.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=DO(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:me(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.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 UO from"node:fs";import Pd from"node:fs/promises";import HO from"node:http";import co from"node:path";import Di from"node:os";import{createRequire as WO}from"node:module";import Dd from"ws";import LO from"node:path";import FO from"node:os";var Rs=process.env.BOSTRAT_STATE_DIR||LO.join(process.env.HOME||FO.tmpdir(),".bostrat");function ck(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function lk(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var Pi=(()=>{try{return WO(import.meta.url)("../package.json").version||null}catch{return null}})(),qO=process.env.BOSTRAT_STATE_DIR||co.join(process.env.HOME||Di.tmpdir(),".bostrat");function KO(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 GO({home:t=process.env.HOME||Di.homedir(),env:e=process.env}={}){let n=r=>{try{return UO.statSync(r),!0}catch{return!1}};return{claude:n(co.join(t,".claude",".credentials.json")),codex:n(co.join(e.CODEX_HOME||co.join(t,".codex"),"auth.json"))}}var Rt=1,uk=1e3,VO=3e4,zO=6e4,JO=1500,XO=500,dk=100,YO=2e3,ZO=25e3,QO=6e4;function fk({url:t,token:e,nodeId:n=Di.hostname(),name:r=Di.hostname(),listTracks:o,detectAgents:s=null,sampleMetrics:a=null,metricsMs:i=QO,selfUrl:c=`http://127.0.0.1:${process.env.PORT||3362}`,log:l=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=lk(t);let u=c.replace(/^http/,"ws"),f=new Map,d=new Map,p=co.join(qO,"plane-link","cursor.json"),m={},w=new Map,y=null,b=!1,S=!1,E=uk,O=null,nt=null,R=null,C=null,v=null,D=!1;async function B(){try{m=JSON.parse(await Pd.readFile(p,"utf8"))}catch{m={}}}function lt(){D=!0,!R&&(R=setTimeout(async()=>{if(R=null,!!D){D=!1;try{await Pd.mkdir(co.dirname(p),{recursive:!0}),await Pd.writeFile(p,JSON.stringify(m))}catch(K){l.error("[plane-link] cursor flush failed:",K?.message||K)}}},YO),R.unref?.())}function q(K){return!y||y.readyState!==Dd.OPEN?!1:(y.send(JSON.stringify(K)),!0)}function T(K){for(let{trackId:Y,event:$}of K)w.set($.id,{trackId:Y,seq:$.seq});return q({v:Rt,ch:"events",type:"append",events:K})}async function g(){if(!a)return;let K;try{K=await a()}catch(Y){l.error("[plane-link] metrics sample failed:",Y?.message||Y);return}K&&q({v:Rt,ch:"ctl",type:"metrics",metrics:K})}function h(){a&&(v&&clearInterval(v),g(),v=setInterval(g,i),v.unref?.())}async function A(){let K;try{K=await o()}catch(Y){l.error("[plane-link] backfill listTracks failed:",Y?.message||Y);return}q({v:Rt,ch:"ctl",type:"tracks",ids:K.map(Y=>Y.id).filter(Y=>typeof Y=="string")});for(let Y of K){if(!b)return;let $;try{$=await L(Y)}catch{continue}let z=m[Y.id]??0,xt=$.filter(ht=>ht.seq>z&&typeof ht.id=="string");for(let ht=0;ht<xt.length;ht+=dk){let k=xt.slice(ht,ht+dk).map(P=>({trackId:Y.id,event:P}));if(!T(k))return}}}function _(K){!b||!K?.trackId||typeof K.id!="string"||T([{trackId:K.trackId,event:K}])}let J=new Map;async function W(K){let{reqId:Y,method:$,path:z,headers:xt={},body:ht}=K,k=Object.fromEntries(Object.entries(xt).filter(([,$t])=>$t!=null)),P=new AbortController;J.set(Y,P);try{if(K.host){await it(K,k,P);return}let $t=await fetch(c+z,{method:$,headers:k,body:ht?Buffer.from(ht,"base64"):void 0,signal:P.signal}),Qt=Object.fromEntries($t.headers);for(let Tt of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Qt[Tt];if(Qt["content-type"]||(Qt["content-type"]="application/octet-stream"),q({v:Rt,ch:"control",type:"http_head",reqId:Y,status:$t.status,headers:Qt}),$t.body){for await(let Tt of $t.body)if(!q({v:Rt,ch:"control",type:"http_data",reqId:Y,data:Buffer.from(Tt).toString("base64")}))break}q({v:Rt,ch:"control",type:"http_end",reqId:Y,error:null})}finally{J.delete(Y)}}function it(K,Y,$){let{reqId:z,method:xt,path:ht,body:k}=K,P=new URL(c);return new Promise($t=>{let Qt=HO.request({host:P.hostname,port:P.port,method:xt,path:ht,headers:{...Y,host:K.host},signal:$.signal},Tt=>{let{connection:cf,"keep-alive":KN,"transfer-encoding":GN,upgrade:VN,...Z0}=Tt.headers;q({v:Rt,ch:"control",type:"http_head",reqId:z,status:Tt.statusCode||502,headers:Z0}),Tt.on("data",Q0=>{q({v:Rt,ch:"control",type:"http_data",reqId:z,data:Buffer.from(Q0).toString("base64")})||Tt.destroy()}),Tt.on("end",()=>{q({v:Rt,ch:"control",type:"http_end",reqId:z,error:null}),$t()}),Tt.on("error",()=>{q({v:Rt,ch:"control",type:"http_end",reqId:z,error:"view upstream error"}),$t()})});Qt.on("error",Tt=>{q({v:Rt,ch:"control",type:"http_end",reqId:z,error:Tt?.message||"view request failed"}),$t()}),k&&Qt.write(Buffer.from(k,"base64")),Qt.end()})}function Pt(){if(S)return;y=new Dd(t,{headers:{authorization:`Bearer ${e}`}}),y.on("open",async()=>{let Y=null;try{Y=await s?.()||null}catch{Y=null}q({v:Rt,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Pi,providers:GO(),...Y?{agents:Y}:{}})}),y.on("message",Y=>{let $;try{$=JSON.parse(Y)}catch{return}if($.ch==="ctl"&&$.type==="hello_ok"){b=!0,E=uk,O=null,l.log(`[plane-link] connected to plane as ${n}`),$.minNodeVersion&&Pi&&KO(Pi,$.minNodeVersion)&&l.error(`[plane-link] this node is bostrat-node ${Pi}; the plane asks for \u2265${$.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),C&&clearInterval(C),C=setInterval(()=>q({v:Rt,ch:"ctl",type:"ping"}),ZO),C.unref?.(),h(),A().catch(z=>l.error("[plane-link] backfill failed:",z?.message||z));return}if($.ch==="ctl"&&$.type==="usage_caps"){ni($.caps??{},{source:"plane",updatedAt:$.updatedAt??Date.now()}).then(z=>l.log(`[plane-link] spend caps ${Object.keys(z.caps).length?`updated (${Object.keys(z.caps).join(", ")})`:"cleared"} from plane`)).catch(z=>l.error("[plane-link] spend-caps push rejected:",z?.message||z));return}if($.ch==="events"&&$.type==="ack"&&Array.isArray($.ids)){for(let z of $.ids){let xt=w.get(z);xt&&(w.delete(z),(m[xt.trackId]??0)<xt.seq&&(m[xt.trackId]=xt.seq))}lt();return}if($.ch==="control"&&$.type==="http_open"){W($).catch(z=>q({v:Rt,ch:"control",type:"http_end",reqId:$.reqId,error:z?.message||"tunnel error"}));return}if($.ch==="control"&&$.type==="http_cancel"){J.get($.reqId)?.abort();return}if($.ch==="attach"&&$.type==="open"){let z=new Dd(u+$.path,Array.isArray($.protocols)?$.protocols:[],$.host?{headers:{host:$.host}}:void 0);f.set($.streamId,z),d.set($.streamId,[]),z.on("open",()=>{let ht=d.get($.streamId)||[];d.delete($.streamId);for(let{buf:k,bin:P}of ht)z.send(k,{binary:P})}),z.on("message",(ht,k)=>{let P=k?ht:Buffer.from(String(ht));q({v:Rt,ch:"attach",type:"data",streamId:$.streamId,data:P.toString("base64"),...k?{bin:!0}:{}})});let xt=()=>{d.delete($.streamId),f.delete($.streamId)&&q({v:Rt,ch:"attach",type:"close",streamId:$.streamId})};z.on("close",xt),z.on("error",xt);return}if($.ch==="attach"&&$.type==="data"){let z=f.get($.streamId);if(!z)return;let xt=Buffer.from($.data,"base64"),ht=$.bin===!0,k=d.get($.streamId);k?k.push({buf:xt,bin:ht}):z.send(xt,{binary:ht});return}if($.ch==="attach"&&$.type==="close"){let z=f.get($.streamId);f.delete($.streamId),d.delete($.streamId),z?.close();return}});let K=()=>{b&&l.log("[plane-link] disconnected from plane"),b=!1,C&&(clearInterval(C),C=null),v&&(clearInterval(v),v=null),w.clear();for(let z of f.values())z.close();f.clear(),d.clear();for(let z of J.values())z.abort();if(J.clear(),S||nt)return;O==null&&(O=Date.now());let Y=Date.now()-O<zO,$=Y?JO+Math.random()*XO:E;nt=setTimeout(()=>{nt=null,Pt()},$),nt.unref?.(),Y||(E=Math.min(E*2,VO))};y.on("close",K),y.on("error",K)}return Ut.on("event",_),B().then(Pt),{stop:()=>{S=!0,Ut.off("event",_),nt&&clearTimeout(nt),R&&clearTimeout(R),C&&clearInterval(C),v&&clearInterval(v),q({v:Rt,ch:"ctl",type:"bye",reason:"shutdown"}),y?.close()},status:()=>({connected:b,nodeId:n,cursor:{...m},inflight:w.size})}}import tM from"node:fs";import pk from"node:fs/promises";import $s from"node:os";import eM from"node:path";var nM=1024*1024,Bi=t=>Math.round(t/nM);async function rM(t){try{let e=await pk.statfs(t),n=e.blocks*e.bsize,r=n-e.bavail*e.bsize;return{diskUsedMb:Bi(r),diskTotalMb:Bi(n)}}catch{return{diskUsedMb:null,diskTotalMb:null}}}async function oM(){try{return(await Oe()).filter(e=>e.isBostrat).length}catch{return null}}async function sM(){let t=0,e=!1;for(let n of["workspaces","worktrees"])try{let r=await pk.readdir(eM.join(Rs,n),{withFileTypes:!0});t+=r.filter(o=>o.isDirectory()).length,e=!0}catch{}return e?t:null}async function mk({stateDir:t=Rs,now:e=Date.now}={}){let n=$s.totalmem(),[r,o,s,a]=await Promise.all([_a().catch(()=>null),rM(tM.existsSync(t)?t:$s.tmpdir()),oM(),sM()]),i=$s.cpus()?.length||null,c=$s.loadavg()?.[0]??null;return{ts:e(),loadavg1:c!=null?Math.round(c*100)/100:null,cpus:i,cpuPct:c!=null&&i?Math.min(100,Math.round(c/i*100)):null,memUsedMb:r!=null?Bi(n-r):null,memTotalMb:Bi(n),...o,boxes:s,worktrees:a,procUptimeS:Math.round(process.uptime()),osUptimeS:Math.round($s.uptime())}}function jd(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 hk=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),wk=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Bd=t=>Math.round(t*1e4)/1e4;async function gk(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=hk(),u=0,f=[];for(let d of t||[]){let p=await L(d),m=he(p,{boxLabels:jd(d)});if(m.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let w of m.unpricedModels||[])c.add(w);wk(l,m.totalTokens);for(let[w,y]of Object.entries(m.byModel))n[w]||={usd:0,events:0,tokens:hk()},n[w].usd=Bd(n[w].usd+y.usd),n[w].events+=y.events,wk(n[w].tokens,y.tokens);for(let[w,y]of Object.entries(m.byProvider))r[w]||={usd:0,events:0},r[w].usd=Bd(r[w].usd+y.usd),r[w].events+=y.events;e.push({id:d.id,name:d.name,usd:m.totalUsd,rollup:m})}return e.sort((d,p)=>p.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Bd(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var Os=t=>`$${t.toFixed(4)}`,lo=(t,e)=>String(t).padEnd(e);function yk(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 ${Os(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(` ${lo(a,26)} ${lo(Os(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(` ${lo(a.name||a.id,30)} ${Os(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${lo(c.label,40)} ${lo(Os(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${lo(Os(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
393
|
-
`)}import uM from"node:fs/promises";import dM from"node:path";var ji=t=>t.payload?.ts??t.ts,aM=12e4,In=t=>Math.round(t*1e4)/1e4,iM=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function cM(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>ji(S)>=e,a=vr([No(n?.name),No(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 E=vr(S.payload.task);E&&i.set(S.boxId,E)}let c=new Map,l=S=>{let E=S||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},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}),f=[...r].sort((S,E)=>ji(S)-ji(E)),d=0,p=0,m=0,w={turns:0};for(let S of f){let E=l(S.boxId),O=E.segments[E.segments.length-1],nt=ji(S);switch(E.lastTs!=null&&nt>E.lastTs&&s(S)&&(O.activeMs+=Math.min(nt-E.lastTs,aM)),E.lastTs=nt,S.provider&&(E.provider=S.provider),S.type){case"turn.user":{let R=S.payload?.text;if(S.source==="hook"||!String(R||"").trim())break;let C=u(vr(R));E.segments.push(C),s(S)&&(C.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let R=S.payload?.tool;R&&(O.tools[R]=(O.tools[R]||0)+1),s(S)&&(O.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let R=S.payload||{},C={input:R.tokens?.input||0,output:R.tokens?.output||0,cacheRead:R.tokens?.cacheRead||0,cacheWrite:R.tokens?.cacheWrite??R.tokens?.cacheCreation??0},v=Number(R.usd)||0;!v&&R.tokens&&(v=me(R.model,C)),!v&&R.tokens&&!Ta(R.model)&&(w.turns+=1),O.usd+=v,O.tokens.input+=C.input,O.tokens.output+=C.output,O.tokens.cacheRead+=C.cacheRead,O.tokens.cacheWrite+=C.cacheWrite;let D=R.model||"unknown";O.models[D]=(O.models[D]||0)+v,S.provider&&(O.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(p+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let y=new Map,b=!1;for(let[S,E]of c)for(let O of E.segments){if(!(O.usd>0||O.activeMs>0||O.turns>0||O.toolCalls>0))continue;let R=O.textCat?.category??i.get(S)?.category??Cp(O.tools)?.category??a?.category??"uncategorized";y.has(R)||y.set(R,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let C=y.get(R);C.usd+=O.usd,C.activeMs+=O.activeMs,C.turns+=O.turns,C.toolCalls+=O.toolCalls;for(let[D,B]of Object.entries(O.models))C.models[D]=(C.models[D]||0)+B;let v=O.provider||E.provider;v&&(C.providers[v]=(C.providers[v]||0)+O.usd),v==="cursor"&&O.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:b,unpricedTurns:w.turns}}function kk(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 w of t||[]){let{track:y,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:E,merged:O,failed:nt,sawCursorActivity:R,unpricedTurns:C}=cM(w,{since:r});R&&(i=!0),c+=C;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:O,subtasksFailed:nt};for(let[D,B]of S){v.usd+=B.usd,v.activeMs+=B.activeMs,v.turns+=B.turns,v.categories[D]={usd:In(B.usd),activeMs:B.activeMs};for(let[T,g]of Object.entries(B.models))v.models[T]=In((v.models[T]||0)+g);for(let[T,g]of Object.entries(B.providers))v.providers[T]=In((v.providers[T]||0)+g);o.has(D)||o.set(D,iM());let lt=o.get(D);lt.usd+=B.usd,lt.activeMs+=B.activeMs,lt.turns+=B.turns,lt.toolCalls+=B.toolCalls;for(let[T,g]of Object.entries(B.models))lt.models[T]=(lt.models[T]||0)+g;for(let[T,g]of Object.entries(B.providers))lt.providers[T]=(lt.providers[T]||0)+g;let q=lt.byTrack.get(y?.id)||{usd:0,activeMs:0};q.usd+=B.usd,q.activeMs+=B.activeMs,lt.byTrack.set(y?.id,q)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=In(v.usd),v.dominantCategory=lM(v.categories),s.push(v))}let l=[...o.values()].reduce((w,y)=>w+y.usd,0),u=[...o.values()].reduce((w,y)=>w+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),m=[...o.entries()].map(([w,y])=>({category:w,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:In(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:bk(y.models),providers:bk(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,S)=>S[1].usd-b[1].usd||S[1].activeMs-b[1].activeMs).slice(0,2).map(([b,S])=>({trackId:b,name:p.get(b),usd:In(S.usd),activeMs:S.activeMs}))})).sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs);return s.sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:In(l),activeMs:u,turns:[...o.values()].reduce((w,y)=>w+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var lM=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},bk=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,In(n)]));var uo={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function fM(t){let e=String(t||"7d");return e in uo?{key:e,ms:uo[e]}:{key:"7d",ms:uo["7d"]}}function pM(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=si(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 mM(t){if(!t?.cwd)return null;try{let n=(await uM.readFile(dM.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 xk(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:fM(e),f=[];for(let d of t||[]){let p=await L(d),m=pM(d,p);i&&c<o&&(m=m.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:m,brief:await mM(d)})}return{window:l,...kk(f,{windowMs:u,now:c})}}var Rn=36e5,Ak=24*Rn,hM=400,wM=12,fo=t=>Math.round(t*1e4)/1e4;function gM({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in uo?String(t||"7d"):"7d",i=uo[a];return{key:a,since:i==null?null:r-i,until:r}}var yM=(t,e)=>t!=null&&e-t<=48*Rn?Rn:Ak;function Sk(t,e){if(e===Rn)return Math.floor(t/Rn)*Rn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function bM(t,e){if(e===Rn)return t+Rn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var vk=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Tk=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},kM=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,fo(n)]));async function _k(t,e={}){let n=e.now??Date.now(),r=gM({...e,now:n}),o=r.since==null?Ak:yM(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=vk(),f=0,d=0,p=0,m=0,w=0,y=new Set,b=new Map,S=[],E=[],O=[],nt=null;for(let v of t||[]){let B=(await L(v)).filter(T=>T.type==="cost.recorded");if(!B.length){v.archivedAt||O.push(v.name||v.id);continue}let lt=[];for(let T of B){let g=T.ts??0;s(g)?lt.push(T):i(g)&&(w+=Ln(T).usd)}if(!lt.length)continue;let q=he(lt,{boxLabels:jd(v)});f+=q.totalUsd,d+=q.turns,p+=q.repricedTurns||0,m+=q.unpricedTurns||0;for(let T of q.unpricedModels||[])y.add(T);Tk(u,q.totalTokens);for(let[T,g]of Object.entries(q.byModel))c[T]||={usd:0,events:0,tokens:vk()},c[T].usd=fo(c[T].usd+g.usd),c[T].events+=g.events,Tk(c[T].tokens,g.tokens);for(let[T,g]of Object.entries(q.byProvider))l[T]||={usd:0,events:0},l[T].usd=fo(l[T].usd+g.usd),l[T].events+=g.events;for(let T of lt){let{usd:g}=Ln(T);if(!g)continue;let h=Sk(T.ts??0,o),A=b.get(h)??{t:h,total:0,byProvider:{}};A.total+=g;let _=T.provider||"unknown";A.byProvider[_]=(A.byProvider[_]||0)+g,b.set(h,A),(nt==null||h<nt)&&(nt=h)}S.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:q.totalUsd,turns:q.turns,providers:Object.keys(q.byProvider)});for(let[T,g]of Object.entries(q.byBox))E.push({trackId:v.id,trackName:v.name||v.id,boxId:T,...g})}let R=r.since!=null?Sk(r.since,o):nt,C=[];for(let v=R,D=0;v!=null&&v<r.until&&D<hM;v=bM(v,o),D++){let B=b.get(v);C.push(B?{t:v,total:fo(B.total),byProvider:kM(B.byProvider)}:{t:v,total:0,byProvider:{}})}return S.sort((v,D)=>D.usd-v.usd),E.sort((v,D)=>D.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:C,fleet:{totalUsd:fo(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:O},prior:a!=null?{totalUsd:fo(w),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:E.slice(0,wM)}}import po from"node:fs/promises";import xM from"node:fs";import Nt from"node:path";import SM from"node:os";import{execFile as vM}from"node:child_process";import{promisify as TM}from"node:util";var AM=TM(vM),Ek=process.env.BOSTRAT_STATE_DIR||Nt.join(process.env.HOME||SM.tmpdir(),".bostrat"),Li=!1,Ld=null;async function Fd(t,e){let n=await po.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await po.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await po.mkdir(Nt.dirname(e),{recursive:!0}),await po.copyFile(t,e),!0)}var _M=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Fi(t,e,{skip:n=null}={}){let r=0,o=await po.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(_M.has(s.name)||n?.has(s.name))continue;let a=Nt.join(t,s.name);s.isDirectory()?r+=await Fi(a,Nt.join(e,s.name)):s.isFile()&&(r+=await Fd(a,Nt.join(e,s.name))?1:0)}return r}async function Ik({stateDir:t=Ek,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Ek}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await po.mkdir(e,{recursive:!0});let o=0;o+=await Fd(Nt.join(t,"tracks.json"),Nt.join(e,"tracks.json"))?1:0,o+=await Fd(Nt.join(t,"events.jsonl"),Nt.join(e,"events.jsonl"))?1:0,o+=await Fi(Nt.join(t,"events"),Nt.join(e,"events")),o+=await Fi(Nt.join(t,"memory"),Nt.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await Fi(Nt.join(u.cwd,".bostrat"),Nt.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>AM("git",["-C",e,...u]);xM.existsSync(Nt.join(e,".git"))||(await a("init","-q"),await a("config","user.email","agent@bostrat.local"),await a("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await a("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await dc(e).catch(()=>{}));let i=!1,{stdout:c}=await a("status","--porcelain");c.trim()&&(await a("add","-A"),await a("commit","-q","-m",`state sync ${new Date().toISOString()}`),i=!0);let l=!1;if(n&&(i||Li))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,Li&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),Li=!1,Ld=null}catch(u){Li=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
394
|
-
`).slice(-3).join(" | ").slice(0,300);f!==
|
|
395
|
-
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function
|
|
396
|
-
`).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
|
|
397
|
-
`).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
|
|
398
|
-
`),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:
|
|
399
|
-
`,{mode:384}),
|
|
389
|
+
_${o} archived_`:"")}async function HO(t,e){let n=await zn();if(!n)return pt("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 pt("Usage: `/bostrat start <name> [repo]`");let i=await r.listRepos(),c=a[a.length-1]?.toLowerCase(),l=a.length>1?i.find(f=>f.name.toLowerCase()===c):null,u=(l?a.slice(0,-1):a).join(" ");return l?(dr(kk(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),pt(`Starting *${Y(u)}* on ${Y(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:FO(u,i)}}case"archive":{if(!a.length)return pt("Usage: `/bostrat archive <track>`");let{match:i,live:c}=Ud(await r.listTracks(),a.join(" "));return i?(dr(BO(t,n,{track:i,responseUrl:e.response_url})),pt(`Archiving *${Y(i.name)}*\u2026`)):Hd(c,a.join(" "))}case"handoff":{let i=r.knownAgents??DO,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return pt(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=Ud(await r.listTracks(),l);return u?(dr(jO(t,n,{track:u,agent:c,responseUrl:e.response_url})),pt(`Handing *${Y(u.name)}* to ${Y(c)}\u2026`)):Hd(f,l)}case"stop":{if(!a.length)return pt("Usage: `/bostrat stop <track>`");let{match:i,live:c}=Ud(await r.listTracks(),a.join(" "));return i?(dr(LO(t,n,{track:i,responseUrl:e.response_url})),pt(`Stopping *${Y(i.name)}*\u2026`)):Hd(c,a.join(" "))}case"list":return pt(await UO(r));default:return pt(PO)}}async function WO(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await Gd(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 Ze(),await po(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(l||"question")}~
|
|
390
|
+
\u2713 *${Y(a)}*`}}]}).catch(()=>{})}}catch(c){await qt(n.response_url,pt(`Couldn't answer: ${c?.message||c}`))}}async function qO(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(d=>d.type==="pr.offered"&&d.payload?.offerId===s);if(!u)throw new Error("unknown offer");if(l.some(d=>d.type==="pr.offer.answered"&&d.payload?.offerId===s))throw new Error("offer already answered");let f=a==="pr"?["commit","push","pr"]:a==="push"?["commit","push"]:null;f&&await i.sayToLead(c.id,di(f,{branch:u.payload.branch,base:u.payload.base}),{kind:f.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:f?a:"dismiss",actions:f}})}catch(c){await qt(n.response_url,pt(`Couldn't act on the offer: ${c?.message||c}`))}}async function KO(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await zn();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 qt(e.response_url,{...pt(`Starting *${Y(s)}*\u2026`),replace_original:!0}),dr(kk(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"?dr(WO(t,r,e,o)):n.action_id==="bostrat-ship"&&dr(qO(t,r,e,o))}async function GO(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 MO(e.channel,e.thread_ts);if(!n)return;let r=await zn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await fr(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function VO(t){return async e=>{if(e.type==="slash_commands")return HO(t,e.payload);if(e.type==="interactive"){await KO(t,e.payload);return}if(e.type==="events_api"){await GO(t,e.payload?.event);return}}}var zO=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 JO(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${Y(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:Ms(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 XO(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await po(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await fr(t,e,r.question||"The lead needs an answer",JO(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await Ze()}async function YO(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 Ze(),await po(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(s||"question")}~
|
|
391
|
+
\u2713 _answered_`}}]})}async function ZO(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 fr(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${Y(Ms(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await Ze()}async function QO(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await Ze();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await po(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function tC(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??eC)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await mk(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${Y(o.name)}*${a}${o.group?` (group \`${Y(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${Y(a.name)}*`).join(", ");await fr(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${Y(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function eC(t,e){let n=await pn(t);if(!n)return null;let r=Ne(n,e);if(!r)return null;let o=await Cs.readFile(zt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function nC(t,e){if(!zO.has(e.type))return;let n=await Gd(e.trackId);if(!n)return;let r=await zn();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 Ze(),await qd(r,n);return;case"turn.agent":{if(!o.text)return;let s=NO(o.text);if(!s)return;await fr(r,n,s,[{type:"section",text:{type:"mrkdwn",text:Y(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Ka(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return XO(r,n,e);case"prompt.answered":return YO(r,n,e);case"pr.offered":return ZO(r,n,e);case"pr.offer.answered":return QO(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 fr(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 Ze(),await qd(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await fr(r,n,`Live preview ${o.name}: ${Va(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${Y(o.name)}*: ${Va(o.slug)}
|
|
392
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return tC(r,n,e,t.deps)}}function xk(t){let e={deps:t};Ft.on("event",s=>{nC(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await zn()||(r&&clearInterval(r),r=null,n=hk({getToken:async()=>(await zn())?.appToken,onEnvelope:VO(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import rC from"node:fs/promises";function oC(t){let e=new Map;for(let n of String(t||"").split(`
|
|
393
|
+
`)){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 sC(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 aC(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Oe(e.cwd,e.sessionId):e?.cwd?qc(e.cwd):null}async function Sk(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 iC(r,o,t,i);break;default:break}return a}async function iC(t,e,n,r){let o=[],s=aC(n,e);if(s)try{o=oC(await rC.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=sC(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:dn(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.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 uC from"node:fs";import Vd from"node:fs/promises";import dC from"node:http";import mo from"node:path";import qi from"node:os";import{createRequire as fC}from"node:module";import zd from"ws";import cC from"node:path";import lC from"node:os";var Ns=process.env.BOSTRAT_STATE_DIR||cC.join(process.env.HOME||lC.tmpdir(),".bostrat");function vk(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function Tk(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var Hi=(()=>{try{return fC(import.meta.url)("../package.json").version||null}catch{return null}})(),pC=process.env.BOSTRAT_STATE_DIR||mo.join(process.env.HOME||qi.tmpdir(),".bostrat");function mC(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 hC({home:t=process.env.HOME||qi.homedir(),env:e=process.env}={}){let n=r=>{try{return uC.statSync(r),!0}catch{return!1}};return{claude:n(mo.join(t,".claude",".credentials.json")),codex:n(mo.join(e.CODEX_HOME||mo.join(t,".codex"),"auth.json"))}}var It=1,Ak=1e3,wC=3e4,gC=6e4,yC=1500,bC=500,Wi=100,kC=2e3,xC=25e3,SC=6e4,vC=6e4;function Ek({url:t,token:e,nodeId:n=qi.hostname(),name:r=qi.hostname(),listTracks:o,detectAgents:s=null,sampleMetrics:a=null,metricsMs:i=vC,selfUrl:c=`http://127.0.0.1:${process.env.PORT||3362}`,log:l=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=Tk(t);let u=c.replace(/^http/,"ws"),f=new Map,d=new Map,p=mo.join(pC,"plane-link","cursor.json"),m={},w={},y=new Map,k=null,S=!1,_=!1,$=Ak,z=null,O=null,P=null,v=null,M=null,D=!1;async function it(){try{m=JSON.parse(await Vd.readFile(p,"utf8"))}catch{m={}}}function ct(){D=!0,!P&&(P=setTimeout(async()=>{if(P=null,!!D){D=!1;try{await Vd.mkdir(mo.dirname(p),{recursive:!0}),await Vd.writeFile(p,JSON.stringify(m))}catch(G){l.error("[plane-link] cursor flush failed:",G?.message||G)}}},kC),P.unref?.())}function T(G){return!k||k.readyState!==zd.OPEN?!1:(k.send(JSON.stringify(G)),!0)}function g(G){let B=Date.now();for(let{trackId:b,event:E}of G){let lt=y.get(E.id);y.set(E.id,{trackId:b,seq:E.seq,event:E,sentAt:B,tries:(lt?.tries??0)+1})}return T({v:It,ch:"events",type:"append",events:G})}function h(G){let B=null;for(let b of y.values())b.trackId===G&&(B==null||b.seq<B)&&(B=b.seq);return B}function A(G){let B=w[G];if(B==null)return;let b=h(G),E=b==null?B:Math.min(B,b-1);E>(m[G]??0)&&(m[G]=E,ct())}function I(){if(!S)return;let G=Date.now(),B=[...y.values()].filter(b=>b.event&&G-b.sentAt>=SC);if(B.length){B.sort((b,E)=>b.trackId===E.trackId?b.seq-E.seq:b.trackId<E.trackId?-1:1),l.log(`[plane-link] re-sending ${B.length} unacked event(s) the plane never confirmed`);for(let b=0;b<B.length;b+=Wi){let E=B.slice(b,b+Wi).map(lt=>({trackId:lt.trackId,event:lt.event}));if(!g(E))return}}}async function J(){if(!a)return;let G;try{G=await a()}catch(B){l.error("[plane-link] metrics sample failed:",B?.message||B);return}G&&T({v:It,ch:"ctl",type:"metrics",metrics:G})}function W(){a&&(M&&clearInterval(M),J(),M=setInterval(J,i),M.unref?.())}async function at(){let G;try{G=await o()}catch(B){l.error("[plane-link] backfill listTracks failed:",B?.message||B);return}T({v:It,ch:"ctl",type:"tracks",ids:G.map(B=>B.id).filter(B=>typeof B=="string")});for(let B of G){if(!S)return;let b;try{b=await L(B)}catch{continue}let E=m[B.id]??0,lt=b.filter(nt=>nt.seq>E&&typeof nt.id=="string");for(let nt=0;nt<lt.length;nt+=Wi){let bt=lt.slice(nt,nt+Wi).map(Dt=>({trackId:B.id,event:Dt}));if(!g(bt))return}}}function Mt(G){!S||!G?.trackId||typeof G.id!="string"||g([{trackId:G.trackId,event:G}])}let St=new Map;async function Vs(G){let{reqId:B,method:b,path:E,headers:lt={},body:nt}=G,bt=Object.fromEntries(Object.entries(lt).filter(([,ue])=>ue!=null)),Dt=new AbortController;St.set(B,Dt);try{if(G.host){await pr(G,bt,Dt);return}let ue=await fetch(c+E,{method:b,headers:bt,body:nt?Buffer.from(nt,"base64"):void 0,signal:Dt.signal}),tn=Object.fromEntries(ue.headers);for(let Zt of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete tn[Zt];if(tn["content-type"]||(tn["content-type"]="application/octet-stream"),T({v:It,ch:"control",type:"http_head",reqId:B,status:ue.status,headers:tn}),ue.body){for await(let Zt of ue.body)if(!T({v:It,ch:"control",type:"http_data",reqId:B,data:Buffer.from(Zt).toString("base64")}))break}T({v:It,ch:"control",type:"http_end",reqId:B,error:null})}finally{St.delete(B)}}function pr(G,B,b){let{reqId:E,method:lt,path:nt,body:bt}=G,Dt=new URL(c);return new Promise(ue=>{let tn=dC.request({host:Dt.hostname,port:Dt.port,method:lt,path:nt,headers:{...B,host:G.host},signal:b.signal},Zt=>{let{connection:hP,"keep-alive":wP,"transfer-encoding":gP,upgrade:yP,...fx}=Zt.headers;T({v:It,ch:"control",type:"http_head",reqId:E,status:Zt.statusCode||502,headers:fx}),Zt.on("data",px=>{T({v:It,ch:"control",type:"http_data",reqId:E,data:Buffer.from(px).toString("base64")})||Zt.destroy()}),Zt.on("end",()=>{T({v:It,ch:"control",type:"http_end",reqId:E,error:null}),ue()}),Zt.on("error",()=>{T({v:It,ch:"control",type:"http_end",reqId:E,error:"view upstream error"}),ue()})});tn.on("error",Zt=>{T({v:It,ch:"control",type:"http_end",reqId:E,error:Zt?.message||"view request failed"}),ue()}),bt&&tn.write(Buffer.from(bt,"base64")),tn.end()})}function zs(){if(_)return;k=new zd(t,{headers:{authorization:`Bearer ${e}`}}),k.on("open",async()=>{let B=null;try{B=await s?.()||null}catch{B=null}T({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Hi,providers:hC(),...B?{agents:B}:{}})}),k.on("message",B=>{let b;try{b=JSON.parse(B)}catch{return}if(b.ch==="ctl"&&b.type==="hello_ok"){S=!0,$=Ak,z=null,l.log(`[plane-link] connected to plane as ${n}`),b.minNodeVersion&&Hi&&mC(Hi,b.minNodeVersion)&&l.error(`[plane-link] this node is bostrat-node ${Hi}; the plane asks for \u2265${b.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),v&&clearInterval(v),v=setInterval(()=>{T({v:It,ch:"ctl",type:"ping"}),I()},xC),v.unref?.(),W(),at().catch(E=>l.error("[plane-link] backfill failed:",E?.message||E));return}if(b.ch==="ctl"&&b.type==="usage_caps"){ii(b.caps??{},{source:"plane",updatedAt:b.updatedAt??Date.now()}).then(E=>l.log(`[plane-link] spend caps ${Object.keys(E.caps).length?`updated (${Object.keys(E.caps).join(", ")})`:"cleared"} from plane`)).catch(E=>l.error("[plane-link] spend-caps push rejected:",E?.message||E));return}if(b.ch==="events"&&b.type==="ack"&&Array.isArray(b.ids)){let E=new Set;for(let lt of b.ids){let nt=y.get(lt);nt&&(y.delete(lt),(w[nt.trackId]??0)<nt.seq&&(w[nt.trackId]=nt.seq),E.add(nt.trackId))}for(let lt of E)A(lt);return}if(b.ch==="control"&&b.type==="http_open"){Vs(b).catch(E=>T({v:It,ch:"control",type:"http_end",reqId:b.reqId,error:E?.message||"tunnel error"}));return}if(b.ch==="control"&&b.type==="http_cancel"){St.get(b.reqId)?.abort();return}if(b.ch==="attach"&&b.type==="open"){let E=new zd(u+b.path,Array.isArray(b.protocols)?b.protocols:[],b.host?{headers:{host:b.host}}:void 0);f.set(b.streamId,E),d.set(b.streamId,[]),E.on("open",()=>{let nt=d.get(b.streamId)||[];d.delete(b.streamId);for(let{buf:bt,bin:Dt}of nt)E.send(bt,{binary:Dt})}),E.on("message",(nt,bt)=>{let Dt=bt?nt:Buffer.from(String(nt));T({v:It,ch:"attach",type:"data",streamId:b.streamId,data:Dt.toString("base64"),...bt?{bin:!0}:{}})});let lt=()=>{d.delete(b.streamId),f.delete(b.streamId)&&T({v:It,ch:"attach",type:"close",streamId:b.streamId})};E.on("close",lt),E.on("error",lt);return}if(b.ch==="attach"&&b.type==="data"){let E=f.get(b.streamId);if(!E)return;let lt=Buffer.from(b.data,"base64"),nt=b.bin===!0,bt=d.get(b.streamId);bt?bt.push({buf:lt,bin:nt}):E.send(lt,{binary:nt});return}if(b.ch==="attach"&&b.type==="close"){let E=f.get(b.streamId);f.delete(b.streamId),d.delete(b.streamId),E?.close();return}});let G=()=>{S&&l.log("[plane-link] disconnected from plane"),S=!1,v&&(clearInterval(v),v=null),M&&(clearInterval(M),M=null),y.clear(),w={};for(let E of f.values())E.close();f.clear(),d.clear();for(let E of St.values())E.abort();if(St.clear(),_||O)return;z==null&&(z=Date.now());let B=Date.now()-z<gC,b=B?yC+Math.random()*bC:$;O=setTimeout(()=>{O=null,zs()},b),O.unref?.(),B||($=Math.min($*2,wC))};k.on("close",G),k.on("error",G)}return Ft.on("event",Mt),it().then(zs),{stop:()=>{_=!0,Ft.off("event",Mt),O&&clearTimeout(O),P&&clearTimeout(P),v&&clearInterval(v),M&&clearInterval(M),T({v:It,ch:"ctl",type:"bye",reason:"shutdown"}),k?.close()},status:()=>({connected:S,nodeId:n,cursor:{...m},inflight:y.size})}}import TC from"node:fs";import _k from"node:fs/promises";import Ps from"node:os";import AC from"node:path";var EC=1024*1024,Ki=t=>Math.round(t/EC);async function _C(t){try{let e=await _k.statfs(t),n=e.blocks*e.bsize,r=n-e.bavail*e.bsize;return{diskUsedMb:Ki(r),diskTotalMb:Ki(n)}}catch{return{diskUsedMb:null,diskTotalMb:null}}}async function IC(){try{return(await $e()).filter(e=>e.isBostrat).length}catch{return null}}async function RC(){let t=0,e=!1;for(let n of["workspaces","worktrees"])try{let r=await _k.readdir(AC.join(Ns,n),{withFileTypes:!0});t+=r.filter(o=>o.isDirectory()).length,e=!0}catch{}return e?t:null}async function Ik({stateDir:t=Ns,now:e=Date.now}={}){let n=Ps.totalmem(),[r,o,s,a]=await Promise.all([Oa().catch(()=>null),_C(TC.existsSync(t)?t:Ps.tmpdir()),IC(),RC()]),i=Ps.cpus()?.length||null,c=Ps.loadavg()?.[0]??null;return{ts:e(),loadavg1:c!=null?Math.round(c*100)/100:null,cpus:i,cpuPct:c!=null&&i?Math.min(100,Math.round(c/i*100)):null,memUsedMb:r!=null?Ki(n-r):null,memTotalMb:Ki(n),...o,boxes:s,worktrees:a,procUptimeS:Math.round(process.uptime()),osUptimeS:Math.round(Ps.uptime())}}function Jd(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 Rk=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),$k=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Gi=t=>Math.round(t*1e4)/1e4;async function Ok(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=0,l=new Set,u=Rk(),f=0,d=[];for(let p of t||[]){let m=await L(p),w=me(m,{boxLabels:Jd(p)});if(w.turns===0){p.archivedAt?f++:d.push(p.name||p.id);continue}o+=w.totalUsd,s+=w.turns,a+=w.repricedTurns||0,i+=w.asRecordedUsd||0,c+=w.unpricedTurns||0;for(let y of w.unpricedModels||[])l.add(y);$k(u,w.totalTokens);for(let[y,k]of Object.entries(w.byModel))n[y]||={usd:0,events:0,tokens:Rk()},n[y].usd=Gi(n[y].usd+k.usd),n[y].events+=k.events,$k(n[y].tokens,k.tokens);for(let[y,k]of Object.entries(w.byProvider))r[y]||={usd:0,events:0},r[y].usd=Gi(r[y].usd+k.usd),r[y].events+=k.events;e.push({id:p.id,name:p.name,usd:w.totalUsd,rollup:w})}return e.sort((p,m)=>m.usd-p.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Gi(o),asRecordedUsd:Gi(i),turns:s,repricedTurns:a,unpricedTurns:c,unpricedModels:[...l],totalTokens:u,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:f+d.length,tracksSilentArchived:f,tracksSilentLive:d},tracks:e}}var $n=t=>`$${t.toFixed(4)}`,ho=(t,e)=>String(t).padEnd(e);function Ck(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 ${$n(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) priced at current table rates (differs from as-recorded)`),e.asRecordedUsd!=null&&Math.abs(e.asRecordedUsd-e.totalUsd)>=.005&&r.push(`as-recorded total ${$n(e.asRecordedUsd)} \xB7 current-rates total ${$n(e.totalUsd)} (${e.totalUsd>=e.asRecordedUsd?"+":""}${$n(e.totalUsd-e.asRecordedUsd)})`),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(` ${ho(a,26)} ${ho($n(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(` ${ho(a.name||a.id,30)} ${$n(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${ho(c.label,40)} ${ho($n(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${ho($n(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
394
|
+
`)}import NC from"node:fs/promises";import PC from"node:path";var Vi=t=>t.payload?.ts??t.ts,$C=12e4,On=t=>Math.round(t*1e4)/1e4,OC=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function CC(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>Vi(S)>=e,a=Er([Lo(n?.name),Lo(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 _=Er(S.payload.task);_&&i.set(S.boxId,_)}let c=new Map,l=S=>{let _=S||"unattributed";return c.has(_)||c.set(_,{segments:[u(null)],lastTs:null,provider:null}),c.get(_)},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}),f=[...r].sort((S,_)=>Vi(S)-Vi(_)),d=0,p=0,m=0,w={turns:0};for(let S of f){let _=l(S.boxId),$=_.segments[_.segments.length-1],z=Vi(S);switch(_.lastTs!=null&&z>_.lastTs&&s(S)&&($.activeMs+=Math.min(z-_.lastTs,$C)),_.lastTs=z,S.provider&&(_.provider=S.provider),S.type){case"turn.user":{let O=S.payload?.text;if(S.source==="hook"||!String(O||"").trim())break;let P=u(Er(O));_.segments.push(P),s(S)&&(P.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let O=S.payload?.tool;O&&($.tools[O]=($.tools[O]||0)+1),s(S)&&($.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let{usd:O,tokens:P,model:v,unpriced:M}=Me(S);M&&(w.turns+=1),$.usd+=O,P&&($.tokens.input+=P.input,$.tokens.output+=P.output,$.tokens.cacheRead+=P.cacheRead,$.tokens.cacheWrite+=P.cacheWrite),$.models[v]=($.models[v]||0)+O,S.provider&&($.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(p+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let y=new Map,k=!1;for(let[S,_]of c)for(let $ of _.segments){if(!($.usd>0||$.activeMs>0||$.turns>0||$.toolCalls>0))continue;let O=$.textCat?.category??i.get(S)?.category??Wp($.tools)?.category??a?.category??"uncategorized";y.has(O)||y.set(O,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let P=y.get(O);P.usd+=$.usd,P.activeMs+=$.activeMs,P.turns+=$.turns,P.toolCalls+=$.toolCalls;for(let[M,D]of Object.entries($.models))P.models[M]=(P.models[M]||0)+D;let v=$.provider||_.provider;v&&(P.providers[v]=(P.providers[v]||0)+$.usd),v==="cursor"&&$.usd===0&&(k=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:k,unpricedTurns:w.turns}}function Nk(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 w of t||[]){let{track:y,events:k}=w;if(!k?.length){a+=1;continue}let{perCategory:S,handoffs:_,merged:$,failed:z,sawCursorActivity:O,unpricedTurns:P}=CC(w,{since:r});O&&(i=!0),c+=P;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:_,subtasksMerged:$,subtasksFailed:z};for(let[M,D]of S){v.usd+=D.usd,v.activeMs+=D.activeMs,v.turns+=D.turns,v.categories[M]={usd:On(D.usd),activeMs:D.activeMs};for(let[T,g]of Object.entries(D.models))v.models[T]=On((v.models[T]||0)+g);for(let[T,g]of Object.entries(D.providers))v.providers[T]=On((v.providers[T]||0)+g);o.has(M)||o.set(M,OC());let it=o.get(M);it.usd+=D.usd,it.activeMs+=D.activeMs,it.turns+=D.turns,it.toolCalls+=D.toolCalls;for(let[T,g]of Object.entries(D.models))it.models[T]=(it.models[T]||0)+g;for(let[T,g]of Object.entries(D.providers))it.providers[T]=(it.providers[T]||0)+g;let ct=it.byTrack.get(y?.id)||{usd:0,activeMs:0};ct.usd+=D.usd,ct.activeMs+=D.activeMs,it.byTrack.set(y?.id,ct)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=On(v.usd),v.dominantCategory=MC(v.categories),s.push(v))}let l=[...o.values()].reduce((w,y)=>w+y.usd,0),u=[...o.values()].reduce((w,y)=>w+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),m=[...o.entries()].map(([w,y])=>({category:w,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:On(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:Mk(y.models),providers:Mk(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((k,S)=>S[1].usd-k[1].usd||S[1].activeMs-k[1].activeMs).slice(0,2).map(([k,S])=>({trackId:k,name:p.get(k),usd:On(S.usd),activeMs:S.activeMs}))})).sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs);return s.sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:On(l),activeMs:u,turns:[...o.values()].reduce((w,y)=>w+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MC=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},Mk=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,On(n)]));var wo={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function DC(t){let e=String(t||"7d");return e in wo?{key:e,ms:wo[e]}:{key:"7d",ms:wo["7d"]}}function BC(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=ui(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 jC(t){if(!t?.cwd)return null;try{let n=(await NC.readFile(PC.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 Pk(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:DC(e),f=[];for(let d of t||[]){let p=await L(d),m=BC(d,p);i&&c<o&&(m=m.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:m,brief:await jC(d)})}return{window:l,...Nk(f,{windowMs:u,now:c})}}var Cn=36e5,Lk=24*Cn,LC=400,FC=12,go=t=>Math.round(t*1e4)/1e4;function UC({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in wo?String(t||"7d"):"7d",i=wo[a];return{key:a,since:i==null?null:r-i,until:r}}var HC=(t,e)=>t!=null&&e-t<=48*Cn?Cn:Lk;function Dk(t,e){if(e===Cn)return Math.floor(t/Cn)*Cn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function WC(t,e){if(e===Cn)return t+Cn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var Bk=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),jk=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},qC=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,go(n)]));async function Fk(t,e={}){let n=e.now??Date.now(),r=UC({...e,now:n}),o=r.since==null?Lk:HC(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=Bk(),f=0,d=0,p=0,m=0,w=0,y=new Set,k=new Map,S=[],_=[],$=[],z=null;for(let v of t||[]){let D=(await L(v)).filter(T=>T.type==="cost.recorded");if(!D.length){v.archivedAt||$.push(v.name||v.id);continue}let it=[];for(let T of D){let g=T.ts??0;s(g)?it.push(T):i(g)&&(w+=Me(T).usd)}if(!it.length)continue;let ct=me(it,{boxLabels:Jd(v)});f+=ct.totalUsd,d+=ct.turns,p+=ct.repricedTurns||0,m+=ct.unpricedTurns||0;for(let T of ct.unpricedModels||[])y.add(T);jk(u,ct.totalTokens);for(let[T,g]of Object.entries(ct.byModel))c[T]||={usd:0,events:0,tokens:Bk()},c[T].usd=go(c[T].usd+g.usd),c[T].events+=g.events,jk(c[T].tokens,g.tokens);for(let[T,g]of Object.entries(ct.byProvider))l[T]||={usd:0,events:0},l[T].usd=go(l[T].usd+g.usd),l[T].events+=g.events;for(let T of it){let{usd:g}=Me(T);if(!g)continue;let h=Dk(T.ts??0,o),A=k.get(h)??{t:h,total:0,byProvider:{}};A.total+=g;let I=T.provider||"unknown";A.byProvider[I]=(A.byProvider[I]||0)+g,k.set(h,A),(z==null||h<z)&&(z=h)}S.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:ct.totalUsd,turns:ct.turns,providers:Object.keys(ct.byProvider)});for(let[T,g]of Object.entries(ct.byBox))_.push({trackId:v.id,trackName:v.name||v.id,boxId:T,...g})}let O=r.since!=null?Dk(r.since,o):z,P=[];for(let v=O,M=0;v!=null&&v<r.until&&M<LC;v=WC(v,o),M++){let D=k.get(v);P.push(D?{t:v,total:go(D.total),byProvider:qC(D.byProvider)}:{t:v,total:0,byProvider:{}})}return S.sort((v,M)=>M.usd-v.usd),_.sort((v,M)=>M.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:P,fleet:{totalUsd:go(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:$},prior:a!=null?{totalUsd:go(w),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:_.slice(0,FC)}}import yo from"node:fs/promises";import KC from"node:fs";import Ct from"node:path";import GC from"node:os";import{execFile as VC}from"node:child_process";import{promisify as zC}from"node:util";var JC=zC(VC),Uk=process.env.BOSTRAT_STATE_DIR||Ct.join(process.env.HOME||GC.tmpdir(),".bostrat"),zi=!1,Xd=null;async function Yd(t,e){let n=await yo.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await yo.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await yo.mkdir(Ct.dirname(e),{recursive:!0}),await yo.copyFile(t,e),!0)}var XC=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Ji(t,e,{skip:n=null}={}){let r=0,o=await yo.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(XC.has(s.name)||n?.has(s.name))continue;let a=Ct.join(t,s.name);s.isDirectory()?r+=await Ji(a,Ct.join(e,s.name)):s.isFile()&&(r+=await Yd(a,Ct.join(e,s.name))?1:0)}return r}async function Hk({stateDir:t=Uk,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Uk}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await yo.mkdir(e,{recursive:!0});let o=0;o+=await Yd(Ct.join(t,"tracks.json"),Ct.join(e,"tracks.json"))?1:0,o+=await Yd(Ct.join(t,"events.jsonl"),Ct.join(e,"events.jsonl"))?1:0,o+=await Ji(Ct.join(t,"events"),Ct.join(e,"events")),o+=await Ji(Ct.join(t,"memory"),Ct.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await Ji(Ct.join(u.cwd,".bostrat"),Ct.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>JC("git",["-C",e,...u]);KC.existsSync(Ct.join(e,".git"))||(await a("init","-q"),await a("config","user.email","agent@bostrat.local"),await a("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await a("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await kc(e).catch(()=>{}));let i=!1,{stdout:c}=await a("status","--porcelain");c.trim()&&(await a("add","-A"),await a("commit","-q","-m",`state sync ${new Date().toISOString()}`),i=!0);let l=!1;if(n&&(i||zi))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,zi&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),zi=!1,Xd=null}catch(u){zi=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
395
|
+
`).slice(-3).join(" | ").slice(0,300);f!==Xd&&(Xd=f,console.warn(`[state-sync] PUSH FAILING (retrying each cycle; off-box copy is STALE): ${f}`))}return{staged:o,committed:i,pushed:l}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as YC}from"node:child_process";import{promisify as ZC}from"node:util";var QC=ZC(YC),tM=12e4,eM=6e4;function nM(t){return`refs/bostrat/wip/${t}`}function Kk(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function rM(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:tM}var Xi=(t,e,n=15e3)=>QC("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),Wk=new Map,qk=new Set;async function oM(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await Xi(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await Xi(r,["remote","get-url","origin"])}catch{return qk.has(r)||(qk.add(r),e.log?.(`[wip-sync] ${n}: no origin remote \u2014 durable code fidelity is chat/memory-only for this Track`)),{pushed:!1,reason:"no-origin"}}try{let o=await ls(r,"bostrat: wip snapshot"),{stdout:s}=await Xi(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if(Wk.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=nM(n);return await Xi(r,["push","--force","origin",`${o}:${i}`],eM),Wk.set(r,a),await R({id:n,cwd:r},{type:"code.pushed",actor:"orchestrator",payload:{ref:i,sha:o,wip:!0,ts:Date.now()}}).catch(()=>{}),e.log?.(`[wip-sync] ${n}: pushed ${o.slice(0,7)} \u2192 ${i}`),{pushed:!0,sha:o,ref:i}}catch(o){return e.log?.(`[wip-sync] ${n}: push failed (will retry): ${String(o?.message||o).split(`
|
|
396
|
+
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function Gk({listTracks:t,sweepMs:e=rM(),log:n=console}){let r=!1,o=async()=>{if(!r){r=!0;try{let a=await t()??[];for(let i of a)i?.archivedAt||i?.kind==="setup"||await oM(i,{log:n})}catch(a){n.log?.(`[wip-sync] sweep failed: ${a?.message||a}`)}finally{r=!1}}},s=setInterval(o,e);return s.unref?.(),o(),{stop:()=>clearInterval(s),cycle:o}}import{execFile as sM}from"node:child_process";import{promisify as aM}from"node:util";import{randomUUID as iM}from"node:crypto";import cM from"node:fs";import lM from"node:os";import uM from"node:path";var Nn=aM(sM),Yi=t=>new Promise(e=>setTimeout(e,t)),Pn=Bc,Zd=process.env.BOSTRAT_USAGE_PROBE_CWD||uM.join(lM.homedir(),".bostrat","usage-probe"),dM=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,fM=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,pM=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Vk=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Mn=null,Ds=null,Bs=null;async function mM(){try{return await Nn(C,N("has-session","-t",Pn)),!0}catch{return!1}}function hM(){Mn&&clearTimeout(Mn),Mn=setTimeout(()=>wM().catch(()=>{}),dM),Mn.unref&&Mn.unref()}async function wM(){Mn&&(clearTimeout(Mn),Mn=null);try{await Nn(C,N("kill-session","-t",Pn))}catch{}}async function gM(){let t=Date.now()+pM;for(;;){let e=await Qd().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Yi(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Yi(500)}}async function yM(){if(await mM())return;if(!await an())throw new Error("host tmux server not running \u2014 start globo-tmux.service");cM.mkdirSync(Zd,{recursive:!0}),await en(Zd);let t=iM();await Nn(C,N("new-session","-d","-s",Pn,"-c",Zd,"-x","200","-y","60",...Dc()));let e=`${Pc} --session-id ${t}`;await Nn(C,N("send-keys","-t",Pn,e,"Enter")),await gM()}async function Qd(){let{stdout:t}=await Nn(C,N("capture-pane","-t",Pn,"-p"));return t}async function bM(){await yM(),await Nn(C,N("send-keys","-t",Pn,"-l","/usage")),await Nn(C,N("send-keys","-t",Pn,"Enter"));let t=Date.now()+fM,e="";for(;await Yi(400),e=await Qd(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Yi(900),e=await Qd(),await Nn(C,N("send-keys","-t",Pn,"Escape")).catch(()=>{}),hM(),e}function kM(t){let e=String(t||"").split(`
|
|
397
|
+
`).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 xM(t){let e=String(t||"").split(`
|
|
398
|
+
`).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 zk({fresh:t=!1}={}){return!t&&Ds&&Date.now()-Ds.at<Vk?Ds.data:Bs||(Bs=(async()=>{try{let e=await bM(),n=kM(e),r={...n,contributors:xM(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return Ds={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 Ds={at:Date.now()-(Vk-3e3),data:n},n}finally{Bs=null}})(),Bs)}var SM=262144;function vM(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 Jk(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:vM(t.resets_in_seconds)}:null}function TM(t){let e=String(t||"").split(`
|
|
399
|
+
`),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:Jk(i.primary),week:Jk(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 tf(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await Qn(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 Lt(n.file,SM),o=TM(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 Xk(t,{fresh:e=!1}={}){let n=new Set;for(let o of t||[])for(let s of o.boxes||[])s.agent&&n.add(s.agent);let r=[];if((n.has("claude")||n.size===0)&&r.push(await Zi({agent:"claude"},{fresh:e})),n.has("codex")){let o=[],s=[...t||[]].sort((i,c)=>(i.archivedAt?1:0)-(c.archivedAt?1:0)||(c.createdAt||0)-(i.createdAt||0));for(let i of s)for(let c of i.boxes||[])c.agent==="codex"&&o.push({...c,cwd:c.cwd||i.cwd});let a=null;for(let i of o.slice(0,8)){let c=await tf(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await tf(null))}return n.has("cursor")&&r.push(await Zi({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Zi(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?tf(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await zk({fresh:e})}}function AM(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var EM=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Qi=(t,e)=>Object.assign(new Error(t),{status:e});async function Yk(t,e){if(!AM(e))throw Qi("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${Qt(e)}${t}`,{headers:EM(e)})}catch(o){throw Qi(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw Qi(r?.error||`plane replied ${n.status}`,n.status);return r}async function Zk(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await Yk(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function Qk(t,{env:e=process.env}={}){if(!t)throw Qi("message id required",400);return(await Yk(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function ef(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 _M(t){let[e,n]=t.split("/"),r=ef(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 t0(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(_M).filter(Boolean)}function IM(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function e0(t){let e=t0(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:IM,allowed(r){if(n)return!0;let o=ef((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}function n0(t,e){let n=t0(t);return{active:n.length>0,allows(r){if(!n.length)return!1;let o=(r.socket&&r.socket.remoteAddress||"").replace(/^::ffff:/,""),s=ef(o);if(s==null||!n.some(i=>i(s)))return!1;let a=e(r);return!!(a&&a.view)}}}import r0 from"node:crypto";import tc from"node:fs";import RM from"node:path";function o0(t=process.env,{stateDir:e=Ns,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=RM.join(e,"api-token");try{let a=tc.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=r0.randomBytes(32).toString("base64url");try{tc.mkdirSync(e,{recursive:!0}),tc.writeFileSync(o,s+`
|
|
400
|
+
`,{mode:384}),tc.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function js(t){let e=t?.socket?.remoteAddress||"";if(!e)return!1;let n=e.replace(/^::ffff:/,"");return n==="::1"||n==="127.0.0.1"||/^127\./.test(n)}function s0(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:r0.timingSafeEqual(n,r)}function a0(t,e=null){let n=t?.headers?.["x-bostrat-token"];if(typeof n=="string"&&n.trim())return n.trim();let r=t?.headers?.authorization||"",o=/^Bearer\s+(.+)$/i.exec(r)?.[1];if(o)return o.trim();try{return(e??new URL(t.url,"http://node")).searchParams.get("token")||""}catch{return""}}var $M=[/^\/api\/github\/token\b/],OM=t=>$M.some(e=>e.test(t));function i0({token:t,enforce:e=!0,log:n=console}={}){return function(o,s,a){let i=o.path||o.url||"";if(!i.startsWith("/api"))return a();let c=a0(o);return s0(c,t)?a():OM(i)?(n.log?.(`[perimeter] refused ${o.method} ${i} \u2014 sensitive route requires the node API token`),s.status(401).json({error:"this route requires the node API token"})):!e||js(o)?a():(n.log?.(`[perimeter] refused ${o.method} ${i} from ${o.socket?.remoteAddress||"?"} \u2014 no node API token`),s.status(401).json({error:"unauthorized"}))}}function c0(t,e,{enforce:n=!0}={}){return!n||s0(a0(t),e)?!0:js(t)}import CM from"node:http";import MM from"node:net";function l0(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 ec(t){let e=l0(t.headers.host);return e?{slug:e,view:gl(e)}:null}var NM=t=>`<!doctype html><meta charset="utf-8">
|
|
400
401
|
<meta http-equiv="refresh" content="3">
|
|
401
402
|
<title>${t.name} \u2014 starting</title>
|
|
402
403
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
403
404
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
404
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
405
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function u0(t,e,n){let r=ec(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(NM(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=CM.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 d0(t,e,n){let r=ec(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
|
|
405
406
|
\r
|
|
406
407
|
bostrat: no live view "${s}"\r
|
|
407
|
-
`),e.destroy(),!0;let a=
|
|
408
|
+
`),e.destroy(),!0;let a=MM.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
|
|
408
409
|
`)+`\r
|
|
409
410
|
\r
|
|
410
|
-
`),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
|
|
411
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,fC)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${pC}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>t0)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${t0}-char cap; a changelog bullet is one line`);r.push({text:i,...c?{area:c}:{}})}}if(!r.length)throw new Error("no `- ` bullets found \u2014 an entry is a bullet list, optionally under a `# headline`");if(r.length>Qk)throw new Error(`${r.length} bullets \u2014 over the ${Qk}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function n0(t,{repos:e=Kd()}={}){let n=new Set(e);return(await ve()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function r0(t,{md:e,day:n=null}){let r=wC(e),o=n==null||n===""?hC():String(n);if(!mC.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await I({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as gC}from"node:child_process";import{promisify as yC}from"node:util";var Vd=yC(gC),o0=3e4,bC=30*6e4,kC=o0,xC=10*6e4,SC=10080*6e4,vC=60*6e4,TC=8,AC=1440*6e4,_C=3,s0="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Gd=60*6e4,EC=15*6e4,Vi=new Map,zi=new Map,IC=/could not resolve|not found|no pull requests?/i,RC=t=>IC.test(String(t?.message||t||""));function $C(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Gd)}var a0={fast:kC,slow:xC,decay:vC};function i0(t,e){return t.armedAt&&e-t.armedAt<bC||$C(t,e)?"fast":e-(t.firstSeen||0)>SC?"decay":"slow"}function OC(t,e){return a0[i0(t,e)]}function js(t,e,n,r){let o=t.lanes?.get(V(e.repo,e.number))||{};return gi({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function MC(t,e){Ge("watch");try{let{stdout:n}=await Vd("gh",["pr","view",String(e),"--repo",t,"--json",s0],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await de({slug:t})}});return JSON.parse(n||"null")}catch(n){throw kn(n)&&xn(n),n}}async function CC(t){Ge("watch");try{let{stdout:e}=await Vd("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",s0],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await de({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw kn(e)&&xn(e),e}}async function NC(){let t=new Map;for(let e of await at().catch(()=>[]))t.set(e.id,e);return t}async function PC(t,e){await I({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 DC(t,e){let n=await sr(t);return n?Xr(n,e):null}async function BC(t,e,n){let r=await sr(t);if(!r)throw new Error(`no local checkout for ${t}`);return Yr(r,e,{method:n})}async function jC(t,e){let n=await sr(t);if(!n)throw new Error(`no local checkout for ${t}`);return hs(n,e)}async function LC(t,e){await I({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 mo(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 FC=10*6e4;async function UC(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>AC){await mo(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await mo(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await mo(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<FC)return;await mo(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await mo(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await mo(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await c0(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Ps(t,e,n){await I({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 HC(t,e){try{return await Vd("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Ds(t,e,n,r=()=>!0){return(await L(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Bs=(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 WC(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 qa(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ga(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<EC;await Kh(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await qh(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Ds(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Ps(a,"pushed",{...Bs(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Gd)await Ka(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 Ka(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=js(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Ds(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Ps(a,"ci-fixed",{...Bs(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ka(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Gd)await Va(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Va(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){js(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Ds(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Ps(a,"reconciled",{...Bs(t,e),base:i.baseRefName||null}).catch(()=>{}),await Va(t.repo,t.number).catch(()=>{})}}}}async function qC(t,e,n,r){let o=js(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Ds(s,"closed",t.number)&&await Ps(s,"closed",{...Bs(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function KC(t,e,n){js(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Ds(r,"lost",t.number)&&await Ps(r,"lost",Bs(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function c0(t,e,n,r,o={}){let s=js(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await L(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function GC(t={}){let{now:e=Date.now(),auth:n=Ku,view:r=MC,list:o=CC,links:s=Vh,trackMap:a=NC,emitMerged:i=PC,mark:c=wn,archiveIfArmed:l=vn,notifyIfArmed:u=Tn,notifyIfResolved:f=bd,batchThreshold:d=_C,detail:p=DC,merge:m=BC,updateBranch:w=jC,lookup:y=Bt,disarm:b=Yo,emitDisarmed:S=LC,localHasCommit:E=HC}=t;if(!(await n().catch(()=>null))?.authenticated||li()>0)return 0;let R=(await s().catch(()=>[])).filter(T=>e-(Vi.get(V(T.repo,T.number))||0)>=OC(T,e));if(!R.length)return 0;let C=Date.now(),v=new Map,D=new Map;for(let T of R){let g=i0(T,e);v.set(V(T.repo,T.number),{lane:g,intervalMs:a0[g]}),Vi.set(V(T.repo,T.number),e),D.has(T.repo)||D.set(T.repo,[]),D.get(T.repo).push(T)}let B=await a().catch(()=>new Map),lt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:w,lookup:y,disarm:b,emitDisarmed:S,localHasCommit:E};for(let[T,g]of D){let h,A=new Set;if(g.length>d)h=await o(T).catch(()=>null);else{h=new Map;for(let _ of g)try{let J=await r(T,_.number);J?.number!=null&&h.set(Number(J.number),J)}catch(J){RC(J)&&A.add(V(_.repo,_.number))}}if(h)for(let _ of g){let J=V(_.repo,_.number),W=h.get(Number(_.number));if(!W){if(A.has(J)){let it=(zi.get(J)||0)+1;zi.set(J,it),it>=TC&&(await KC(_,B,lt),zi.delete(J),Vi.delete(J))}continue}zi.delete(J),W.state==="MERGED"||W.state==="CLOSED"?(W.state==="MERGED"?await c0(_,W,B,lt):await qC(_,W,B,lt),Vi.delete(J)):(await WC(_,W,B,lt).catch(()=>{}),_.autoMerge&&await UC(_,B,lt).catch(()=>{}))}}let q={};for(let{lane:T}of v.values())q[T]=(q[T]||0)+1;return console.log(`[pr-watch] probed ${R.length} link(s) (${Object.entries(q).map(([T,g])=>`${T} ${g}`).join(", ")}) in ${Date.now()-C}ms`),R.length}function l0(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await GC()}catch{}finally{t=!1}}},o0)}import Qe from"node:fs/promises";import ho from"node:path";import w0 from"node:os";import u0 from"node:fs/promises";import Ji from"node:path";import{execFile as VC}from"node:child_process";import{promisify as zC}from"node:util";import{fileURLToPath as JC}from"node:url";var XC=zC(VC),f0=Ji.dirname(JC(import.meta.url)),YC=Ji.resolve(f0,"..","..","client","dist"),ZC="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",QC=new Map([...ZC].map((t,e)=>[t,e]));function tN(t){let e=[],n=0,r=0;for(let o of t){let s=QC.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 eN(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=tN(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 nN(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 d0=new Map;async function rN(t){let e;try{e=await u0.stat(t)}catch{return null}let n=d0.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await u0.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:eN(r.mappings||"")};return d0.set(t,o),o}catch{return null}}var zd=null;function oN(){return zd||(zd=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):XC("git",["rev-parse","--short","HEAD"],{cwd:Ji.resolve(f0,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),zd}var sN=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function aN(t,{distDir:e=YC}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(sN)){let[a,i,c,l]=s,u=await rN(Ji.join(e,"assets",`${i}.map`)),f=u?nN(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[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 p0(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await oN();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await aN(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var iN=()=>process.env.BOSTRAT_STATE_DIR||ho.join(process.env.HOME||w0.tmpdir(),".bostrat"),Cn=()=>ho.join(iN(),"telemetry"),g0=()=>ho.join(Cn(),".relay-offsets.json"),cN=50,lN=14,uN=3e4,m0=500,Zt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function dN(t,e){if(!t||typeof t!="object")return null;let n=Zt(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:Zt(t.anonId,64),sessionId:Zt(t.sessionId,64),userId:Zt(t.userId,64),release:Zt(t.release,64),appVersion:Zt(t.appVersion,32),route:Zt(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:Zt(t.error.kind,256),message:Zt(t.error.message,8192),stack:Zt(t.error.stack,32768),fingerprint:Zt(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:Zt(s?.n,64),r:Zt(s?.r,128)}))),o}var y0=t=>new Date(t).toISOString().slice(0,10),h0=0;async function b0(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,cN):[]).map(s=>dN(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await p0(s);await Qe.mkdir(Cn(),{recursive:!0});let o=ho.join(Cn(),`${y0(e)}.ndjson`);return await Qe.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
412
|
-
`).join(""),"utf8"),e-
|
|
413
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=m0){let p=f.slice(d,d+m0).map(w=>pN(w)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await S0(r)}return{sent:o,disabled:!1}}var Jd=null,Xd=!1;function v0(){Jd||(Jd=setInterval(async()=>{if(!Xd){Xd=!0;try{await mN()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Xd=!1}}},uN),Jd.unref?.())}import Zi from"node:fs/promises";import I0 from"node:path";import hN from"node:os";var R0=()=>process.env.BOSTRAT_STATE_DIR||I0.join(process.env.HOME||hN.tmpdir(),".bostrat"),$0=()=>I0.join(R0(),"dictation-glossary.json"),wN=100,gN=500,yN=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Qi=null,Yd=null,Zd=Promise.resolve();function T0(){let t={},e=Date.now();for(let n of yN)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function O0(){return Qi||(Yd??=Zi.readFile($0(),"utf8").then(t=>JSON.parse(t)?.terms??T0()).catch(()=>T0()).then(t=>Qi=t).finally(()=>Yd=null),Yd)}async function A0(){let t=JSON.stringify({terms:Qi},null,2),e=$0(),n=`${e}.${process.pid}.tmp`;await Zi.mkdir(R0(),{recursive:!0}),await Zi.writeFile(n,t),await Zi.rename(n,e)}function bN(){return Zd=Zd.then(A0,A0),Zd.catch(()=>{})}async function Qd(){return Object.values(await O0()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var kN=t=>Math.max(1,Math.ceil(t.length/4));async function xN(){let t=[],e=0;for(let{term:n}of await Qd()){let r=kN(n);if(!(e+r>gN)&&(t.push(n),e+=r,t.length>=wN))break}return t}async function SN(t,e){let n=await O0(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var _0=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Yi=t=>t.toLowerCase();function E0(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function M0(t,e){let n=_0(t),r=_0(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 f=s-1;f>=0;f--)a[u][f]=Yi(n[u])===Yi(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)Yi(n[c])===Yi(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(E0(r[l])&&i.add(r[l]),l++);for(;l<s;l++)E0(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await SN(u,"learned");return await bN(),[...i]}var Ls=null;function vN(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 C0(){if(!Ls)return null;let t=await Wo();if(!t||t.apiKey.slice(-6)!==Ls.keyTail)return null;let{keyTail:e,...n}=Ls;return n}async function N0(t,e){let n=await Wo();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 xN())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=vN(o.status,i);c.persistent&&(Ls={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 Ls=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var tf={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function TN(t){if(!t)return null;let e=String(t).trim();if(tf[e])return e;let n=null;for(let r of Object.keys(tf))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function AN(t,e={}){let n=tf[TN(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function _N(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var Fs=null;async function EN(){if(!Fs)return null;let t=await La();if(!t||t.apiKey.slice(-6)!==Fs.keyTail)return null;let{keyTail:e,...n}=Fs;return n}async function IN(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=_N(e.status,o);s.persistent&&(Fs={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}Fs=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var RN={openai:IN};async function P0(){let t=await La();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await EN()}:{configured:!1}}async function D0(){let t=await La();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await RN[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function B0(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=AN(e,s),i=await I(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var W0=ft.dirname(ON(import.meta.url)),j0=Number(process.env.PORT)||3362,tc=ck(process.env),q0=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,wo=Uk(process.env.BOSTRAT_ALLOW_IPS),sf=Hk(process.env.BOSTRAT_VIEW_FRONT_CIDRS,Gi),K0=qk(),G0=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=Ie();x.disable("x-powered-by");x.use((t,e,n)=>{if(Ns(t)||wo.allowed(wo.clientIp(t))||sf.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Xk);x.use(Vk({token:K0,enforce:G0}));x.use("/api/mocks",Ie.json({limit:"1mb"}));x.use("/api/mocks",Ie.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",Ie.json({limit:"1mb"}));x.use("/api/telemetry",Ie.json({limit:"256kb"}));x.use("/api/hook",Ie.json({limit:"2mb"}));x.use("/api/dictation",Ie.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",Ie.raw({type:()=>!0,limit:"25mb"}));x.use(Ie.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:zt,hostTmuxAlive:await sn(),autoTriggerPct:q0})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await Oe()})});x.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 Bn(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})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!bt(n))return e.status(400).json({error:"invalid session name"});if(!await rt(n))return e.status(404).json({error:"session not found"});try{await pe(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!bt(n))return e.status(400).json({error:"invalid session name"});let r=await rt(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Oo(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!bt(n))return e.status(400).json({error:"invalid session name"});try{let r=await am(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)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=va(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})});var L0=10;x.get("/api/tracks",async(t,e)=>{let n=await at(),r=n.filter(s=>s.archivedAt&&!s.archiving),o=n;if(t.query.archived!=="all"&&r.length>L0){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,L0).map(a=>a.id));o=n.filter(a=>!a.archivedAt||a.archiving||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:_i,agentAvailability:await pd(),tickets:await Ua()})});x.get("/api/cost",async(t,e)=>{try{let n=await at();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await gk(n);if(r.cursorUsage=Uu(),t.query.format==="text")return e.type("text/plain").send(yk(r));e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await xk(await at(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await _k(await at(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Uu(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,n.caps=is(),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/caps",(t,e)=>{e.json(is())});x.post("/api/usage/caps",async(t,e)=>{try{e.json(await ni(t.body?.caps??{},{source:"local"}))}catch(n){e.status(400).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Dk(await at(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await Qh(await at()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await tw(await at(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Lb()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await ff(t.get(qs),{byToken:mb,legacy:()=>pb(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Ks("refused",{reason:"unknown-token",hook:t.body?.hook_event_name}),e.json({ok:!1,error:"unknown hook token"});n==="token"&&t.body?.session_id&&r.box?.sessionId&&t.body.session_id!==r.box.sessionId&&Ks("mismatch",{track:r.track.id,box:r.box.name}),Ks(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await ik(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!k0(wo.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await b0(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await Re(),r=new Set((await Promise.all(n.map(a=>ot(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await xo()).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)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await Js(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:zs(),calls:Gg(),latency:ky()})});x.post("/api/github/refresh",(t,e)=>{Sf(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await jk(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await Lk(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function af(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function ec({repo:t=null,number:e=null,branch:n=null}){let r=t?await ot(t):null;if(r&&e!=null){let s=await Bt(r,e),a=s&&await et(s.trackId);if(a)return a}return n?(await at()).find(s=>s.workspace?.branch===n&&af(s,t,r))??null:null}async function V0({repo:t=null,number:e=null,branch:n=null}){let r=await ec({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await ot(t).catch(()=>null):null;return(await at()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&af(a,t,o))??null}async function ue(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ec({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await ot(s).catch(()=>null):null);await I({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function PN(t){let e=(await at()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await ot(n.path);if(r)for(let o of n.prs){if(await Bt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&af(a,n.path,r));s&&await Xt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Ey();await PN(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!ms.has(w))throw new Error(`invalid merge method: ${w}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await ot((await Ve(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await ec({repo:n,number:y,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await hs(n,y),await Xt({repo:b,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Jo(b,y,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Jn(b,y,i===!0),await Xo(b,y,c===!0&&i!==!0),_e(),await ue("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await Yr(n,r,{method:o||"squash"});await ue("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await ot(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Jn(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Xo(p,d.number,!0).catch(()=>{}),await Tn(p,d.number).catch(()=>{}),m=await vn(p,d.number).catch(()=>!1),await wn(p,d.number,"merged").catch(()=>{})),i===!0&&!m){let w=await ec({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await so(w.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.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 ot((await Ve(n)).path).catch(()=>null),l=c?await Yo(c,i).catch(()=>null):null;try{await zu(n,i)}catch(u){if(!l)throw u}_e(),await ue("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})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await ot(n).catch(()=>null),s=await Oy(n,{method:r||"merge",onMerged:a=>{ue("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&Tn(o,a.number).catch(()=>{}).then(()=>vn(o,a.number)).catch(()=>{}).then(()=>wn(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})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:$y()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!ms.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await et(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await ve()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Jn(d.repo,d.number,o===!0).catch(()=>{}),await Xo(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let p={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(p);try{let m=await sr(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let w=await Xr(m,d.number);f.push({r:p,l:d,repoPath:m,pr:w,plan:Iy(w,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:d,l:p,repoPath:m,pr:w,plan:y}of Ry(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await Yr(m,p.number,{method:i}),await ue("merged",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c}),await Tn(p.repo,p.number).catch(()=>{}),await vn(p.repo,p.number).catch(()=>{}),await wn(p.repo,p.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await hs(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Jo(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await ue("auto-armed",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(_e(),!u.some(d=>d.ok)){if(o===!0)for(let d of l)await Jn(d.repo,d.number,!1).catch(()=>{});if(s===!0&&o!==!0)for(let d of l)await Xo(d.repo,d.number,!1).catch(()=>{});return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u})}e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await et(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await ve()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Yo(a.repo,a.number).catch(()=>null),c=await sr(a.repo).catch(()=>null);c&&await zu(c,a.number).catch(()=>{}),i&&(await ue("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}_e(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await et(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=qt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let y=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!y)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return y});else if(f=await us(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let w of f)try{let y=await Xu(w.dir,{title:r,body:o,base:s}),b=w.repo||await ot(w.dir).catch(()=>null);b&&y.number&&await Xt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await ue("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),p.push({repo:w.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:w.name,slug:w.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(w=>w.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(w=>`${w.repo}: ${w.error}`).join("; "),results:p})}let c=await Xu(i.cwd,{title:r,body:o,base:s}),l=await ot(i.cwd).catch(()=>null);l&&c.number&&await Xt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await ue("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await us(n),o=new Map(r.map(a=>[a.base,a])),s=qt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await $b(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await Ob(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Xr(n,r),s=await ot(n).catch(()=>null),a=await V0({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=qt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await My(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ft.join("ci",i.name):"ci",f=ft.join(a.cwd,".bostrat",u),d=ft.join(".bostrat",u,`pr-${o.number}-fail.log`);await Gt.promises.mkdir(f,{recursive:!0}),await Gt.promises.writeFile(ft.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
414
|
-
${
|
|
411
|
+
`),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 f0(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}.${PM(n.host)}/`}function PM(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 l0(e)&&gl(n[0])?n.slice(1).join("."):e}var p0=10,m0=200,DM=120,BM=24,jM=/^\d{4}-\d{2}-\d{2}$/;function nf(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var LM=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function h0(t,e=Date.now()){if(!t)return e-24*36e5;let n=/^(\d+)\s*([hd])$/.exec(String(t).trim());if(n)return e-Number(n[1])*(n[2]==="h"?36e5:24*36e5);let r=Date.parse(t);if(!Number.isNaN(r))return r;throw new Error(`unparseable since "${t}" \u2014 use 24h, 7d, or an ISO date`)}function FM(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Ko(t);if(e.length)throw new Error(`changelog entry appears to contain a secret VALUE (${e.join(", ")}) \u2014 entries ship off-box; never put a credential in one. Replace it and retry.`);let n=null,r=[];for(let o of t.split(`
|
|
412
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,DM)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${BM}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>m0)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${m0}-char cap; a changelog bullet is one line`);r.push({text:i,...c?{area:c}:{}})}}if(!r.length)throw new Error("no `- ` bullets found \u2014 an entry is a bullet list, optionally under a `# headline`");if(r.length>p0)throw new Error(`${r.length} bullets \u2014 over the ${p0}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function w0(t,{repos:e=nf()}={}){let n=new Set(e);return(await Se()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function g0(t,{md:e,day:n=null}){let r=FM(e),o=n==null||n===""?LM():String(n);if(!jM.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await R({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as UM}from"node:child_process";import{promisify as HM}from"node:util";var of=HM(UM),y0=3e4,WM=30*6e4,qM=y0,KM=10*6e4,GM=10080*6e4,VM=60*6e4,zM=8,JM=1440*6e4,XM=3,b0="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",rf=60*6e4,YM=15*6e4,nc=new Map,rc=new Map,ZM=/could not resolve|not found|no pull requests?/i,QM=t=>ZM.test(String(t?.message||t||""));function tN(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<rf)}var k0={fast:qM,slow:KM,decay:VM};function x0(t,e){return t.armedAt&&e-t.armedAt<WM||tN(t,e)?"fast":e-(t.firstSeen||0)>GM?"decay":"slow"}function eN(t,e){return k0[x0(t,e)]}function Hs(t,e,n,r){let o=t.lanes?.get(K(e.repo,e.number))||{};return Ti({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function nN(t,e){Ge("watch");try{let{stdout:n}=await of("gh",["pr","view",String(e),"--repo",t,"--json",b0],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await de({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Sn(n)&&vn(n),n}}async function rN(t){Ge("watch");try{let{stdout:e}=await of("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",b0],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await de({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Sn(e)&&vn(e),e}}async function oN(){let t=new Map;for(let e of await st().catch(()=>[]))t.set(e.id,e);return t}async function sN(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 aN(t,e){let n=await ir(t);return n?eo(n,e):null}async function iN(t,e,n){let r=await ir(t);if(!r)throw new Error(`no local checkout for ${t}`);return no(r,e,{method:n})}async function cN(t,e){let n=await ir(t);if(!n)throw new Error(`no local checkout for ${t}`);return ks(n,e)}async function lN(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 bo(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 uN=10*6e4;async function dN(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>JM){await bo(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await bo(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await bo(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<uN)return;await bo(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await bo(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await bo(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await S0(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Ls(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 fN(t,e){try{return await of("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Fs(t,e,n,r=()=>!0){return(await L(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Us=(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 pN(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 Ja(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ya(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<YM;await nw(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await ew(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Fs(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Ls(a,"pushed",{...Us(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>rf)await Xa(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 Xa(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Hs(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Fs(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Ls(a,"ci-fixed",{...Us(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Xa(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>rf)await Za(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Za(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Hs(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Fs(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Ls(a,"reconciled",{...Us(t,e),base:i.baseRefName||null}).catch(()=>{}),await Za(t.repo,t.number).catch(()=>{})}}}}async function mN(t,e,n,r){let o=Hs(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Fs(s,"closed",t.number)&&await Ls(s,"closed",{...Us(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function hN(t,e,n){Hs(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Fs(r,"lost",t.number)&&await Ls(r,"lost",Us(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function S0(t,e,n,r,o={}){let s=Hs(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await L(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function wN(t={}){let{now:e=Date.now(),auth:n=rd,view:r=nN,list:o=rN,links:s=ow,trackMap:a=oN,emitMerged:i=sN,mark:c=yn,archiveIfArmed:l=An,notifyIfArmed:u=En,notifyIfResolved:f=Od,batchThreshold:d=XM,detail:p=aN,merge:m=iN,updateBranch:w=cN,lookup:y=Pt,disarm:k=es,emitDisarmed:S=lN,localHasCommit:_=fN}=t;if(!(await n().catch(()=>null))?.authenticated||mi()>0)return 0;let O=(await s().catch(()=>[])).filter(T=>e-(nc.get(K(T.repo,T.number))||0)>=eN(T,e));if(!O.length)return 0;let P=Date.now(),v=new Map,M=new Map;for(let T of O){let g=x0(T,e);v.set(K(T.repo,T.number),{lane:g,intervalMs:k0[g]}),nc.set(K(T.repo,T.number),e),M.has(T.repo)||M.set(T.repo,[]),M.get(T.repo).push(T)}let D=await a().catch(()=>new Map),it={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:w,lookup:y,disarm:k,emitDisarmed:S,localHasCommit:_};for(let[T,g]of M){let h,A=new Set;if(g.length>d)h=await o(T).catch(()=>null);else{h=new Map;for(let I of g)try{let J=await r(T,I.number);J?.number!=null&&h.set(Number(J.number),J)}catch(J){QM(J)&&A.add(K(I.repo,I.number))}}if(h)for(let I of g){let J=K(I.repo,I.number),W=h.get(Number(I.number));if(!W){if(A.has(J)){let at=(rc.get(J)||0)+1;rc.set(J,at),at>=zM&&(await hN(I,D,it),rc.delete(J),nc.delete(J))}continue}rc.delete(J),W.state==="MERGED"||W.state==="CLOSED"?(W.state==="MERGED"?await S0(I,W,D,it):await mN(I,W,D,it),nc.delete(J)):(await pN(I,W,D,it).catch(()=>{}),I.autoMerge&&await dN(I,D,it).catch(()=>{}))}}let ct={};for(let{lane:T}of v.values())ct[T]=(ct[T]||0)+1;return console.log(`[pr-watch] probed ${O.length} link(s) (${Object.entries(ct).map(([T,g])=>`${T} ${g}`).join(", ")}) in ${Date.now()-P}ms`),O.length}function v0(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await wN()}catch{}finally{t=!1}}},y0)}import Qe from"node:fs/promises";import ko from"node:path";import $0 from"node:os";import T0 from"node:fs/promises";import oc from"node:path";import{execFile as gN}from"node:child_process";import{promisify as yN}from"node:util";import{fileURLToPath as bN}from"node:url";var kN=yN(gN),E0=oc.dirname(bN(import.meta.url)),xN=oc.resolve(E0,"..","..","client","dist"),SN="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",vN=new Map([...SN].map((t,e)=>[t,e]));function TN(t){let e=[],n=0,r=0;for(let o of t){let s=vN.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 AN(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=TN(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 EN(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 A0=new Map;async function _N(t){let e;try{e=await T0.stat(t)}catch{return null}let n=A0.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await T0.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:AN(r.mappings||"")};return A0.set(t,o),o}catch{return null}}var sf=null;function IN(){return sf||(sf=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):kN("git",["rev-parse","--short","HEAD"],{cwd:oc.resolve(E0,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),sf}var RN=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function $N(t,{distDir:e=xN}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(RN)){let[a,i,c,l]=s,u=await _N(oc.join(e,"assets",`${i}.map`)),f=u?EN(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[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 _0(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await IN();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await $N(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var ON=()=>process.env.BOSTRAT_STATE_DIR||ko.join(process.env.HOME||$0.tmpdir(),".bostrat"),Dn=()=>ko.join(ON(),"telemetry"),O0=()=>ko.join(Dn(),".relay-offsets.json"),CN=50,MN=14,NN=3e4,I0=500,Yt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function PN(t,e){if(!t||typeof t!="object")return null;let n=Yt(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:Yt(t.anonId,64),sessionId:Yt(t.sessionId,64),userId:Yt(t.userId,64),release:Yt(t.release,64),appVersion:Yt(t.appVersion,32),route:Yt(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:Yt(t.error.kind,256),message:Yt(t.error.message,8192),stack:Yt(t.error.stack,32768),fingerprint:Yt(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:Yt(s?.n,64),r:Yt(s?.r,128)}))),o}var C0=t=>new Date(t).toISOString().slice(0,10),R0=0;async function M0(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,CN):[]).map(s=>PN(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await _0(s);await Qe.mkdir(Dn(),{recursive:!0});let o=ko.join(Dn(),`${C0(e)}.ndjson`);return await Qe.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
413
|
+
`).join(""),"utf8"),e-R0>6*3600*1e3&&(R0=e,DN(e).catch(()=>{})),r.length}async function DN(t=Date.now()){let e=C0(t-MN*24*3600*1e3),n=[];try{n=await Qe.readdir(Dn())}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 Qe.rm(ko.join(Dn(),s),{force:!0});let o=await P0();for(let s of r)delete o[s];await D0(o)}var sc=new Map;function N0(t,e=Date.now()){let n=t||"unknown",r=sc.get(n);return r||(sc.size>512&&sc.clear(),r={tokens:40,at:e},sc.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 P0(){try{return JSON.parse(await Qe.readFile(O0(),"utf8"))||{}}catch{return{}}}async function D0(t){await Qe.mkdir(Dn(),{recursive:!0}),await Qe.writeFile(O0(),JSON.stringify(t),"utf8")}function BN(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||$0.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 jN({fetchImpl:t=fetch}={}){let e=await vh();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Qe.readdir(Dn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await P0(),o=0;for(let s of n){let a=ko.join(Dn(),s),i=r[s]||0,c;try{let d=await Qe.open(a,"r");try{let p=(await d.stat()).size;if(p<=i)continue;c=Buffer.alloc(p-i),await d.read(c,0,c.length,i)}finally{await d.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
414
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=I0){let p=f.slice(d,d+I0).map(w=>BN(w)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await D0(r)}return{sent:o,disabled:!1}}var af=null,cf=!1;function B0(){af||(af=setInterval(async()=>{if(!cf){cf=!0;try{await jN()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{cf=!1}}},NN),af.unref?.())}import ic from"node:fs/promises";import H0 from"node:path";import LN from"node:os";var W0=()=>process.env.BOSTRAT_STATE_DIR||H0.join(process.env.HOME||LN.tmpdir(),".bostrat"),q0=()=>H0.join(W0(),"dictation-glossary.json"),FN=100,UN=500,HN=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],cc=null,lf=null,uf=Promise.resolve();function j0(){let t={},e=Date.now();for(let n of HN)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function K0(){return cc||(lf??=ic.readFile(q0(),"utf8").then(t=>JSON.parse(t)?.terms??j0()).catch(()=>j0()).then(t=>cc=t).finally(()=>lf=null),lf)}async function L0(){let t=JSON.stringify({terms:cc},null,2),e=q0(),n=`${e}.${process.pid}.tmp`;await ic.mkdir(W0(),{recursive:!0}),await ic.writeFile(n,t),await ic.rename(n,e)}function WN(){return uf=uf.then(L0,L0),uf.catch(()=>{})}async function df(){return Object.values(await K0()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var qN=t=>Math.max(1,Math.ceil(t.length/4));async function KN(){let t=[],e=0;for(let{term:n}of await df()){let r=qN(n);if(!(e+r>UN)&&(t.push(n),e+=r,t.length>=FN))break}return t}async function GN(t,e){let n=await K0(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var F0=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),ac=t=>t.toLowerCase();function U0(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function G0(t,e){let n=F0(t),r=F0(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 f=s-1;f>=0;f--)a[u][f]=ac(n[u])===ac(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)ac(n[c])===ac(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(U0(r[l])&&i.add(r[l]),l++);for(;l<s;l++)U0(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await GN(u,"learned");return await WN(),[...i]}var Ws=null;function VN(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 V0(){if(!Ws)return null;let t=await Vo();if(!t||t.apiKey.slice(-6)!==Ws.keyTail)return null;let{keyTail:e,...n}=Ws;return n}async function z0(t,e){let n=await Vo();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 KN())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=VN(o.status,i);c.persistent&&(Ws={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 Ws=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var ff={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function zN(t){if(!t)return null;let e=String(t).trim();if(ff[e])return e;let n=null;for(let r of Object.keys(ff))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function JN(t,e={}){let n=ff[zN(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function XN(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var qs=null;async function YN(){if(!qs)return null;let t=await qa();if(!t||t.apiKey.slice(-6)!==qs.keyTail)return null;let{keyTail:e,...n}=qs;return n}async function ZN(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=XN(e.status,o);s.persistent&&(qs={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}qs=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var QN={openai:ZN};async function J0(){let t=await qa();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await YN()}:{configured:!1}}async function X0(){let t=await qa();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await QN[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function Y0(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=JN(e,s),i=await R(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var rx=ft.dirname(eP(import.meta.url)),Z0=Number(process.env.PORT)||3362,lc=vk(process.env),ox=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,xo=e0(process.env.BOSTRAT_ALLOW_IPS),gf=n0(process.env.BOSTRAT_VIEW_FRONT_CIDRS,ec),sx=o0(),ax=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=_e();x.disable("x-powered-by");x.use((t,e,n)=>{if(js(t)||xo.allowed(xo.clientIp(t))||gf.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(u0);x.use(i0({token:sx,enforce:ax}));x.use("/api/mocks",_e.json({limit:"1mb"}));x.use("/api/mocks",_e.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",_e.json({limit:"1mb"}));x.use("/api/telemetry",_e.json({limit:"256kb"}));x.use("/api/hook",_e.json({limit:"2mb"}));x.use("/api/dictation",_e.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",_e.raw({type:()=>!0,limit:"25mb"}));x.use(_e.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Vt,hostTmuxAlive:await an(),autoTriggerPct:ox})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await $e()})});x.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 Fn(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})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});if(!await tt(n))return e.status(404).json({error:"session not found"});try{await pe(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});let r=await tt(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Do(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});try{let r=await wm(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)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Ra(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})});var Q0=10;x.get("/api/tracks",async(t,e)=>{let n=await st(),r=n.filter(s=>s.archivedAt&&!s.archiving),o=n;if(t.query.archived!=="all"&&r.length>Q0){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Q0).map(a=>a.id));o=n.filter(a=>!a.archivedAt||a.archiving||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:Mi,agentAvailability:await Ad(),tickets:await Ga()})});x.get("/api/cost",async(t,e)=>{try{let n=await st();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await Ok(n);if(r.cursorUsage=Qu(),t.query.format==="text")return e.type("text/plain").send(Ck(r));e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await Pk(await st(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await Fk(await st(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Qu(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,n.caps=ds(),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/caps",(t,e)=>{e.json(ds())});x.post("/api/usage/caps",async(t,e)=>{try{e.json(await ii(t.body?.caps??{},{source:"local"}))}catch(n){e.status(400).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Xk(await st(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await uw(await st()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await dw(await st(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await tk()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await Sf(t.get(Xs),{byToken:Rb,legacy:()=>Ib(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Ys("refused",{reason:"unknown-token",hook:t.body?.hook_event_name}),e.json({ok:!1,error:"unknown hook token"});n==="token"&&t.body?.session_id&&r.box?.sessionId&&t.body.session_id!==r.box.sessionId&&Ys("mismatch",{track:r.track.id,box:r.box.name}),Ys(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await Sk(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!N0(xo.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await M0(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await Ie(),r=new Set((await Promise.all(n.map(a=>et(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Eo()).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)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await ea(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:ta(),calls:ry(),latency:Py()})});x.post("/api/github/refresh",(t,e)=>{Cf(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Zk(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await Qk(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function yf(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function uc({repo:t=null,number:e=null,branch:n=null}){let r=t?await et(t):null;if(r&&e!=null){let s=await Pt(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await st()).find(s=>s.workspace?.branch===n&&yf(s,t,r))??null:null}async function ix({repo:t=null,number:e=null,branch:n=null}){let r=await uc({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await et(t).catch(()=>null):null;return(await st()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&yf(a,t,o))??null}async function le(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await uc({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await et(s).catch(()=>null):null);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,...l?{repo:l}:{},...i}}).catch(()=>{})}async function sP(t){let e=(await st()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await et(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&&yf(a,n.path,r));s&&await Jt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Hy();await sP(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!bs.has(w))throw new Error(`invalid merge method: ${w}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let k=await et((await Ve(n)).path);if(!k)throw new Error("repo has no GitHub remote");let S=await uc({repo:n,number:y,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await ks(n,y),await Jt({repo:k,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Qo(k,y,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Yn(k,y,i===!0),await ts(k,y,c===!0&&i!==!0),Ae(),await le("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await no(n,r,{method:o||"squash"});await le("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await et(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Yn(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await ts(p,d.number,!0).catch(()=>{}),await En(p,d.number).catch(()=>{}),m=await An(p,d.number).catch(()=>!1),await yn(p,d.number,"merged").catch(()=>{})),i===!0&&!m){let w=await uc({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await uo(w.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.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 et((await Ve(n)).path).catch(()=>null),l=c?await es(c,i).catch(()=>null):null;try{await ad(n,i)}catch(u){if(!l)throw u}Ae(),await le("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})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await et(n).catch(()=>null),s=await Gy(n,{method:r||"merge",onMerged:a=>{le("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&En(o,a.number).catch(()=>{}).then(()=>An(o,a.number)).catch(()=>{}).then(()=>yn(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})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Ky()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!bs.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await Se()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Yn(d.repo,d.number,o===!0).catch(()=>{}),await ts(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let p={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(p);try{let m=await ir(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let w=await eo(m,d.number);f.push({r:p,l:d,repoPath:m,pr:w,plan:Wy(w,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:d,l:p,repoPath:m,pr:w,plan:y}of qy(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await no(m,p.number,{method:i}),await le("merged",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c}),await En(p.repo,p.number).catch(()=>{}),await An(p.repo,p.number).catch(()=>{}),await yn(p.repo,p.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await ks(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Qo(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await le("auto-armed",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(k){d.ok=!1,d.action="failed",d.reason=k?.message||String(k)}if(Ae(),!u.some(d=>d.ok)){if(o===!0)for(let d of l)await Yn(d.repo,d.number,!1).catch(()=>{});if(s===!0&&o!==!0)for(let d of l)await ts(d.repo,d.number,!1).catch(()=>{});return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u})}e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await Q(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await Se()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await es(a.repo,a.number).catch(()=>null),c=await ir(a.repo).catch(()=>null);c&&await ad(c,a.number).catch(()=>{}),i&&(await le("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}Ae(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await Q(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=Wt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let y=u.find(k=>k.name===w||k.repo===w||k.base===w||k.dir===w);if(!y)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return y});else if(f=await ms(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let w of f)try{let y=await cd(w.dir,{title:r,body:o,base:s}),k=w.repo||await et(w.dir).catch(()=>null);k&&y.number&&await Jt({repo:k,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await le("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:k??w.name}}),p.push({repo:w.name,slug:k??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:w.name,slug:w.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(w=>w.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(w=>`${w.repo}: ${w.error}`).join("; "),results:p})}let c=await cd(i.cwd,{title:r,body:o,base:s}),l=await et(i.cwd).catch(()=>null);l&&c.number&&await Jt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await le("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await ms(n),o=new Map(r.map(a=>[a.base,a])),s=Wt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await Kb(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await Gb(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await eo(n,r),s=await et(n).catch(()=>null),a=await ix({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Wt(a.workspace).find(k=>k.base===n||s&&k.repo===s)??null,c=a.workspace?.kind==="multi",l=await Vy(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ft.join("ci",i.name):"ci",f=ft.join(a.cwd,".bostrat",u),d=ft.join(".bostrat",u,`pr-${o.number}-fail.log`);await Kt.promises.mkdir(f,{recursive:!0}),await Kt.promises.writeFile(ft.join(a.cwd,d),l.map(k=>`\u2550\u2550\u2550\u2550 ${k.name}${k.link?` \xB7 ${k.link}`:""} \u2550\u2550\u2550\u2550
|
|
415
|
+
${k.log??"(no fetchable log \u2014 see link)"}
|
|
415
416
|
`).join(`
|
|
416
|
-
`));let p=l.map(b=>b.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",w=await le(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Xt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Uh(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await ue("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(b=>b.name),log:d})}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})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Xr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await ot(n).catch(()=>null),a=await V0({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=qt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await le(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Xt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Hh(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await ue("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.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=Cg.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await et(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await le(a.id,ai(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await I({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&xb(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var Us=t=>e=>({...e,url:Zk(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Ma({trackId:t.query.track||void 0});e.json({views:n.map(Us(t)),maxLive:fn})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await Km(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Us(t)(n),session:Rr(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await et(n):null;if(!i&&s&&(i=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ll({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:Us(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})}});x.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Us(t)(await Gm(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Us(t)(await Vm(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})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await zm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var DN=[["linear",Kn,wl],["jira",ja,ml]],ef=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await pl(),r={};for(let[o,s,a]of DN){let i=await s();if(!i){ef.delete(o),r[o]={configured:!1};continue}let c=ef.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},ef.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)})}});x.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await pl()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await dh(o),i=n?await et(n):null;!i&&r&&(i=(await at()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ft.sep)))||null),i&&await I({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})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await at()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ft.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 Cr(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})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await et(r):null;if(!o&&n&&(o=(await at()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ft.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 _h(),ticket:await Se(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await Se(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Cr(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})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Il(t.params.id)})});var rc=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;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await $h(t.params.id))}catch(n){let r=n?.message||String(n);e.status(rc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Rl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(rc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Oh(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(rc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await et(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Mh(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(rc(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await at()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ft.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 Nh(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})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await Ch()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await et(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Ph(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})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Hn({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await dn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await dn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ne(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Jt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await dn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ne(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Jt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.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 et(o):null;if(!l&&a&&(l=(await at()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+ft.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await Am({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:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await _m(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})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Im(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await le(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})}});x.delete("/api/mocks/:id",async(t,e)=>{try{let n=(await Vy(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await Rm(t.params.id),...n.length?{danglingFlows:n}:{}})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/flows",async(t,e)=>{try{e.json({flows:await Hy({trackId:t.query.track||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/flows/:id",async(t,e)=>{let n=await Wy(t.params.id);if(!n)return e.status(404).json({error:"flow not found"});e.json({flow:n})});x.post("/api/flows",async(t,e)=>{let{trackId:n,name:r,steps:o,cwd:s}=t.body||{};try{let a=n?await et(n):null;if(!a&&s&&(a=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.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 qy({trackId:a.id,name:r,steps:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,flow:c})}catch(a){let i=a?.message||String(a),c=/already exists/i.test(i)?409:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(i)?400:500;e.status(c).json({error:i})}});x.post("/api/flows/:id/update",async(t,e)=>{try{let{steps:n,note:r}=t.body||{};e.json({ok:!0,flow:await Ky(t.params.id,{steps:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/flows/:id",async(t,e)=>{try{e.json(await Gy(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await at(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ft.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Pa({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await dl(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await dl(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Qm(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(Le(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await et(n):null;if(!a&&s&&(a=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.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 eh({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})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await nh(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})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await rh(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})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await oh(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=e0(t.query.since||null);e.json({since:n,repos:Kd(),prs:await n0(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await et(o):null;if(!a&&s&&(a=(await at()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ft.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await r0(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var BN=120,nf=400,nc=null;x.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>nf)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${nf} cap; a notification is a nudge, put the detail in chat`});let c=Uo(`${n||""}
|
|
417
|
-
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await
|
|
417
|
+
`));let p=l.map(k=>k.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",w=await ce(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Jt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Zh(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await le("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(k=>k.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(k=>k.name),log:d})}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})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await eo(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await et(n).catch(()=>null),a=await ix({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Wt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await ce(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Jt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Qh(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await le("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.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=qg.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await ce(a.id,di(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await R({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&Db(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var Ks=t=>e=>({...e,url:f0(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Ba({trackId:t.query.track||void 0});e.json({views:n.map(Ks(t)),maxLive:mn})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await nh(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Ks(t)(n),session:Mr(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await Q(n):null;if(!i&&s&&(i=(await st()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await yl({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:Ks(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})}});x.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Ks(t)(await rh(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Ks(t)(await oh(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})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await sh(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var aP=[["linear",Vn,Al],["jira",Wa,vl]],pf=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Sl(),r={};for(let[o,s,a]of aP){let i=await s();if(!i){pf.delete(o),r[o]={configured:!1};continue}let c=pf.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},pf.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)})}});x.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await Sl()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Sh(o),i=n?await Q(n):null;!i&&r&&(i=(await st()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ft.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})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await st()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ft.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 Br(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})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await Q(r):null;if(!o&&n&&(o=(await st()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ft.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 Bh(),ticket:await xe(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await xe(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Br(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})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Bl(t.params.id)})});var fc=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;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Uh(t.params.id))}catch(n){let r=n?.message||String(n);e.status(fc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await jl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(fc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Hh(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(fc(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await Q(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Wh(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(fc(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await st()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ft.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 Kh(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})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await qh()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await Q(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Gh(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})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await qn({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await pn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await pn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ne(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(zt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await pn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ne(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(zt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.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 Q(o):null;if(!l&&a&&(l=(await st()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+ft.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await Dm({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:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Bm(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})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Lm(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ce(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})}});x.delete("/api/mocks/:id",async(t,e)=>{try{let n=(await cb(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await Fm(t.params.id),...n.length?{danglingFlows:n}:{}})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/flows",async(t,e)=>{try{e.json({flows:await rb({trackId:t.query.track||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/flows/:id",async(t,e)=>{let n=await ob(t.params.id);if(!n)return e.status(404).json({error:"flow not found"});e.json({flow:n})});x.post("/api/flows",async(t,e)=>{let{trackId:n,name:r,steps:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await st()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.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 sb({trackId:a.id,name:r,steps:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,flow:c})}catch(a){let i=a?.message||String(a),c=/already exists/i.test(i)?409:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(i)?400:500;e.status(c).json({error:i})}});x.post("/api/flows/:id/update",async(t,e)=>{try{let{steps:n,note:r}=t.body||{};e.json({ok:!0,flow:await ab(t.params.id,{steps:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/flows/:id",async(t,e)=>{try{e.json(await ib(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await st(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ft.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Fa({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await kl(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await kl(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=uh(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(Le(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await st()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.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 fh({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})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await ph(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})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await mh(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})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await hh(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=h0(t.query.since||null);e.json({since:n,repos:nf(),prs:await w0(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await st()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ft.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await g0(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var iP=120,mf=400,dc=null;x.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>mf)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${mf} cap; a notification is a nudge, put the detail in chat`});let c=Ko(`${n||""}
|
|
418
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await st()).find(m=>m.cwd&&(s===m.cwd||s.startsWith(m.cwd+ft.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(p=>p.name===l.leadBox),f=(p,m)=>String(p).replace(/\s+/g," ").trim().slice(0,m),d={title:n?f(n,iP):l.name,body:f(i,mf),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await R(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:dc?dc.status().connected:null})}catch(i){e.status(500).json({error:i?.message||String(i)})}});x.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 z0(t.body,t.headers["content-type"]))}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.get("/api/dictation/glossary",async(t,e)=>{e.json({configured:!!await Vo(),terms:await df(),problem:await V0()})});x.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 G0(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await Q(t.params.id))return e.status(404).json({error:"track not found"});e.json(await J0())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});if(n.archivedAt)return e.status(409).json({error:"track is archived"});e.json(await X0())}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.post("/api/tracks/:id/voice-chat/usage",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let{model:r,usage:o,seconds:s}=t.body||{};e.json({ok:!0,...await Y0(n,{model:r,usage:o,seconds:s})})}catch(n){e.status(n?.status||500).json({error:n?.message||String(n)})}});x.post("/api/tracks",async(t,e)=>{let{name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i,ticket:c}=t.body||{};try{let l=await $d({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Br(l,c,{source:"create"})}catch(f){u=f?.message||String(f)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});x.get("/api/tracks/:id",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:cx(Os(t.params.id)),subtasks:fo(t.params.id).map(Gs)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Zb(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Bl(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})}});x.post("/api/tracks/:id/archive",async(t,e)=>{try{e.json({ok:!0,track:await uo(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})}});x.post("/api/tracks/:id/unarchive",async(t,e)=>{try{e.json({ok:!0,track:await nk(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})}});x.get("/api/tracks/:id/messages",async(t,e)=>{let n=await Ed(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});x.get("/api/tracks/:id/events",async(t,e)=>{let n=await Q(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}
|
|
418
419
|
data: ${JSON.stringify(i)}
|
|
419
420
|
|
|
420
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await L(n))i.seq>o&&r(i);let s=
|
|
421
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await L(n))i.seq>o&&r(i);let s=Zp(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
421
422
|
|
|
422
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await
|
|
423
|
-
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await
|
|
423
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=String(t.query.q||"").trim();if(!r)return e.status(400).json({error:"q (query) is required"});let o=Math.min(Math.max(Number(t.query.k)||8,1),50),s=Fo(await L(n)),a=va(s,r,{k:o,role:t.query.role||null,tag:t.query.tag||null});e.json({q:r,indexed:s.N,hits:a.map(i=>({score:Number(i.score.toFixed(3)),seq:i.chunk.seq,ts:i.chunk.ts,role:i.chunk.role,box:i.chunk.boxId,provider:i.chunk.provider,tags:i.chunk.tags,text:i.chunk.text}))})});x.get("/api/memory",async(t,e)=>{e.json({stores:await Gf()})});x.delete("/api/memory/:key/:file",async(t,e)=>{let n=String(t.params.key||"");if(!/^[a-z0-9][a-z0-9._-]*$/.test(n))return e.status(400).json({error:"bad store key"});try{e.json({ok:!0,...await Vf(n,String(t.params.file||""))})}catch(r){let o=r?.message||String(r);e.status(/not found/i.test(o)?404:/bad fact/i.test(o)?400:500).json({error:o})}});var cP=["Run a memory consolidation pass on this workspace's `.bostrat/memory/` directory:","1. Read `MEMORY.md` and every fact file.","2. Merge duplicates and facts that describe the same thing (keep the best-written one; fold unique details in; give the survivor a `supersedes:` entry naming what it absorbed, delete the absorbed file, and remove its index line).","3. Resolve contradictions in favor of the newest evidence \u2014 check the code where feasible before deciding.","4. Mark clearly-stale facts: correct them in place or delete them (file + index line).","5. Bump `metadata.modified` (ISO-8601, now) on every fact you touch.","6. Keep `MEMORY.md` one line per fact, under 200 lines.","When done, summarize what you merged, corrected, and deleted \u2014 that summary is the review record."].join(`
|
|
424
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await ce(t.params.id,cP)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:/not running/i.test(r)?400:500).json({error:r})}});x.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await ok(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})}});x.post("/api/tracks/:id/say",async(t,e)=>{let{text:n,attachments:r}=t.body||{};try{e.json(await ce(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);if(o?.usageCap)return e.status(429).json({error:"usage_cap",message:s,cap:o.usageCap});e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});x.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await Q(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 mb(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})}});x.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await Q(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=ft.basename(String(t.params.file||""));if(!r||!md(ft.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(ft.join(pd(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 lP=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await Q(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(!lP.test(o)||!Number.isInteger(s)||s<0)return e.status(400).json({error:"bad image ref"});let a=(n.boxes||[]).find(d=>d.name===r);if(!a||a.agent!=="claude"||!a.cwd||!a.sessionId)return e.status(404).json({error:"image source not available"});let i=Oe(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=tP.createInterface({input:Kt.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let d of u){if(!d.includes(c))continue;let p;try{p=JSON.parse(d)}catch{continue}if(p.uuid===o){l=p,u.close();break}}let f=l?Kc(l)[s]:null;if(!f||!/^image\//.test(f.mediaType))return e.status(404).json({error:"image not found"});e.set("content-type",f.mediaType),e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.send(Buffer.from(f.data,"base64"))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/interrupt",async(t,e)=>{try{e.json(await Rd(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})}});x.delete("/api/tracks/:id/queue",async(t,e)=>{try{let{removed:n,restored:r,draftRev:o}=await Cb(t.params.id);e.json({ok:!0,removed:n.filter(s=>s.kind!=="ship-notify"),restored:r,draftRev:o})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await _d(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|no answer selected/i.test(a)?409:500;e.status(i).json({error:a})}});x.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await Xb({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})}});x.post("/api/tracks/:id/navigate-prompt",async(t,e)=>{let{promptId:n,questionIndex:r}=t.body||{};try{e.json(await Ob(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})}});x.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n,rev:r}=t.body||{};try{e.json(await Fb(t.params.id,typeof n=="string"?n:"",{rev:typeof r=="number"?r:null}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/required/i.test(s)?400:500;e.status(a).json({error:s})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await Q(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 Zi(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Ub(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})}});x.post("/api/tracks/:id/subtask",async(t,e)=>{let{agent:n,task:r,skipPermissions:o,reason:s}=t.body||{};try{let a=await lk(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Gs(a)})}catch(a){let i=a?.message||String(a);if(a?.usageCap)return e.status(429).json({error:"usage_cap",message:i,cap:a.usageCap});let 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})}});x.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:fo(t.params.id).map(Gs)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await $b(t.params.id,t.params.sid);if(!n)return e.status(404).json({error:"subtask not found"});e.json(n)});x.post("/api/tracks/:id/subtask/:sid/cancel",(t,e)=>{try{e.json({ok:!0,subtask:Gs(Dd(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});x.post("/api/tracks/:id/subtask/:sid/merge-now",(t,e)=>{try{e.json({ok:!0,subtask:Gs(dk(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Gs(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}x.post("/api/tracks/:id/handoff-lead",async(t,e)=>{let{toAgent:n,skipPermissions:r,reason:o}=t.body||{};try{let s=await Md(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);if(s?.usageCap)return e.status(429).json({error:"usage_cap",message:a,cap:s.usageCap});e.status(/already running/i.test(a)?409:/not found/i.test(a)?404:400).json({error:a})}});x.post("/api/tracks/:id/spin-up-view",async(t,e)=>{try{e.json(await Bb(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})}});x.get("/api/tracks/:id/handoff-lead",(t,e)=>{e.json(cx(Os(t.params.id))??{running:!1})});function cx(t){return t?{running:!t.finishedAt,step:t.step,error:t.error,fromBox:t.fromBox,toBox:t.toBox,toAgent:t.toAgent,skipPermissions:t.skipPermissions,startedAt:t.startedAt,finishedAt:t.finishedAt}:null}x.get("/api/debug/handoff",async(t,e)=>{let n=Math.min(Number(t.query.limit)||80,500);e.json({verbose:la(),paths:up,trace:await lp({limit:n})})});var hf=ft.resolve(rx,"..","..","client","dist");Kt.existsSync(hf)&&(x.use(_e.static(hf)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ft.join(hf,"index.html"))));var lx=nP.createServer(x),uP=new oP({noServer:!0}),ux=(t,e,n)=>{if(!(js(t)||xo.allowed(xo.clientIp(t))||gf.allows(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
424
425
|
\r
|
|
425
|
-
`),e.destroy();return}if(
|
|
426
|
+
`),e.destroy();return}if(d0(t,e,n))return;if(!c0(t,sx,{enforce:ax})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
426
427
|
\r
|
|
427
|
-
`),e.destroy();return}let r=
|
|
428
|
+
`),e.destroy();return}let r=vp(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
428
429
|
\r
|
|
429
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
430
|
+
`),e.destroy();return}let o=r.sessionName;if(!yt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
430
431
|
\r
|
|
431
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
432
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;uP.handleUpgrade(t,e,n,i=>kp(i,o,{cols:s,rows:a}))};lx.on("upgrade",ux);var dx=process.env.BOSTRAT_CERT_DIR||ft.join(process.env.HOME||wf.tmpdir(),".bostrat","certs"),tx=Number(process.env.BOSTRAT_TLS_PORT)||3443,ex=ft.join(dx,"bostrat.globopi.key"),nx=ft.join(dx,"bostrat.globopi.crt");if(Kt.existsSync(ex)&&Kt.existsSync(nx)){let t=rP.createServer({key:Kt.readFileSync(ex),cert:Kt.readFileSync(nx)},x);t.on("upgrade",ux),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(tx,lc,()=>{console.log(`[bostrat-agent] https listening on ${lc||"0.0.0.0"}:${tx}`)})}lx.listen(Z0,lc,()=>{console.log(`[bostrat-agent] listening on ${lc||"0.0.0.0"}:${Z0}`),console.log(`[bostrat-agent] tmux socket: ${Vt}`),console.log(`[bostrat-agent] auto-trigger default: ${ox}%`),console.log(`[bostrat-agent] ip allowlist: ${xo.open?"OPEN":process.env.BOSTRAT_ALLOW_IPS||"CLOSED (BOSTRAT_ALLOW_IPS unset)"}`+(gf.active?` \xB7 view front-door: ${process.env.BOSTRAT_VIEW_FRONT_CIDRS} (registered views only)`:"")),ik().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),ck().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),rk().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)),Jb().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)),mP(),pP(),Sg({listTracks:st,getTrack:Q}).then(t=>console.log(`[bostrat-agent] spend caps ${t.configured?"armed":"unconfigured (fail-open)"} \u2014 seeded ${Object.keys(t.spent).length} provider counter(s)`)).catch(t=>console.error("[bostrat-agent] spend-caps seed failed (staying fail-open):",t?.message||t)),Kk()&&Gk({listTracks:st}),v0(),Vh({resolveTrack:Q}),xk({listTracks:st,createTrack:$d,archiveTrack:uo,listRepos:Ie,resolveTrack:Q,sayToLead:ce,answerPrompt:_d,startLeadHandoff:Md,interruptLead:Rd,listSubtaskJobs:fo,cancelSubtask:Dd,readEvents:L,appendEvent:R,knownAgents:Mi}),dP(),No().then(t=>{t||console.warn(`[boot] no host tmux server on ${Vt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),B0(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(dc=Ek({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||wf.hostname(),name:process.env.BOSTRAT_NODE_NAME||wf.hostname(),listTracks:st,detectAgents:Ad,sampleMetrics:Ik}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`),yr().catch(()=>{}),yh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} runbooks to the plane`),()=>{}),zh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} ticket links to the plane`),()=>{}),Wm().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} mocks to the plane`),()=>{}),db().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} flows to the plane`),()=>{}),sw().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} PR links to the plane`),()=>{}))});process.on("SIGTERM",()=>{try{dc?.stop()}catch{}setTimeout(()=>process.exit(0),150)});function dP(){fP(),Da().catch(()=>{}),setInterval(()=>Da().catch(()=>{}),1e4)}function fP(){for(let t of["bostrat-view","bostrat-mock","bostrat-flow","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ft.resolve(rx,"..","bin",t),n=ft.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Kt.existsSync(e))continue;Kt.mkdirSync(n,{recursive:!0});let r=ft.join(n,t);(Kt.existsSync(r)?Kt.readlinkSync(r):null)!==e&&(Kt.rmSync(r,{force:!0}),Kt.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 pP(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await st(),r=await Hk({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 mP(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await st();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await Ed(n.id).catch(()=>{}),await zy(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|